React原理浅析(一):JS对象与DOM的相互转换

424 阅读2分钟

原文链接:github.com/chinanf-boy…

前言

本系列React原理浅析文章,通过实现一个与React有相同API的Didact,以及如何逐步构建它,使React内部更容易理解。 本文通过学习上述原文,再加上作者自身的理解和梳理,以求达到一个更结构化的学习效果,帮助大家更细致和透彻地理解整个React的实现逻辑。

作者认为,一个框架的构成主要是由各种特定结构的对象以及分工不同的函数组合而成,而函数就是一个输入输出和功能的定义。所以在本系列文章中,会主要关注对象的结构以及函数的出参入参

第一节,我们先从html DOM和Javascript对象相互转换开始构建:

  • html DOM => JS对象:定义这个JS对象(Element)的结构
  • JS对象 => html DOM:render函数

html DOM => JS对象:Element对象

我们把这个JS对象命名为Element(元素)。

两个必要的属性

  • type:html标签类型
  • props:元素上的属性和子元素

eg:

const element = {
  type: "div",
  props: {
    id: "container",
    children: [
      { type: "input", props: { value: "foo", type: "text" } },
      { type: "a", props: { href: "/bar" } },
      { type: "span", props: {} }
    ]
  }
};

描述这个dom:

<div id="container">
  <input value="foo" type="text">
  <a href="/bar"></a>
  <span></span>
</div>

JS对象 => html DOM:render函数

上述可以将html的DOM描述成一个Element对象,那么同样的,我们也需要一种机制将Element对象转换成真正的DOM,我们用render函数实现。

参数

  • 入参:Element元素对象,要挂载的父节点DOM
  • 出参:无

功能

  • 创建Element元素对应的DOM树挂载到父节点DOM下
  • 给元素添加属性和事件监听器
function render(element, parentDom) {
  const { type, props } = element;
  const dom = document.createElement(type);

  const isListener = name => name.startsWith("on");
  // 是否开头-on
  Object.keys(props).filter(isListener).forEach(name => {
    const eventType = name.toLowerCase().substring(2); // 取两位后
    dom.addEventListener(eventType, props[name]);
  });
  // 每一个开头-on 的属性-对应-函数 props[name] - >用-dom-addEvent 接连

  const isAttribute = name => !isListener(name) && name != "children";
  // 不是-监听事件 和 不能是-孩子 

  Object.keys(props).filter(isAttribute).forEach(name => {
    dom[name] = props[name];
  });
 // 过滤出来的属性 - 赋予 - > dom
  const childElements = props.children || [];
  // 子元素递归调用render函数
  childElements.forEach(childElement => render(childElement, dom));

  parentDom.appendChild(dom);
}

注意:render函数内部对元素的子元素递归调用了自身,记住这一点,我们后续优化会针对这一点进行分析。