babel
在runtime时, babel转译器将对每一个节点都执行在编译注释(Pragma)中声明的函数。 例如:
before babel: (the code you write)
/** @jsx h */let foo =Hello!;复制代码
after babel: (the code you run)
var foo = h('div', { id:"foo"}, 'Hello!'); 复制代码
也可以在浏览器里打印看一下结果:
Document 复制代码
createElement
将jsx中的element转换成 plain object
function createElement(type, config, ...args) { const props = Object.assign({}, config); const hasChildren = args.length > 0; const rawChildren = hasChildren ? [].concat(...args) : []; props.children = rawChildren .filter(c => c != null && c !== false) .map(c => c instanceof Object ? c : createTextElement(c)); return { type, props };}function createTextElement(value) { return createElement(TEXT_ELEMENT, { nodeValue: value });}复制代码
这里对text node特殊处理了一下,使其不那么特殊, 如此后面处理时省略了许多判断
instance
每个element(jsx转换而来的)对应一个instance,instance包含了dom, element, childInstances。 如果是custom component 则还有一个publicInstance,是组件的实例.
function instantiate(element) { const { type, props } = element; const isDomElement = typeof type === "string"; // 这里对custom component与built-in component分别做处理 if (isDomElement) { const isTextElement = type === TEXT_ELEMENT; const dom = isTextElement ? document.createTextNode("") : document.createElement(type); // updateDomProperties 作用是更新dom上的属性,比如class、id、或者删除新增事件监听函数等 updateDomProperties(dom, [], props); // 处理子组件 const childElements = props.children || []; const childInstances = childElements.map(instantiate); const childDoms = childInstances.map(childInstance => childInstance.dom); // 将子组件的实例插入到dom上 childDoms.forEach(childDom => dom.appendChild(childDom)); const instance = { dom, element, childInstances }; return instance; } else { const instance = {}; // 或者custom-component 实例 const publicInstance = createPublicInstance(element, instance); // 调用render方法,获得子组件,再获取child instance. // 注意这里是childInstance而不是childInstances,因为custom component只能一个子组件,不能是数组 const childElement = publicInstance.render(); const childInstance = instantiate(childElement); const dom = childInstance.dom; Object.assign(instance, { dom, element, childInstance, publicInstance }); return instance; }}复制代码
createPublicInstance
function createPublicInstance(element, internalInstance) { const { type, props } = element; const publicInstance = new type(props); publicInstance.__internalInstance = internalInstance; return publicInstance;}复制代码
这里的internalInstance其实就是 instance,之所以需要将其绑定到组件实例上是因为 在自定义组件中update需要用到,这里可以先看一下Component的setState实现
setState(partialState) { this.state = Object.assign({}, this.state, partialState); updateInstance(this.__internalInstance);}function updateInstance(internalInstance) { const parentDom = internalInstance.dom.parentNode; const element = internalInstance.element; reconcile(parentDom, internalInstance, element);}复制代码
reconciliation 对组件进行 diff
下面instance是当前的状态,而element是新的状态,将这两者进行diff,就可以得出如何更新dom节点
function reconcile(parentDom, instance, element) { if (instance == null) { // 不存在instance则创建一个 const newInstance = instantiate(element); parentDom.appendChild(newInstance.dom); return newInstance; } else if (element == null) { // 不存在element说明该element已经被删除 parentDom.removeChild(instance.dom); return null; } else if (instance.element.type !== element.type) { // 如果新旧element类型不同则替换 const newInstance = instantiate(element); parentDom.replaceChild(newInstance.dom, instance.dom); return newInstance; } else if (typeof element.type === "string") { // build-in component 更新属性 updateDomProperties(instance.dom, instance.element.props, element.props); instance.childInstances = reconcileChildren(instance, element); instance.element = element; return instance; } else { // 更新custom component的属性 instance.publicInstance.props = element.props; const childElement = instance.publicInstance.render(); const oldChildInstance = instance.childInstance; const childInstance = reconcile( parentDom, oldChildInstance, childElement ); instance.dom = childInstance.dom; instance.childInstance = childInstance; instance.element = element; return instance; }}function reconcileChildren(instance, element) { const dom = instance.dom; const childInstances = instance.childInstances; const nextChildElements = element.props.children || []; const newChildInstances = []; const count = Math.max(childInstances.length, nextChildElements.length); for (let i = 0; i < count; i++) { const childInstance = childInstances[i]; const childElement = nextChildElements[i]; const newChildInstance = reconcile(dom, childInstance, childElement); newChildInstances.push(newChildInstance); } // 过滤掉已经被删除的element return newChildInstances.filter(instance => instance != null);}复制代码
Component
由于之前已经讲过了setState,其他部分就很简单了
class Component { constructor(props) { this.props = props; this.state = this.state || {}; } setState(partialState) { this.state = Object.assign({}, this.state, partialState); updateInstance(this.__internalInstance); }}复制代码
render
function render(element, container) { reconcile(container, null, element);}复制代码
一个完整的例子
Document 复制代码
总结
本文是对文章 的总结。旨在帮助在阅读react源码之前先整体把握react的结构组成,帮助更好的阅读源码。由于react16大量更新了代码,引入了Fiber:Incremental reconciliation,下篇看一下fiber的简单的实现。是同一位作者所写。