文章首发于我的博客 https://github.com/mcuking/bl...相干代码请查阅 https://github.com/mcuking/bl...
反对 JSX
能够通过 babel 实现将 JSX 转化为原生 js,例如:
const hw = <div>Hello World</div>const hw = React.createElement('div', null, "Hello World")
以上两行是等效的,所以本我的项目无需关怀 JSX 语法
virtual-dom
react 中 virtual-dom 的概念,即应用一个 js 对象——vnode 来形容 DOM 节点,而后依据 vnode 进行实际操作 DOM 节点,从而渲染出 DOM 树。
其中,vnode 对象有 3 个属性:
- nodeName: 可能是某个字符串,或 html 标签,抑或是某个函数
- props
- children
以下就是如何通过 createElement 函数,从 JSX 转化后的代码中生成咱们所要的 vnode:
// 负责生成 vnodeexport default function createElement(comp, props, ...args) { let children = [] for (let i = 0; i < args.length; i++) { if (args[i] instanceof Array) { children = children.concat(args[i]) } else { children.push(args[i]) } } return { nodeName: comp, props: props || {}, children }}
从 virtual-dom 到理论渲染
以下是咱们应用 react 写的一个组件
class Animal extends Component { render() { return ( <Pet/> ) }}class Pet extends Component { render() { return ( <Cat/> ) }}class Cat extends Component { render() { return ( <div>i am a cat</div> ) }}render(<Animal/>, document.getElementById('container'))
最终会渲染为 i am a cat
渲染过程是:渲染 Animal 的 Vnode -> 渲染 Pet 的 Vnode -> 渲染 Cat 的 Vnode
这是一个递归的过程:递归的终止条件是——渲染 html 标签:
- 当 nodeName 为 html 标签时,间接操作 dom
- 当 nodeName 为组件时,通过 递归 操作组件执行 render 办法返回的 vnode
代码如下:
function render(vnode, parent) { let dom if(typeof vnode == "string") { dom = document.createTextNode(vnode) parent.appendChild(dom) } else if(typeof vnode.nodeName == "string") { dom = document.createElement(vnode.nodeName) setAttrs(dom, vnode.props) parent.appendChild(dom) for(let i = 0; i < vnode.children.length; i++) { render(vnode.children[i], dom) } } else if (typeof vnode.nodeName == "function") { let func = vnode.nodeName let inst = new func(vnode.props) let innerVnode = inst.render() render(innerVnode, parent) }}// 设置 DOM 节点属性function setAttrs(dom, props) { for (let k in props) { // 属性为 className 时,改为 class if (k === 'className') { dom.setAttribute('class', props[k]) continue } // 属性为 style 时 if (k === 'style') { if (typeof props[k] === 'string') { dom.style.cssText = props[k] } if (typeof props[k] === 'object') { for (let v in props[k]) { dom.style[v] = props[k][v] } } continue } // 属性为 on 结尾的绑定的事件 if (k[0] === 'o' && k[1] === 'n') { dom.addEventListener(k.substring(2).toLowerCase(), props[k], false) continue } // 其余属性间接赋值 dom.setAttribute(k, props[k]) }}
总结一下:
- createElement —— 负责创立 vnode
render —— 是依据生成的 vnode, 渲染到理论的 dom 的一个递归办法
- 当 vnode 是字符串时, 创立 textNode 节点
- 当 vnode.nodeName 是字符串的时, 创立 dom 节点, 依据 props 设置节点属性, 遍历 render children
- 当 vnode.nodeName 是函数的时, 获取 render 办法的返回值 vnode, 执行 render(vnode)
相干文章
- mini-react 实现原理解说 第一讲
- mini-react 实现原理解说 第二讲
- mini-react 实现原理解说 第三讲
- mini-react 实现原理解说 第四讲
- mini-react 实现原理解说 第五讲