React的最小实现(一)
前言
本系列文章将对 React 完成最小的实现。 第一章主要实现 createElement 函数和 render 函数,createElement 函数的作用是实现 jsx 向 js 的转换。 render 函数则是进行实际的渲染工作。
createElement 函数
编写前我们需要知道 React 中 createElement 的函数细节。
从 React 文档中我们可以知道 createElement 可以来创建一个 React 元素,它有 type、props 和 children 三个参数。
- type:type 参数必须是一个有效的 React 组件类型,例如一个字符串标签名(如 'div' 或 'span'),或一个 React 组件(一个函数式组件、一个类式组件,或者是一个特殊的组件如 Fragment)。
- props:props 参数必须是一个对象或 null。如果你传入 null,它会被当作一个空对象。创建的 React 元素的 props 与这个参数相同。注意,props 对象中的 ref 和 key 比较特殊,它们 不会 作为 element.props.ref 和 element.props.key 出现在创建的元素 element 上,而是作为 element.ref 和 element.key 出现。
- 可选 ...children:零个或多个子节点。它们可以是任何 React 节点,包括 React 元素、字符串、数字、portal、空节点(null、undefined、true 和 false),以及 React 节点数组。
import { createElement } from "react"
function Greeting({ name }) {
return createElement("h1", { className: "greeting" }, "你好")
}
那么什么是 React 元素呢?
我们同样引用文档的概念
元素是用来描述一部分用户界面的轻量级结构。比如,
<Greeting name="泰勒" />
和 createElement(Greeting, { name: '泰勒' }) 都会生成一个这样的对象:
// 极度简化的样子
{
type: Greeting,
props: {
name: '泰勒'
},
key: null,
ref: null,
}
到这里我们对于我们的要编写 createElement 函数以及有了一定的目标,同时我们进行再次的简化(我更喜欢简单的代码)。
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children
}
}
}
因为 children 有可能不是对象,即 children 本身为文本节点内容,因此我们添加 createTextElement 函数做特殊处理。
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => (typeof child === "object" ? child : createTextElement(child.toString())))
}
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
}
}
render 函数
目前我们只考虑将想 DOM 添加内容,我们使用元素类型来创造节点,然后将新节点添加到容器中。 同时考虑到 children,我们使用递归去实现子节点的挂载。 同时考虑文本元素的特殊情况。
function render(element, container) {
const dom = element.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type)
Object.keys(element.props).forEach((name) => {
// 对样式我们进行特殊处理
if (name === "style" && typeof element.props[name] === "object") {
Object.assign(dom.style, element.props[name])
} else if (name !== "children") {
dom[name] = element.props[name]
}
})
element.props.children?.forEach((child) => render(child, dom))
container.appendChild(dom)
}