资讯专栏INFORMATION COLUMN

构建自己的React:(1)Rendering DOM elements

Turbo / 1030人阅读

摘要:我们用原生的对象来描述我们要渲染的东西,并称这类对象为。下一步是将元素及其子元素渲染成。所以,将会使用来表示文本节点,并使用来装文本值。创建添加事件监听设置属性递归渲染子元素将添加到父内我们目前创建了一个可以渲染元素及其子元素为的方法。

翻译自这里:https://engineering.hexacta.c...
本系列的目的是创建类似于React的一个简易的工具库。

DOM review

开始之前,我们先看下我们要用到的DOM API:

// 通过id查找元素
const domRoot = document.getElementById("root");
// 根据执行标签名创建元素
const domInput = document.createElement("input");
// 设置元素属性
domInput["type"] = "text";
domInput["value"] = "Hi world";
domInput["className"] = "my-class";
// 添加事件监听
domInput.addEventListener("change", e => alert(e.target.value));
// 创建文本节点
const domText = document.createTextNode("");
// 设置节点内容
domText["nodeValue"] = "Foo";
// 往页面上添加元素
domRoot.appendChild(domInput);
// 往页面上添加文本节点
domRoot.appendChild(domText);

注意到这里我们给元素设置了properties而不是attributes,而且只有有效的properties才能被设置。

Didact Elements

我们用原生的JS对象来描述我们要渲染的东西,并称这类对象为Didact Elements。这些元素对应的JS对象都有两个必要的属性:typepropstype可以是个字符串也可以是一个函数,但在我们介绍组件之前我们先只使用字符串。props是一个可以为空(null)的对象。props下还可以含有children属性,children属性值为一个装有Didact Elements的数组。

我们后面将会频繁的使用Didact Elements,所以我们会用元素称呼Didact Elements。不要和HTML的元素搞混了,HTML元素会被称作DOM元素或者使用命名变量时干脆就叫dom

举个例子,我们会用下面这个对象:

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

来描述下面这个dom:

Didact Elements和React Elements很像。但通常情况下你不会使用JS手动去创建一个React Elements,更多的是使用JSX或者是createElement方法来创建。在Didact中我们也会使用相同的方法创建元素,但会将这部分内容放在下一节。

Render DOM Elements

下一步是将元素及其子元素渲染成dom。我们使用render(类似于ReactDOM.render)方法来接收一个元素和一个dom容器。这个方法会将这个元素描述的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);
}

我们现在仍没有处理属性和事件。我们先用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);
}
Render DOM Text Nodes

目前render还不支持文本节点。首先我们要定义文本节点是什么样的。在react中,一个Foo这样的元素需要这样描述:

const reactElement = {
  type: "span",
  props: {
    children: ["Foo"]
  }
}

注意到这里的子元素已经不是对象,而只是一个字符串。这和我们对Didact Elements的定义有不一样:children应该是装有Didact Elements的数组,并且所有元素都有typeprops属性。如果我们继续遵守这个规则接下来我们将较少使用if的次数。所以,Didact Elements将会使用type="TEXT_ELEMENT"来表示文本节点,并使用nodeValue来装文本值。例如下面这样:

const textElement = {
  type: "span",
  props: {
    children: [
      {
        type: "TEXT_ELEMENT",
        props: { nodeValue: "Foo" }
      }
    ]
  }
};

现在我们已经定义好了能够渲染的文本节点。和其他节点不同的是,文本节点需要使用createTextNode来创建而不是createElement,而nodeValue会通过相同方法来设置。

function render(element, parentDom) {
  const { type, props } = element;

  // 创建DOM
  const isTextElement = type === "TEXT_ELEMENT";
  const dom = isTextElement
    ? document.createTextNode("")
    : 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));

  // 将dom添加到父dom内
  parentDom.appendChild(dom);
}
Summary

我们目前创建了一个可以渲染元素及其子元素为DOM的render方法。下一步我们需要一个快速简单的方法来创建元素。下一节我们将在Didact中使用JSX。

下一节:

https://engineering.hexacta.c...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99669.html

相关文章

  • React之渲染元素

    摘要:渲染元素开场白接着上一节的讲解后我们大概清楚了以下几个事儿知道是个什么东东为什么要推荐使用以及的一些基本语法。本篇文章谈一下是怎么渲染元素的。更新已渲染的元素元素是不可变的,一旦你创建了一个元素,就不能再修改其子元素或任何属性。 React-渲染元素 开场白 接着上一节JSX的讲解后:我们大概清楚了以下几个事儿: 知道JSX是个什么东东 为什么React要推荐使用JSX 以及JSX...

    Jenny_Tong 评论0 收藏0
  • React diff算法

    摘要:传统算法通过循环递归对节点进行依次对比,效率低下,算法复杂度达到,其中是树中节点的总数。对于同一层级的一组子节点,它们可以通过唯一进行区分。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。 https://zhuanlan.zhihu.com/p/... React diff 会帮助我们计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行实...

    caozhijian 评论0 收藏0
  • React官网RECENT POSTS阅读

    摘要:事件行为在浏览器中保持一次,并且符合标准。主要是进行修复。事件已经在移动上支持。阻止已经在上存在的事件错误处理。然后对应的将会被打包送往客户端。在中弃用,现在正式删除。是运行于一个严格的安全策略下成为可能。增加警告提示非生产环境。 ⭐️写在开头 阅读React官网的 RECENT POSTS的个人翻译/摘要(部分)。 英文片段为官网原文片段。 原文地址 ⭐️为什么要使用React ...

    Sike 评论0 收藏0
  • 【译】渲染Elements

    摘要:注不做翻译是中最小的构建部件。在里渲染让我们看一下在下面有在你文件中无处不在的标签我们会把这元素成为元素因为的所有东西都会放在这个元素里面。通过方法,我们能吧渲染到我们根节点上。更新被渲染的是不可变的。 下面是react官方文档的个人翻译,如有翻译错误,请多多指出原文地址:https://facebook.github.io/re...特别感谢Hevaen,同时也向豪大React群所有...

    LoftySoul 评论0 收藏0
  • 【译】介绍JSX

    摘要:介绍我们来看一下下面的变量声明这是有意思的标记语法既不是字符串又不是。也是一个表达式编译后表达式成为常规的对象。防止注入攻击中直接嵌套用户在表单表单中输入的值是安全的。这有助于防止攻击跨站脚本。读取这些对象并使用它们构造并保持更新。 下面是react官方文档的个人翻译,如有翻译错误,请多多指出原文地址:https://facebook.github.io/re...特别感谢Hevaen...

    ymyang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<