-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
先看一个最简单的 preact 例子:
import {h, render} from 'preact';
render(
<div id="app">Hello</div>,
document.body
)经过 babel 转换后可得:
"use strict";
var _preact = require("preact");
(0, _preact.render)((0, _preact.h)(
"div",
{ id: "app" },
"Hello"
), document.body);可见,将一个 jsx 定义的元素挂载到 dom 上是执行了:
render(createElement(type, props, children), parentDom)h 是 createElement 方法的别名。
现在来看下 createElement 的实现:
export function createElement(type, props, children) {
// 配置相应的参数
if (props == null) props = {};
if (arguments.length > 3) {
children = [children];
for (let i = 3; i < arguments.length; i++) {
children.push(arguments[i])
}
}
// 子节点被设为 props.children
if (children != null) {
props.children = children;
}
// 对于组件,设定默认参数
if (type != null && type.defaultProps != null) {
for (let i in type.defaultProps) {
if (props[i] === undefined) props[i] = type.defaultProps[i];
}
}
// 提取 ref
let ref = props.ref;
if (ref) delete props.ref;
// 提取 key
let key = props.key;
if (key) delete props.key;
// 返回 vnode 对象
return createVNode(type, props, null, key, ref);
}
// 组合参数生成 vnode 对象
export function createVNode(type, props, text, key, ref) {
const vnode = {
type,
props,
text,
key,
ref,
_children: null,
_dom: null,
_lastDomChild: null,
_component: null
};
// 这里是一个自定义装饰 vnode 的方法
if (options.vnode) options.vnode(vnode);
return vnode
}现在问题变成:
render(vnode, parentDom)看 render 的实现:
export function render(vnode, parentDom) {
let oldVNode = parentDom._prevVNode;
// 这里将 vnode 用一个空的节点再包起来
vnode = createElement(Fragment, null, [vnode]);
// 比较差异生成 dom 节点
let mounts = [];
diffChildren(parentDom, parentDom._prevVNode = vnode. oldVNode, EMPTY_OBJ, parentDom.ownerSVGElement !== undefined, oldVNode ? null : EMPTY_ARR.slice.call(parentDom.childNodes), mounts, vnode);
commitRoot(mounts, vnode);
}preact 的组件
一些用到的辅助函数
// 复制对象属性
const assign = (obj, props) => {
for (i in props) {
obj[i] = props[i]
}
return obj
}
// 空内容
const Fragment = () => {}Component
export function Component(props, context) {
// 这里从上层接受组件参数与上下文对象
this.props = props;
this.context = context;
}
Component.prototype.setState = function (update, callback) {
// 当第一次执行时, _nextState 为 null 此时会执行后半部分即将 state 复制给 _nextState 并返回。之后其实都是返回 _nextState
let s = (this._nextState !== this.state && this._nextState) || (this._nextState = assign({}, this.state));
// 将 update 的属性复制给 s
if (typeof update !== 'function' || (update = update(s, this.props))) {
assign(s, update);
}
// 若没有传入实质参数则返回
if (update == null) return;
// 更新后的回调函数
if (callback != null) this._renderCallbacks.push(callback);
// 通过 setState 方法更新组件时需要设定 _force 属性为 false 表面为正常更新手段
this._force = false;
// 执行更新
enqueueRender(this);
}
Component.prototype.forceUpdate = function(callback) {
let vnode = this._vnode, dom = this._vnode._dom, parentDom = this._parentDom;
if (parentDom != null) {
// 这里判断 _force 属性若为 null 表明为手动执行该更新方法!因为 setState 会设定 _force 为 false 且每次刷新后 _force 都会变回 null。这么做是为了比较 dom 差异时判断是否要跳过 shouldComponentUpdate 检查
if (this._force == null) this._force = true;
// 执行 dom 差异计算
let mounts = [];
dom = diff(dom, parentDom, vnode, vnode, this._context, parentDom.ownerSVGElement !== undefined, null, mounts, this._ancestorComponent);
if (dom !== null && dom.parentNode !== parentDom) {
parentDom.appendChild(dom);
}
commitRoot(mounts, vnode);
// 将 _force 置为 null
this._force = null;
}
// 更新后的回调,手动更新用
if (callback != null) callback();
}
// render 方法默认返回空内容
Component.prototype.render = Fragment;渲染队列
// 渲染堆栈
let q = [];
// 延迟函数
const defer = typeof Promise == 'function' ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout;
// 入栈
export function enqueueRender(c) {
// 只有当 _dirty 为 false 时才能入栈。且之后 _dirty 被设置为 true。当队列非空时不允许入栈(此时 _dirty 已经被改为 true)
if (!c._dirty && (c._dirty = true) && q.push(c) === 1) {
// 这里将更新操作异步执行
(options.debounceRendering || defer)(process);
}
}
// 出栈执行强制更新
function process() {
let p;
while ((p = q.pop())) {
if (p._dirty) p.forceUpdate();
}
}Reactions are currently unavailable