React之组件渲染原理

54 阅读1分钟

由于jsx的编译过程需要编写一套ast词法解析,需要拆字符串 拼字符串,拼接成函数,后面再单独写一篇

这里只从createElement + render开始手写,在此之前,需要先封装一个each循环方法

1.each

/* 
封装一个对象迭代的方法 
  + 基于传统的for/in循环,会存在一些弊端「性能较差(既可以迭代私有的,也可以迭代公有的);只能迭代“可枚举、非Symbol类型的”属性...」
  + 解决思路:获取对象所有的私有属性「私有的、不论是否可枚举、不论类型」
    + Object.getOwnPropertyNames(arr) -> 获取对象非Symbol类型的私有属性「无关是否可枚举」
    + Object.getOwnPropertySymbols(arr) -> 获取Symbol类型的私有属性
    获取所有的私有属性:
      let keys = Object.getOwnPropertyNames(arr).concat(Object.getOwnPropertySymbols(arr));
    可以基于ES6中的Reflect.ownKeys代替上述操作「弊端:不兼容IE」
      let keys = Reflect.ownKeys(arr);
*/
const each = function each(obj, callback) {
    if (obj === null || typeof obj !== "object") throw new TypeError('obj is not a object');
    if (typeof callback !== "function") throw new TypeError('callback is not a function');
    let keys = Reflect.ownKeys(obj);
    keys.forEach(key => {
        let value = obj[key];
        // 每一次迭代,都把回调函数执行
        callback(value, key);
    });
};

2.React.createEmelent

/* createElement:创建虚拟DOM对象 */
export function createElement(ele, props, ...children) {
    let virtualDOM = {
        $$typeof: Symbol('react.element'),
        key: null,
        ref: null,
        type: null,
        props: {}
    };
    let len = children.length;
    virtualDOM.type = ele;
    if (props !== null) {
        virtualDOM.props = {
            ...props
        };
    }
    if (len === 1) virtualDOM.props.children = children[0];
    if (len > 1) virtualDOM.props.children = children;
    return virtualDOM;
};

3.render

把虚拟DOM变为真实DOM

render函数在渲染的时候,如果type是:

  • 字符串:创建一个标签
  • 通函数:把函数执行,并且把props传递给函数
  • 造函数:把构造函数基于new执行「也就是创建类的一个实例」,也会把解析出来的props传递过去 - 调用一次类组件都会创建一个单独的实例 - 类组件中编写的render函数执行,把返回的jsx「virtualDOM」当做组件视图进行渲染!! 例如:jsx new Vote({ title:'React其实还是很好学的!' })
export function render(virtualDOM, container) {
    let { type, props } = virtualDOM;
    if (typeof type === "string") {
        // 存储的是标签名:动态创建这样一个标签
        let ele = document.createElement(type);
        // 为标签设置相关的属性 & 子节点
        each(props, (value, key) => {
            // className的处理:value存储的是样式类名
            if (key === 'className') {
                ele.className = value;
                return;
            }
            // style的处理:value存储的是样式对象
            if (key === 'style') {
                each(value, (val, attr) => {
                    ele.style[attr] = val;
                });
                return;
            }
            // 子节点的处理:value存储的children属性值
            if (key === 'children') {
                let children = value;
                if (!Array.isArray(children)) children = [children];
                children.forEach(child => {
                    // 子节点是文本节点:直接插入即可
                    if (/^(string|number)$/.test(typeof child)) {
                        ele.appendChild(document.createTextNode(child));
                        return;
                    }
                    // 子节点又是一个virtualDOM:递归处理
                    render(child, ele);
                });
                return;
            }
            ele.setAttribute(key, value);
        });
        // 把新增的标签,增加到指定容器中
        container.appendChild(ele);
    }
};