本节咱们看如何更新组件。在上一节也反复提到renderComponent这个方法了,这节直接从它入手吧。它位于src/vdom/component.js
文件中。javascript
从参数来看,咱们会惊讶它居然会有这么多参数,原来咱们只看到它有两个参数,第二个为数字。第一个参数不用说,是组件的实例。java
export function renderComponent(component, opts, mountAll, isChild) { //若是它是_disable状态,当即返回, if (component._disable) return; //开始取出它先后的props, state,context, base //base是这个组件的render方法生成的虚拟DOM最后转化出来的真实DOM //若是有这个真实DOM,说明它已经mount了,如今是处于更新状态 let props = component.props, state = component.state, context = component.context, previousProps = component.prevProps || props, previousState = component.prevState || state, previousContext = component.prevContext || context, isUpdate = component.base, nextBase = component.nextBase, //真实DOM initialBase = isUpdate || nextBase, //这个变早比较难理,它是component的render方法生成的虚拟DOM的type函数再实例化出来的子组件,至关于一个组件又return出另外一个组件。一般状况下,组件会return出来的虚拟DOM的type为一个字符串,对应div, p, span这些真实存在的nodeName。而type为函数时,它就是一个组件。 initialChildComponent = component._component, skip = false, rendered, inst, cbase; // 若是是更新状态,会通过shouldComponentUpdate,componentWillUpdate钩子 if (isUpdate) { component.props = previousProps; component.state = previousState; component.context = previousContext; if (opts!==FORCE_RENDER && component.shouldComponentUpdate && component.shouldComponentUpdate(props, state, context) === false) { skip = true; } else if (component.componentWillUpdate) { component.componentWillUpdate(props, state, context); } component.props = props; component.state = state; component.context = context; } //GC component.prevProps = component.prevState = component.prevContext = component.nextBase = null; component._dirty = false; if (!skip) { //mount与update都要调用render方法,这时与官方react有点不同,官方react是没有传参,多是早期官方文档没有规范render的参数吧。然后来的官方源码上,render是没有参数的。这个参数不该该preact来背。 rendered = component.render(props, state, context); // 若是用户定义了getChildContext,那么用它与context生成孩子的context if (component.getChildContext) { context = extend(extend({}, context), component.getChildContext()); } let childComponent = rendered && rendered.nodeName, toUnmount, base; //断定render出来的虚拟DOM是否仍是一个组件 if (typeof childComponent==='function') { // set up high order component link let childProps = getNodeProps(rendered); inst = initialChildComponent; //若是先后两次的子组件的类型都一致,而且key也同样,则用setComponentProps方法更新这个子组件 if (inst && inst.constructor===childComponent && childProps.key==inst.__key) { setComponentProps(inst, childProps, SYNC_RENDER, context, false); } else { //不然要替换原来的组件 //toUnmount用来标识一下子要进行unmount操做 toUnmount = inst; //实例化另外一个组件 component._component = inst = createComponent(childComponent, childProps, context); //刷新真实DOM inst.nextBase = inst.nextBase || nextBase; inst._parentComponent = component; //更新子组件的属性,这里面调用WillRecieveProps钩子 setComponentProps(inst, childProps, NO_RENDER, context, false); //异步渲染子组件,这招比较妙,这里你能够看到isChild参数的做用 renderComponent(inst, SYNC_RENDER, mountAll, true); } base = inst.base; } else { //若是此次render出来的不是组件,而是普通虚拟DOM, cbase = initialBase; // destroy high order component link toUnmount = initialChildComponent; if (toUnmount) { cbase = component._component = null; } if (initialBase || opts===SYNC_RENDER) { if (cbase) cbase._component = null; base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); } } //若是元素节点不一样,而且组件实例也不是一个 if (initialBase && base!==initialBase && inst!==initialChildComponent) { let baseParent = initialBase.parentNode; if (baseParent && base!==baseParent) { baseParent.replaceChild(base, initialBase); if (!toUnmount) { initialBase._component = null; recollectNodeTree(initialBase, false); } } } if (toUnmount) { unmountComponent(toUnmount); } //重写真实DOM component.base = base; if (base && !isChild) { let componentRef = component, t = component; //因为组件能返回组件,可能通过N次render后才能返回一个能转换成为真实DOM的普通虚拟DOM,这些组件经过_parentComponent连接在一块儿,它们都是共享同一个真实DOM(base), 这时咱们须要为这些组件都重写base属性 while ((t=t._parentComponent)) { (componentRef = t).base = base; } //在真实DOM上保存最初的那个组件与组件的构造器 //在真实DOM上保存这么多对象实际上是不太好的实现,由于会致使内存泄露,所以才有了recollectNodeTree这个方法 base._component = componentRef; base._componentConstructor = componentRef.constructor; } } //若是是异步插入进行组件的单个render或者是ReactDOM.render,这些组件实例都会先放到mounts数组中。 if (!isUpdate || mountAll) { mounts.unshift(component); } else if (!skip) { //更新完毕,调用componentDidUpdate,afterUpdate钩子 if (component.componentDidUpdate) { component.componentDidUpdate(previousProps, previousState, previousContext); } if (options.afterUpdate){ options.afterUpdate(component); } } //调用setState, forceUpdate钩子 if (component._renderCallbacks!=null) { while (component._renderCallbacks.length) component._renderCallbacks.pop().call(component); } //执行其余组件的更新或插入,diffLevel为一个全局变量 if (!diffLevel && !isChild) flushMounts(); }
这个函数出现的对象与关系太多了,究竟某某是某某的什么,看下图就知了。node
咱们须要知道,组件render后可能产生普通虚拟DOM与子组件,而只有普通虚拟DOM才能转化为真实DOM。组件的实例经过_component
与_parentComponent
联结在一块,方便上下回溯。而实例老是保存着最后转化出来的真实DOM(base, 也叫initialBase)。base上保存着最上面的那个组件实例,也就是_component,此外,为了方便比较,它的构造器也放在DOM节点上。react
renderComponent这个方法主要处理组件更新时的钩子,及创建父子组件间的联系。数组
这个方法的参数的起名也很奇葩,若是改为dom
renderComponent(componentInstance, renderModel, isRenderByReactDOM, isRenderChildComponent)
则好理解些。显示preact的做者不太想知道其奥秘,所以源码的注释也不多不多。异步
好了,咱们看setComponentProps方法,它在renderComponent用了两次。函数
//更新已有的子组件实例 setComponentProps(inst, childProps, SYNC_RENDER, context, false); //新旧子组件的类型不一致,用新组件的实例进行替换 setComponentProps(inst, childProps, NO_RENDER, context, false);
setComponentProps的源码this
export function setComponentProps(component, props, opts, context, mountAll) { if (component._disable) return; //_disable状态下阻止用户 component._disable = true; if ((component.__ref = props.ref)) delete props.ref; if ((component.__key = props.key)) delete props.key; if (!component.base || mountAll) { //若是没有插入到DOM树或正在被ReactDOM.render渲染 if (component.componentWillMount) component.componentWillMount(); } else if (component.componentWillReceiveProps) { //若是是在更新过程当中 component.componentWillReceiveProps(props, context); } //下面依次设置provProps, props, prevContext, context if (context && context!==component.context) { if (!component.prevContext) component.prevContext = component.context; component.context = context; } if (!component.prevProps) component.prevProps = component.props; component.props = props; component._disable = false; //===================== if (opts!==NO_RENDER) { if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) { renderComponent(component, SYNC_RENDER, mountAll); } else { enqueueRender(component); } } if (component.__ref) component.__ref(component); }
最后看 createComponent,这是建立一个组件实例。React的组件有三种,经典组件,纯组件,无状态组件,前两种都是类的形式,能够归为一种,最后一种是普通函数。但在src/vdom/component-recycler.js
咱们看到它们都是new
出来的。spa
export function createComponent(Ctor, props, context) { let list = components[Ctor.name], inst; //类形式的组件 if (Ctor.prototype && Ctor.prototype.render) { inst = new Ctor(props, context); Component.call(inst, props, context); }else {//无状态组件 inst = new Component(props, context); inst.constructor = Ctor; inst.render = doRender; } if (list) { for (let i=list.length; i--; ) { if (list[i].constructor===Ctor) { inst.nextBase = list[i].nextBase; list.splice(i, 1); break; } } } return inst; }
咱们再看一下doRender,这时恍然大悟,原来preact是统一全部组件之后更新都要经过render方法生成它的普通虚拟DOM或子组件。
function doRender(props, state, context) { return this.constructor(props, context); }
此外,preact还经过collectComponent来回收它的真实DOM,而后在createComponent中重复利用。这是它高效的原因之一。
const components = {}; export function collectComponent(component) { let name = component.constructor.name; (components[name] || (components[name] = [])).push(component); }