• 实现一个react系列二:渲染组件


    前言

    本文主要参考了从零开始实现一个React从 0 到 1 实现React
    在上一节JSX和虚拟DOM中,我们了解了react中的JSX到虚拟dom,以及如何将虚拟dom渲染成真实的dom。在这一节中,我们将会了解react中组件是如何渲染的。

    组件

    react中,组件有两种使用方法:

    import React from 'react'
    // 类定义的组件
    class Hello extends React.Component {
      render() {
        return <div>hello</div>
      }
    }
    // 无状态组件,通过函数来定义
    const World = () => {
      return <h1>world!</h1>
    }
    ReactDom.render(<Hello />, document.getElementById('#root'))
    

    通过类定义组件时,是需要继承React.component的,我们第一步就从React.Component的实现开始。
    类实现的组件有自己私有的state,同时可以通过this.props来获取传进来的参数。

    function Component(props) {
      this.props = props
      this.state = {}
    }
    
    • setState

    我们知道在react中,我们可以通过setState来改变组件state的值,而且当state改变后,组件对应的也会重新渲染。

    • 改变state的值:我们可以使用Object.assign来实现。
    • 重新渲染组件:我们可以在改变state值后,调用render函数,重新渲染。异步的setState在后面的章节会实现。
    Component.prototype.setState = function (updateState) {
      // 更新 state
      this.state = Object.assign({}, this.state, updateState)
      // 重新渲染组件
      render(this)
    }
    

    渲染

    在上一节中,我们知道ReactDom.render(),会将其第一个参数转成React.createElement()形式,而组件也会被转为React.createElement(Hello, null)这种形式。


    所以react中组件在渲染时会被当成函数渲染的。所以我们在render函数中需要判断虚拟dom的标签属性(此处用tag表示的)是函数还是原生dom。如果是函数的话,我们只需要拿到组件的jsx转换后对应的虚拟dom, 然后在进行渲染。

    const render = (vdom, root) => {
      if (typeof vdom.tag === 'function') {
        let component
        if (vdom.tag.prototype.render) {
          // 类定义的组件, vdom.attrs 是传入的 props
          component = new vdom.tag(vdom.attrs)
        } else {
          // 函数定义组件
          component = vdom.tag(vdom.attrs)
        }
        return _render(component, root)
      }
      _render(vdom, root)
    }
    

    对应的_render():

    const _render = (vdom, root) => {
      // 类组件的话,需要从 render 函数中拿到 jsx 转换后的虚拟 dom
      const vdomNode = vdom.render ? vdom.render() : vdom
      if (typeof vdomNode === "string" || typeof vdomNode === "number") {
        root.innerText += vdomNode
        return
      }
      const dom = document.createElement(vdomNode.tag)
      if (vdomNode.attrs) {
        for (let attr in vdomNode.attrs) {
          const value = vdomNode.attrs[attr]
          setAttribute(dom, attr, value)
        }
      }
      // 遍历子节点, 渲染子节点
      vdomNode.childs && vdomNode.childs.forEach(child => render(child, dom))
      // 将父节点 root 挂到 vdom 上,当再次渲染组件时,跟据 vdom.root 直接渲染 dom
      if (vdom.root) {
        vdom.root.innerText = ''
        vdom.root.appendChild(dom)
        return
      }
      vdom.root = root
      // 将子元素挂载到其真实 DOM 的父元素上
      root.appendChild(dom)
    }
    

    试一试,刚出锅的代码效果如何。

    import React from "./react"
    import ReactDom from "./reactDom"
    
    const World = props => {
      return (
        <h1>
          world!<p>{props.world}</p>
        </h1>
      )
    }
    
    class Hello extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
          count: 0
        }
      }
      addCount() {
        const { count } = this.state
        this.setState({
          count: count + 1
        })
      }
      render() {
        return (
          <div ha="lou">
            hello
            <World world="function props" />
            <span>{this.props.initProps}</span>
            <div>{this.state.count}</div>
            <button onClick={this.addCount.bind(this)}> + </button>
          </div>
        )
      }
    }
    
    ReactDom.render(
      <Hello initProps="this is props" />,
      document.getElementById("root")
    )
    
    

    小结

    react在渲染组件时,组件会被babel转为React.createElement(fn, null)这种形式,第一参数是函数,所以我们需要从fn中获取由组件的jsx转换后的虚拟dom,然后在将虚拟dom渲染成真实dom
    setState:在调用setState时,先用Object.assign更新state的值,然后重新渲染组件。
    附上本文代码

  • 相关阅读:
    vi/vim键盘图
    PostgreSQL学习----命令或问题小结
    PostgreSQL学习----模式schema
    OSM
    Spring基础(9) : 自动扫描
    Spring基础(8) : properties配置文件
    Spring基础(8) : 延迟加载,Bean的作用域,Bean生命周期
    Spring基础(7) : Bean的名字
    Spring基础(6) : 普通Bean对象中保存ApplicationContext
    Spring基础(5): 构造函数注入无法处理循环依赖
  • 原文地址:https://www.cnblogs.com/yangrenmu/p/10926890.html
Copyright © 2020-2023  润新知