您现在的位置是:首页 >技术教程 >React源码解析之createElement和render方法网站首页技术教程

React源码解析之createElement和render方法

ATWLee 2023-05-15 12:00:03
简介React源码解析之createElement和render方法

参考资料

请注意,这是React16.8的源码解析,当然他完全可以作为你阅读源码的参考,他还没有落后。

Step1

开始之前,要先了解一个知识点⬇️
我们都知道,要在JSX中写React语法,那为什么不能在js文件中写呢?也可以,但是你要使用相关的Babel转一下react语法,转成JS认识的语法。

换句话说,必须得有Babel将JSX转成JS,你的代码才能正常运行。

Step2

看一下这段代码

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

JS肯定无法识别这句话,但是用到Step1中的Babel转一下,JS就能识别了,那么转成了什么样子呢?让我们打印一下element⬇️
在这里插入图片描述
没错,变成了一个对象,记住这个type和props

你可以把type理解成标签名,props理解成这个node的所有属性

要注意:这个对象并不是Babel生成的,是React.createElement的方法生成的。
Babel只是告诉node要把这个react语法通过React.createElement方法转成js能识别的对象(这是我猜的)

Step3

让我们来重写一下精简版的React的createElement方法

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

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

这里的文本(TEXT_ELEMENT)对象好像和现在的略有不同(我没有看到现在React的TEXT_ELEMENT),不过它也不影响源码阅读。

让我们看几个dom节点生成了怎样的对象吧⬇️

const element = <h1 title="foo">Hello</h1>
⬇️⬇️⬇️
const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)
⬇️⬇️⬇️
const element = {
  type: "h1",
  props: {
    title: "foo",
    children: "Hello",
  },
}
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")
)
⬇️⬇️⬇️
const element = {
  type: "div",
  props: {
    title: "id",
    children: [
		{
			type:"a",
			props:{
				children:"bar"
			}
		},
		{
			type:"b",
			props:{}
		},
	],
  },
}

Step4

接下来就是render了,将渲染完的对象element,渲染成真正的dom节点⬇️。
让我们重写一下精简版的ReactDom的render的方法,在18的版本中移除了它,也可以继续参考。

function render(element, container) {
  const { type, props } = element;
  // 根据type创建节点
  const dom =
    type !== "TEXT_ELEMENT"
      ? document.createElement(type)
      : document.createTextNode("");

  // 将属性添加到节点
  Object.keys(props)
    .filter((key) => key !== "children")
    .forEach((name) => (dom[name] = props[name]));

  // 递归增加子节点
  props.children.forEach((chil) => render(chil, dom));
  // 添加节点
  container.appendChild(dom);
}

Step5

接下来,你只需要像初始化react项目时,把你的App挂载到root上就可以了。

const element = (
  <div style="background: salmon">
    <h1>Hello World</h1>
    <h2 style="text-align:right">from Didact</h2>
  </div>
);

const container = document.getElementById("root");
Lee.render(element, container);

你可以看一下这个sandbox来验证你的学习成果。
这是我的完整练习代码⬇️

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

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

function render(element, container) {
  const { type, props } = element;
  const dom =
    type !== "TEXT_ELEMENT"
      ? document.createElement(type)
      : document.createTextNode("");

  Object.keys(props)
    .filter((key) => key !== "children")
    .forEach((name) => (dom[name] = props[name]));

  props.children.forEach((chil) => render(chil, dom));
  container.appendChild(dom);
}

const Lee = {
  render,
  createElement
};

/** @jsx Lee.createElement */
const element = (
  <div style="background: salmon">
    <h1>Hello World</h1>
    <h2 style="text-align:right">from Didact</h2>
  </div>
);

const container = document.getElementById("root");
Lee.render(element, container);

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。