Appearance
jsx为原生dom的渲染
本节目标:理解“jsx为原生dom的渲染”的核心思路,并能把它用于实际开发或面试表达。
说明:原 OneNote 中有图片引用,但图片未包含在导出目录中;本页保留了可用的文字与代码内容。
importReactfrom
"./react";
importReactDOM from
"./react-dom";
//babel会调用自动调用React.createElement去转换 let element1 = (
<divclassName="title"style={{color: "red"
}}><span>helloworld</span></div>
);
debugger
//了解元素element主要属性
console.log(element1);
ReactDOM.render('',document.getElementById("root"));先调用了reactDOM.render方法; 在这个方法中,我们传入了createElement返回的普通对象,也就是vdom;它的结构如下:
可见,对于children,如果只是单个,将是一个对象,如果是多个,将会是一个数组; 它本质上来说,就是用来描述jsx的一个对象,说到底,它描述的是一个会生成dom结构的函数,它描述了函数的名称type,它的参数,还是它返回的jsx结构的描述; let element1 = (
<divclassName="title"style={{color: "red"
}}><span>helloworld</span><span>hellojavascript</span></div>
);此外,如果children是基本数据类型,其中undefined,null,布尔值,不会渲染处理,而字符串和数字才会处理; let element1 = (
<divclassName="title"style={{color: "red"
}}> `{null}`
`{false}`
`{undefined}`
`{true}`
<span>hellojavascript</span></div>
);function
createElement(type,config,children){let
ref;//可以通过 ref引用此元素 let
key;//可以唯一标识一个子元素 if
(config){delete
config.__source; `delete`
config.__self; `ref` `=`
config.ref; `key` `=`
config.key; `delete`
config.ref; `delete`
config.key; `}`
`let` `props` `=` `{`
...config};if
(arguments.length\>
3){
props.children=
Array.prototype.slice.call(arguments,2).map(wrapToVdom); `}` `else` `{`
props.children=
wrapToVdom(children);//children可能是React元素对象,也可能是一个字符串 数字 null undefined}return {
type,ref,key,props
};
}
//转换文本或数字为特殊的
vdom
exportfunction
wrapToVdom(element){return typeof element === "string" || typeof element === "number"? { type:
REACT_TEXT,props: { content: element } }:
element;
}这里没有处理null,undefined,布尔值的情况;
然后我们在render中调用了mount方法: function
render(vdom,container){
mount(vdom,container);
}在mount方法中,我们主要是:
- 使用
vdom创建一个真实dom; - 然后操作
dom,把这个真实dom,挂载到容器中:
export function
mount(vdom,container){let newDOM =
createDOM(vdom);container.appendChild(newDOM);
}我们创建真实dom要调用createDOM方法,这个方法比较重要:
- 它会判断
vdom的type,根据类型的不同,调用不出的函数来创建出真实dom; - 其中
type有几个重要类型:- 一是字符串,这是原生
dom; - 一是普通对象,这个对象上我们会加上
$$typeof来分别出这些对象的区别,判断是使用type&&type.$$typeof; - 另外是函数类型,这是还是区分是类组件还是函数组件;
- 一是字符串,这是原生
先创建要节点的dom,然后处理children,如果children是单个对象,就直接mount这个对象到父元素即可,也就是以这个对象创建新dom,然后挂载到父元素即可; 如果children是数组,也简单,循环这个数组,然后把每一个元素挂载到父元素上; 思考,能不能先创建所有好子dom,然后一起挂载; export function
createDOM(vdom){if
(!vdom)return
null;let {
type,props } =
vdom;let
dom;//真实DOMif
(type===
REACT_TEXT){//如果这个元素是一个文本的话 dom =
document.createTextNode(props.content);} else {dom =
document.createElement(type);//div span p}//处理属性 if
(props){
updateProps(dom,{},
props); `//` 处理`children`
`if`
(props.children){let children =
props.children; `if`
(typeofchildren === 'object' &&
children.type){//说明这是一个React元素
mount(children,dom); `}` `else` `if`
(Array.isArray(children)){
reconcileChildren(props.children,dom); `}`
`}`
}//让虚拟DOM的dom属性指向这个虚拟DOM对应的真实DOM
vdom.dom=
dom;return
dom;
}function
updateProps(dom,oldProps,newProps){for
(letkey in
newProps){if
(key===
"children"){
continue; `}`
`//` 处理`style`
`if`
(key===
"style"){let style =
newProps[key]; `for`
(letattr in
style){
dom.style[attr]=
style[attr]; `}`
`}` `else` `{`
dom[key]=
newProps[key]; `}`
}
}function
reconcileChildren(childrenVdom,parentDOM){for
(leti =
0;i \<
childrenVdom.length;i++){let childVdom =
childrenVdom[i];mount(childVdom,parentDOM);}
}