前言之前我们已经用比较规范的方式实现了字符串和数字的渲染,接下来我们将渲染原生 DOM。
预备知识 正则我们在匹配事件和 style 属性的时候,我们需要用到简单的正则知识点
除了使用正则的 test 判断以外,我们还可以和String.place 搭配使用。注意:这个方法不会改变原字符串。
jquery 事件的命名空间和事件代理在元素创建的时候,我们会在 document 上采用事件委托绑定事件(后面会解释原因),元素移除的时候我们还要移除事件。为了简便操作我们会用命名空间来实现。
原理分析我们在写 React 的代码的时候,我们一般会写 JSX.而在我们的现在开发中,会经过 babel 等转换为 React.createElement 代码。这也就是 JSX 的作用,简单可以理解为 React.createElement 的语法糖。
我们用下面的代码示例
1 2 3 4 5 6 7 8 9 10 11 <div onClick={() => { console .log (1 ); }} style={{ backgroundColor : "red" }} > 你好 <span className="test" id="thisisaspan" > hehe </span> </div>
而如果我们直接在代码中打印这个 element
会是什么样子呢?代码可以看这里
可以看到,打印出来是一个对象,里面有$$typeof、key、type、props
等等很多属性。而我们需要关注的是 type
属性和 props
属性,甚至可以可以看到,props
里面有个 children
属性,而且可以继续展开,里面就是层层递进的 children
子元素。其实这个对象就是虚拟 DOM ,在后面更新的时候我们也会说到。
至此我们也有了一个大概的流程。
和之前的字符串的渲染对比得知,我们这次渲染的是 React.createElement
的返回值,它的返回值是一个实例,我们现在称为 Element
实例。
动手实现经过上述的分析,我们得到此次渲染原生 DOM 我们需要做的事情后大致如下 我们这次渲染的代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const ele = React .createElement ( "div" , { onClick : () => { console .log (1 ); }, style : { backgroundColor : "red" , }, }, "\u4F60\u597D" , React .createElement ( "span" , { className : "test" , id : "thisisaspan" , }, "hehe" ) );React .render (ele, document .getElementById ("root" ));
createElement我们在 react 文件夹新建 element.js,这个函数的作用就是根据将所有传入的参数实例化,返回一个对象 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Element { constructor (type, props ) { this .type = type; this .props = props; } }function createElement (type, props = {}, ...children ) { props.children = children; return new Element (type, props); }export { Element , createElement };
再在 react/index.js 引入 createElement,方便调用。
createUnitcreateElement 函数调用完成之后,我们就要进入 createUnit 工厂函数来进行判断生成实例
这个地方我们要注意,这个时候的 element 是 Element 的实例,因此我们可以用 instanceof,而且渲染的都是原生的 DOM,因此 tag 名称都是字符串,我们可以使用 element.type 辅助判断。
1 2 3 4 5 6 7 8 9 10 function createUnit (element ) { if (["number" , "string" ].includes (typeof element)) { return new TextUint (element); } else if ( + element instanceof Element && + ["string" ].includes (typeof element.type ) + ) { + return new NativeUint (element); + } }
NativeUint经过 createUnit 工厂函数之后我们就要生成 NativeUint 了,我们写一个 NativeUint 的 class。我们仍然是继承了 Unit,将元素存到了 this._currentElement
1 class NativeUint extends Unit {}
调用实例 getMarkUp在这一步我们需要调用 getMarkUp。我们先想想我们这个步骤的大概流程。 我们之前说过,我们的 getMarkup 函数需要传入一个key ,然后返回一个字符串 。对这就是最直观的理解,而返回的字符串可能就是下面的样子。 我们最终需要的字符串大概为:<span data-reactid="0.1" class="test" id="thisisaspan"><span data-reactid="0.1.0">hehe</span></span>
,包含了一个闭合的 tag,上面还有一些属性,我们就需要遍历添加属性,还要添加对应的唯一 id,还要对 children 子元素做处理。
我们理一下:
原生 DOM,要根据渲染对应的 tag style 等 css 处理 class 等属性梳理 事件绑定 为什么要事件委托,因为开始的时候元素还只是个字符串,无法绑定事件 children 的处理 其他属性 我们添加 getMarkup 函数,现在我们讲解这部分代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 getMarkUp (reactId) { this ._reactId = reactId const { type, props } = this ._currentElement let tagStart = `<${type} data-reactid="${reactId} " ` let childString = '' let tagEnd = `</${type} >` for (let propName in props) { if (/^on[A-Z]/ .test (propName)) { let eventName = propName.slice (2 ).toLowerCase () $(document ).on (`${eventName} .${reactId} ` , `[data-reactid="${reactId} "]` , props[propName]) } else if (propName === 'style' ) { const styleObj = props[propName] let styles = Object .entries (styleObj).map (([attr, value] ) => { attr = attr.replace (/[A-Z]/g , (m ) => `-${m.toLocaleLowerCase()} ` ) return `${attr} :${value} ` }).join (';' ) tagStart += ` style="${styles} "` } else if (propName === 'children' ) { let children = props[propName] childString = children.map ((child, index ) => { let unit = createUnit (child) let str = unit.getMarkUp (`${reactId} .${index} ` ) return str }).join ('' ) } else if (propName === 'className' ) { tagStart += ` class="${props[propName]} "` } else { tagStart += ` ${propName} =${props[propName]} ` } } return tagStart + '>' + childString + tagEnd }
最后渲染1 2 3 4 let unit = createUnit (element);let markUp = unit.getMarkUp (React .rootIndex ); $(container).html (markUp);
结语 顺着以上的思路,我们成功渲染了原生 DOM,事件、样式、类名等等都一一成功渲染出来。而原生 DOM 的渲染看起来还是有点点复杂,就是深度优先,直接递归,直到没有元素方才完成使命。
本节代码地址:**代码地址 **
今天,我们成功完成了原生 DOM 的渲染,接下来我们将处理 Class Component,敬请期待吧!