今天刷《JS每日一题》的时候,我看了react jsx渲染成真实DOM的面试题,我看了一遍,我感觉我自己可以很清晰的跟着作者的脚步走。虽然我感觉很清晰逻辑步骤,但是作为菜鸟,多敲敲代码总是好的,也能加深对该知识点的印象。
经过前面几次的学习和思考,我知道了 react中的jsx语法会通过babel转化为 js代码,以React.createElement函数形式存在,createElement函数返回一个ReactElement函数,ReactElement函数返回一个的虚拟节点,虚拟节点中嵌套虚拟节点,就形成了虚拟DOM,最后通过ReactDOM.render方法转化为真实DOM。
简单的代码示例:
<div>
<img src="avatar.png" className="profile" />
<Hello />
</div>
//转化后
React.createElement(
"div",
null,
React.createElement("img", {
src: "avatar.png",
className: "profile"
}),
React.createElement(Hello, null) //组件的名称
);
这里有个疑点?babel转化的时候,是怎么区分是原生标签名?还是组件名呢?
我们写代码的过程中,我们都知道react的组件名称,首字母必须大写,不大写就会相当于原生标签, 也许就是为这里进行解释的吧。
解答:
babel在转化jsx过程中,会判断首字母的大小写
首字母为小写
的时候,会被认为是原生DOM标签
, 那么createElement中的第一个参数就是一个字符串,表示标签的名称首字母为大写
的时候,会被认为是组件
,那么createElement中的第一个参数就是组件的名称
,如上面的HellocreateElement会根据传入的type进行判断:
虚拟DOM会通过ReactDOM.render()进行渲染成真实DOM
ReactDOM.render(element, container[, callback])
首次渲染的时候,容器中的所有DOM节点都会被替换掉,以后的跟新就会使用react中的diff算来,来进行比较。
如果传递了callback回调函数,就会在渲染完成的时候,再进行回调。
那么render函数实现呢?
跟着《JS每日一题》手动敲了一遍,还是挺有收货的,对React.createElement函数有着更近一步的理解咯。
render()
函数的实现function render(vnode, container) {
//createNode函数根据虚拟DOM,返回一个真实DOM
const node = createNode(vnode, container)
//通过appendChild原生方法添加再挂载的容器中
container.appendChild(node)
}
createNode函数
的实现(思路)function createNode(vnode, parentNode) {
let node = null //创建一个变量,来保存真实节点
const { type, props, children } = vnode
if(type === TEXT) { //如果是一个空节点
node = document.createTextNode('')
} else if(typeof type === 'string') { //字符串,说明是一个原生标签
node = document.createElement(type)
} else if(typeof type === 'function') { //组件
//isReactComponent是类组件原型上的一个对象,专门用来区分是函数组件,还是类组件
node = type.isReactComponent ? renderClass(vnode, parentNode) : renderFunction(vnode, parentNode)
}
//用来处理子节点(虚拟DOM) 挂载到当前的node(真实DOM)
renderChild(children, node)
//处理props,绑定在节点上面
handleProps(node, props)
return node
}
在上面的带有有isReactComponent
这个属性,该属性是类组件的原型上,是react内部用来区分该组件到底是类组件还是函数组件。
renderChild函数
的实现(思路)处理子节点虚拟DOM转化为真实DOM(递归
)
//循环遍历 子节点, 然后依次调用render函数
function renderChild(childArr, parentNode) {
for(let i = 0; i < childArr.length; i++) {
let child = childArr[i]
if(Array.isArray(child)) {
for(let k = 0; k < child.length; k++) {
render(child[k], parentNode)
}
} else {
render(child, parentNode)
}
}
}
handleProps函数
的实现(思路)//在createElement函数中,已经收集了(数组的形式)
function handleProps(node, props) {
Object.keys(props).forEach(item => {
//处理事件(onclick)等
if(item.slice(0,2) === 'on') {
let eventName = item.slice(2).toLocaleLowerCase()
node.addEventListener(eventName, props[item])
} else {
node[item] = props[item]
}
})
}
function renderFunction(vnode, parentNode) {
const { type, props } = vnode
//type是个函数名,执行函数,传入props
const node = type(props)
//调用真实节点函数,生成一个真实DOM,并返回
return createNode(node, parentNode)
}
function renderClass(vnode, parentNode) {
const { type, props } = vnode
//type是一个类(构造函数), 通过new创建一个实例
const node = new type(props)
//创建实例后,调用render方法
const renderNode = node.render()
//调用真实节点函数,生成一个真实DOM,并返回
return createNode(renderNode, parentNode)
}