打造你自己的 React

106 阅读2分钟

DOM review

在我们开始之前,我们先看看我们用到的DOM的API。

// Get and element by id
const domRoot = document.getElementById("root")
// Create a new element given a tag name
const domInput = document.createElement("input")
// set properties
domInput["type"] = "text";
domInput["value"] = "Hi world";
domInput["className"] = "my-class";
// listen to events
domInput.addEventListener("change", e => alert(e.target.value));
//create a text node
const domText = document.createTextNode("");
// set text node content
domText["nodeValue"] = "Foo";
// Append an element
domRoot.appendChild(domInp);
// Append a text node( same as previous)
domRoot.appendChild(domText);

注意我们使用 properties 而不是 attributes 来设置元素,因此只能允许有效的属性。

Didact Elements

我们使用纯 JS 对象来描述我们需要渲染什么。我们把它们叫做 Didact Elements. 这些元素包含2个属性: type 和 props. type可以是一个string 或者 一个函数,但我们目前只是会使用string。props 是一个可以为空但不为null的对象。 props 可能有children 属性,该属性是一个Didact元素的数组组成。

例如:

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>

Didact 元素和React的元素很像。但你通常不会使用JS 对象来创建 React元素,一般你会使用 JSX 或者createElemnt.

Render DOM Elemnts

这一步是渲染一个元素以及它的子元素到 dom上。我们使用 render方法(与ReactDOM.render相同)接收一个element和一个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);
}

我们仍然少了properties以及event listeners. 我们来使用Object.keys来遍历props的属性名。

function render(element, parentDOM) {
    ...
    const isListener = name => name.startWith("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];
    });
    ...
}

Render DOM Text Node

render 方法还没有支持的是 text nodes, 首先我们需要定义 text 元如何显示。比如一个描述 Foo的元素应该类似下面这样:

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

但我们看child属性,我里面只有一个string, 如果我们参考 Didact 元素,所有元素都有 type和 props,我们将会有更少的 if, 所以 Didact 的Text元素将有一个type 为"TEXT ELEMENT" 并且世纪的值房再 nodeValue的属性

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

现在我们已经定义了如何渲染它们,不同之处在于其他元素使用createElement渲染,但text元素,使用createTextNode来创建。

    const isTextElement = type === "TEXT ELEMENT";
    const dom = isTextElement ? document.createTextNode(""): document.createElement(type);

如何update DOM

原文参考 engineering.hexacta.com/didact-inst…