手写react一

113 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

出发点

最近在学习react源码,如果直接去看源码,估计能劝退一大帮人,手敲一遍更能让我了解react是如何工作的。好了,废话不多说,直接开始吧。

实现

我们将分为8步,一步一步的实现一个小型的React

  1. 实现createElement函数
  2. 实现render函数
  3. Currnet Mode模式
  4. fibers
  5. render和commit阶段
  6. 协调器
  7. function组件
  8. hooks

最最最最最简单的实现

我们先看一段代码

const element = <h1 title="foo">Hello Word</h1>

const container = document.getElementById("root")

ReactDOM.render(element, container)

首先是创建React element,然后获取id为root的dom节点,最后渲染React element到root节点里。

其中第一行代码JSX转换为js如下,jsx转换为js可以通过babel转换

const element = React.createElement(
    "h1",
    { title: "foo" },
    "Hello",
)

React.createElement 方法接收type、config、childern参数,返回一个对象,该对象包含:$$typeof、type、key、ref、props、_owner属性,我们可以进行简单的模拟,只需要type和props

const element = {
    type: "h1",
    props: {
        title: "foo",
        children: "Hello",
    },
}

其中type为dom节点的类型,props中是JSX中设置的一些属性,children属性为子节点,可以为string和array。

然后是替换ReactDOM.render(element, container)

const container = document.getElementById("root")
const node = document.createElement(element.type)

node["title"] = element.props.title
const text = document.createTextNode("")

text["nodeValue"] = element.props.children
node.appendChild(text)
container.appendChild(node)

到这里,没有使用react实现了一段代码

实现createElement函数

先来看一段简单的JSX代码和转换成JS后的样子

const element = (
    <div id="foo">
        <a>bar</a>
        <b />
    </div>
)
// 转换后
const element = React.createElement(
    "div",
    { id: "foo" },
    React.createElement("a", null, "bar"),
    React.createElement("b")
)

前面我们已经知道React.createElement方法返回的是一个对象,属性type为dom节点的tagName,属性props为dom节点的属性和子节点,下面来实现一下:

function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children,
        },
    }
}

这里的children参数总是为array,用来接收子节点,我们来看些例子:

createElement('div') =>
{
    "type": "div",
    "props": { "children": [] }
}

createElement("div", null, a) =>
{
    "type": "div",
    "props": { "children": [a] }
}

createElement("div", null, a, b) =>
{
    "type": "div",
    "props": { "children": [a, b] }
}

children还可以接收想string或number类型的值,所以继续改进一下createElement方法,当child不是object类型时,就返回type为TEXT_ELEMENT的对象

function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => 
                typeof child === 'object'
                ? child
                : createTextElement(child)
            )
        },
    }
}

function createTextElement(text) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue: text,
            children: [],
        },
    }
}

接下来我们可以用自己实现的createElement替换React.createElement

const Didect = {
    createElement,
}
const element = Didect.createElement(
    'div',
    { id: 'foo' },
    Didect.createElement('a', null, 'bar'),
    Didect.createElement('b'),
)
const container = document.getElementById("root")

ReactDOM.render(element, container)

总结

我们写的JSX会被转换为React.createElement,该方法会创建一个对象,定义react的Element-TYPE类型,并返回这个对象