【7天造一个react】第二天,支持渲染复杂虚拟DOM树

【7天造一个react】第二天,支持渲染复杂虚拟DOM树,第1张

快速开始

本篇文章将打你打造一个稍微复杂一点的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,
} 

大功告成~

写在最后 如果你觉得这篇本文还不错,欢迎🔥点赞 + 关注 + 转发🔥~~~如果本项目帮助到了你,请给仓库一颗⭐️

你的支持是我持续创造的动力!!!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/941000.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-17
下一篇 2022-05-17

发表评论

登录后才能评论

评论列表(0条)

保存