本篇文章将打你打造一个稍微复杂一点的react,它支持复杂虚拟dom树的渲染,我们先来看下最终的效果吧~
// 下载项目到本地
git clone git@github.com:murphywuwu/7-days-react.git
// 切换到hello-world目录
cd examples/list
// 安装依赖
yarn install
// 启动项目
yarn start
应用代码如下:
🚀 启程 生成react元素想要实现如上的渲染效果,我们可以推断出,虚拟dom树应该是如下所示的模样
// 复杂虚拟dom树
{
type: 'ul',
props: {
children: [
{
type: 'li',
props: {
children: '1',
style: {
background: 'blue',
}
}
},
{
type: 'li',
props: {
children: '2',
style: {
background: 'pink',
}
},
},
{
type: 'li',
props: {
children: '3',
style: {
xia: 'black',
}
},
}
],
style:
color: 'white'
}
}
}
这个虚拟DOM树是如何生成的呢?
打开babel编译JSX这个网站输入如下代码
可以看到这个虚拟DOM树是通过React.createElement组合调用生成的。现在,我们通过测试代码来模拟React.createElement生成这个虚拟DOM树的结果
// packages/react/__tests__/react.test.js
test('list', function () {
// 模拟虚拟dom树的生成方式
const result = React.createElement(
'ul',
{
style: {
color: 'white',
},
},
React.createElement('li', { style: { background: 'blue' } }, '1'),
React.createElement('li', { style: { background: 'pink' } }, '2'),
React.createElement('li', { style: { background: 'black' } }, '3'),
)
// 明确我们想要的结果
expect(result).toEqual({
type: 'ul',
props: {
children: [
{
type: 'li',
props: {
children: '1',
style: {
background: 'blue',
},
},
},
{
type: 'li',
props: {
children: '2',
style: {
background: 'pink',
},
},
},
{
type: 'li',
props: {
children: '3',
style: {
background: 'black',
},
},
},
],
style: {
color: 'white',
},
},
})
})
要想到达成如上所示的结果,我们来分析拆解一下
1. 首先,在这个虚拟dom树中的children为一个数组。
2. 其次,createElement方法的第三个以及以后的参数都代表子虚拟dom节点
3. 所以:
第一,在实现createElement方法时,可以通过...params来接收所有子节点
第二,当有多个children时,children为数组
第三,当只有一个children时,children等于它自己
接下来,我们来完善React.createElement方法
// packages/react/lib/react.js
module.exports = {
// 通过...params接收所有子节点
createElement: function (type, props, ...params) {
// 当有多个children,children为数组
let children = params
// 当只有一个children时,children等于它自己
if (params.length == 1) {
children = params[0]
}
return {
type,
props: {
children,
...props,
},
}
},
}
测试通过~
渲染react元素目前,我们已经有能力构造出一颗复杂的DOM树了。现在,我们再接再厉,实现复杂DOM树的渲染。
同样的,我们先编写测试代码
// packages/react-dom/__tests__/react-dom.test.js
test('list', function () {
// 创建root节点
const root = document.createElement('root')
// 生成虚拟dom树
const element = React.createElement(
'ul',
{
style: {
color: 'white',
},
},
React.createElement('li', { style: { background: 'blue' } }, '1'),
React.createElement('li', { style: { background: 'pink' } }, '2'),
React.createElement('li', { style: { background: 'black' } }, '3'),
)
// 渲染虚拟dom树
ReactDOM.render(element, root)
// 明确我们想要的结果
expect(root.innerHTML).toBe(
'123',
)
})
接下来,我们继续拆解任务
1. 首先,这个结果里面创建了一个ul节点它的3个子节点(li节点)
2. 其次,如上所说的这几个节点都添加了style属性
3. 所以:
第一步,依然是创建节点(已实现)
第二步,应该为dom节点添加属性
第三步,为dom节点添加子节点
1. 首先,该例中的子节点是一个数组,这时候应该想到我们可以遍历数组
2. 其次,这个数组里面的节点也是一个虚拟dom,所以,同样的我们需要把这个虚拟dom转换成dom节点,这里就涉及到了递归调用
3. 最后,将虚拟dom节点转换为dom节点后插入父节点中
最后,完善ReactDOM.render方法,让它支持复杂虚拟DOM树的渲染
// packages/react-dom/lib/react-dom.js
function renderDOM(element) {
const { type, props } = element
const children = props.children
// 第一步创建dom
const node = document.createElement(type)
// 第二步为dom添加属性
Object.keys(props).forEach((key) => {
if (key == 'children') return
let val = props[key]
// 支持style属性
if (key == 'style') {
let str = ''
Object.keys(val).forEach((key) => {
str += `${key}: ${val[key]};`
})
val = str
}
node.setAttribute(key, val)
})
// 第三步为dom添加children
if (typeof children == 'string') {
const childNode = document.createTextNode(children)
node.appendChild(childNode)
}
// 支持复杂虚拟DOM树
// 如果children是数组,遍历该数组
if (Array.isArray(children)) {
children.forEach((element) => {
// 将虚拟dom节点转换为真实的dom节点
const childNode = renderDOM(element)
// 真实的dom节点插入父节点node中
node.appendChild(childNode)
})
}
return node
}
function render(element, root) {
const node = renderDOM(element)
// 最后,在root中插入dom
root.appendChild(node)
}
module.exports = {
render,
}
大功告成~
写在最后 如果你觉得这篇本文还不错,欢迎🔥点赞 + 关注 + 转发🔥~~~如果本项目帮助到了你,请给仓库一颗⭐️你的支持是我持续创造的动力!!!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)