JSX & React Element
我们在书写 react 代码时,常会写 jsx 代码
const element = ( <div id="container"> <input value="foo" type="text" /> <a href="/bar">bara> <span onClick={e => alert("Hi")}>click mespan> div>);
这段代码往往会被 babel 进行编译,转化成如下代码。想了解更多关于 JSX 的知识,可前往 WTF is JSX
const element = createElement( "div", { id: "container" }, createElement("input", { value: "foo", type: "text" }), createElement( "a", { href: "/bar" }, "bar" ), createElement( "span", { onClick: e => alert("Hi") }, "click me" ));
最后上面的这段代码会生成真正被 react 使用的 React Element,该 React Element 以 Object 形式被 React 进行解析处理
const element = { type: "div", props: { id: "container", children: [ { type: "input", props: { value: "foo", type: "text" } }, { type: "a", props: { href: "/bar", children: [{ type: "TEXT ELEMENT", props: { nodeValue: "bar" } }] } }, { type: "span", props: { onClick: e => alert("Hi"), children: [{ type: "TEXT ELEMENT", props: { nodeValue: "click me" } }] } } ] }};
渲染 DOM 元素
我们常会利用 React.render()
将 JSX 渲染到页面真实 DOM 上,那么这个过程又是如何实现的呢?
我们已经知道 JSX 会被转成 React Element , 接下来就是 React Element 是如何被渲染到 DOM 上
function render(element, parentDom) { const { type, props } = element; // Create DOM element const isTextElement = type === "TEXT ELEMENT"; // 文本类型判定 const dom = isTextElement ? document.createTextNode("") : document.createElement(type); // Add event listeners const isListener = name => name.startsWith("on"); Object.keys(props).filter(isListener).forEach(name => { const eventType = name.toLowerCase().substring(2); dom.addEventListener(eventType, props[name]); }); // Set properties const isAttribute = name => !isListener(name) && name != "children"; Object.keys(props).filter(isAttribute).forEach(name => { dom[name] = props[name]; }); // Render children const childElements = props.children || []; childElements.forEach(childElement => render(childElement, dom)); // Append to parent parentDom.appendChild(dom);}
根据上述代码,可知整个 render 过程大致如下:
根据 React Element 的 type 字段识别出每个 Element 的类型,根据不同的类型进行相应的处理
如果元素是普通的 DOM 节点,则添加对应的 DOM 属性、事件监听以及孩子节点
最后将生成的 DOM 放到页面 container
DOM DIFF
通过第一部分,我们已经了解到了
JSX 、React Element 以及 JSX 是如何被转化成 React Element 。
React Element 是如何被React利用,并形成 DOM 结构,最终渲染到页面 container 上
接下来,我们要了解下 React 又是如何利用 DOM diff 将不同阶段的 React Element 进行对比,并最终利用 render 函数渲染到页面上
首先,先说明一个概念,React 内部将 DOM diff 的过程称为协调 reconcile , 而每一次 render 其实就是每一次的 reconcile,将旧的 React Element 与 新的 React Element 进行 reconcile ,得到最终的结果
function render(element, container) { const prevInstance = rootInstance; // 1-虚拟dom主树干- == null const nextInstance = reconcile(container, prevInstance, element); rootInstance = nextInstance; // 2-支树干- 领头啦}
reconcile 接收三个参数,分别是容器 constainer、旧的 React Instance、新的 React Element
此处,需要说明下 React Intsance 的概念
React Instance 成为 React 实例,一个 React 实例表示已呈现给DOM的元素,即旧的React Element
它是具有三个属性的纯JS对象:element
,dom
,和childInstances
。
function reconcile(parentDom, instance, element) { if (instance == null) { // Create instance const newInstance = instantiate(element); parentDom.appendChild(newInstance.dom); return newInstance; } else if (element == null) { // // Remove instance parentDom.removeChild(instance.dom); return null; } else if (instance.element.type === element.type) { // Update instance updateDomProperties(instance.dom, instance.element.props, element.props); instance.childInstances = reconcileChildren(instance, element); instance.element = element; return instance; } else { // Replace instance const newInstance = instantiate(element); parentDom.replaceChild(newInstance.dom, instance.dom); return newInstance; }}
首先如果在该 DOM 结构上,如果旧的 React Instance 为空,说明此时不需要 reconcile,直接将新的 React Element 添加到 DOM 结构上即可
反之,如果新的 React Element 为空,说明该节点要被删除
如果比较后发现新旧 React Element 的 type 相同,则需要对其孩子节点进行比较
反之,type 不同,则直接将新的 React Element 替换旧的 React Element 即可
Component State
到此为止,我们已经了解了 JSX 以及 如何将 JSX 通过 DOM diff render 到页面上。
我们知道 React 以组件为基础,那么组件又是如何进行自身的 React Element 更新呢?
我们常以 class App extends React.Component
进行组件的书写,实际上组件的 React Element 更新就是由 React.Component
进行
class Component { constructor(props) { this.props = props; this.state = this.state || {}; } setState(partialState) { this.state = Object.assign({}, this.state, partialState); // 内部实例的引用 updateInstance(this.__internalInstance); // 更新 虚拟-Dom树和 更新 html }}function updateInstance(internalInstance) { const parentDom = internalInstance.dom.parentNode; const element = internalInstance.element; reconcile(parentDom, internalInstance, element); // 对比-虚拟dom树}
重点就在于 setState 方法
setState 传入当前最新的 state,并将其与旧的 state 进行合并,最终调用 updateInstance 进行更新
updateInstance 获取到组件内部维护的 React Instance,调用 reconcile 进行 DOM Diff ,完成组件的 render
附上 ppt 📎周会showtime-自己写个react.pptx
文章讲述的较为浅显,若想更详细的了解,可前往 didact