DIY一个轻量级的react(一)渲染DOM元素

渲染DOM元素

要自己创建一个轻量级的react,我们首先要来学习的是react是如何渲染dom元素的。

元素对象定义

在开始开发之前,我们先回顾一下react对于dom元素的定义。

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

根据这个对于元素的定义,我们将生成下面这样的HTML代码

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

当然我们不会在js中直接使用这样的数据结构去生成HTML代码,通常我们会使用JSX语法,或是createElement方法,但最终还是转化成这样的数据结构来生成HTML代码的,至于怎么转换的,我们将会在之后的教程中一一展示。

渲染DOM元素方法

接下来我们就来创建一个render方法,接收要渲染的元素对象和他的容器,来创建他的dom树并插入到容器中去。

function render(element, parentDom) {
  const { type, props } = element;
  const dom = document.createElement(type);
  const childElements = props.children || [];
  childElements.forEach(childElement => render(childElement, dom));
  parentDom.appendChild(dom);
}
复制代码

通过这样一个简单的递归就可以生成对应的dom树了,但是我们发现我们没有把属性和事件添加到对应的元素中去。通过Object.keys的这个方法把属性和事件遍历到元素中去,注意这里需要props中判断哪些是事件哪些是属性。

function render(element, parentDom) {
  const { type, props } = element;
  const dom = document.createElement(type);

  const isListener = name => name.startsWith("on");
  Object.keys(props).filter(isListener).forEach(name => {
    const eventType = name.toLowerCase().substring(2);
    dom.addEventListener(eventType, props[name]);
  });

  const isAttribute = name => !isListener(name) && name != "children";
  Object.keys(props).filter(isAttribute).forEach(name => {
    dom[name] = props[name];
  });

  const childElements = props.children || [];
  childElements.forEach(childElement => render(childElement, dom));

  parentDom.appendChild(dom);
}
复制代码

渲染text节点

然后我们发现我们的render方法并不支持text元素,我们先来看一下text元素是怎么定义的。

const textElement = {
  type: "span",
  props: {
    children: ["Foo"]
  }
};
复制代码

对应生成的是

<span>Foo</span>
复制代码

但是我们发现这样就我们之前定义的元素就冲突了,我们需要有type和props的元素。于是我们这边用一个特殊的type名字“TEXT ELEMENT”来表示text元素。text内容的属性就为nodeValue。我们来看一下具体例子。

const textElement = {
  type: "span",
  props: {
    children: [
      {
        type: "TEXT ELEMENT",
        props: { nodeValue: "Foo" }
      }
    ]
  }
};
复制代码

接下来我们需要添加判断是否为text节点的代码,是的话使用createTextNode来生成text节点,我们来看一下修改后的代码。

// 渲染dom对象的方法
function render (element, parentDom) {
    const { type, props } = element;

    // 创建DOM元素
    const isTextElement = type === "TEXT ELEMENT";
    const dom = isTextElement
        ? document.createTextNode("")
        : document.createElement(type);

    // 找到props中的事件,并监听对应的事件
    const isListener = name => name.startsWith("on");
    Object.keys(props).filter(isListener).forEach(name => {
        const eventType = name.toLowerCase().substring(2);
        dom.addEventListener(eventType, props[name]);
    });

    // 找到props中的attribute,添加到dom中
    const isAttribute = name => !isListener(name) && name != "children";
    Object.keys(props).filter(isAttribute).forEach(name => {
        dom[name] = props[name];
    });

    // 渲染子元素
    const childElements = props.children || [];
    childElements.forEach(childElement => render(childElement, dom));

    // 添加到父元素中
    parentDom.appendChild(dom);
}
复制代码

总结

这里我们创建了一个render方法使用dom数据结构来生成dom元素,接下来我们要做的是怎么生成虚拟dom的数据结构(如何将jsx转换成dom数据)

推荐

项目的github地址:github.com/abchexuzhen… 关注加星获取最新的更新哦,也可以关注一下前端的公众号哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值