Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yangrenmu opened this issue May 26, 2019 · 0 comments
Open

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

yangrenmu opened this issue May 26, 2019 · 0 comments
Labels

Comments

@yangrenmu
Copy link
Owner

yangrenmu commented May 26, 2019

前言

本文主要参考了从零开始实现一个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的值,然后重新渲染组件。
附上本文代码

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant