zoukankan      html  css  js  c++  java
  • 实现一个简易版的react

    实现一个简易版的react


    React.createElement

    作用:将babel解析的结果转换成树形结构

    class Element {
        constructor(type, props) {
            this.type = type;
            this.props = props;
        }
    }
    function createElement(type, props, ...children) {
        props = props || {};
        props.children = children;
        return new Element(type, props);
    }
    export default createElement
    

    转化前:

    let element = React.createElement("h1",
      { class: "app", style: "color:red;font-size:100px;" },
      "hello",
      React.createElement("button", null, "123"));
    

    转化后:

    React.render

    作用:渲染元素到对应位置

    /* react.js */
    import $ from 'jquery'
    import createElement from './element'
    import createReactUnit from './unit';
    import Component from './component'
    /* React对象 */
    let React = {
        createElement,
        render,
        nextRootIndex: 0,/* 元素编号 */
        Component
    }
    /* render负责将转化的element渲染到页面 */
    function render(element, container) {
        /* 创建单元并编号 */
        let createReactUnitInstance = createReactUnit(element);
        let markUp = createReactUnitInstance.getMarkUp(React.nextRootIndex);
        /* 渲染到容器上 */
        $(container).html(markUp);
        /* 触发订阅函数 */
        $(document).trigger('mounted');
    }
    

    createReactUnit

    createReactUnit函数负责将传入的格式化element分为三类分别处理(文本,原生元素,组件),另外创建一个父类,减少冗余代码

    import $ from 'jquery'
    /* 创建一个父类,放置多次重复写constructor */
    class Unit {
        constructor(element) { this.currentElement = element;  }
    }
    /* 文本单元 */
    class ReactTextUnit extends Unit {
        getMarkUp(rootId) {
            //...
        }
    }
    /* 原生单元 */
    class ReactNativeUnit extends Unit {
        getMarkUp(rootId) {
            //...
        }
    }
    /* 组件单元 */
    class ReactComponent extends Unit {
        getMarkUp(rootId) {
            //...
        }
    }
    /* 根据不同类型生成不同单元 */
    function createReactUnit(element) {
        /* 创建文本单元 */
        if (typeof element === "string" || typeof element === "number") {
            return new ReactTextUnit(element);
        }
        /* 创建标签 */
        if (typeof element === "object" && typeof element.type === "string") {
            return new ReactNativeUnit(element);
        }
        /* 组件 */
        if (typeof element === "object"  && typeof element.type === "function") {
            return new ReactComponent(element);
        }
    }
    export default createReactUnit
    
    1. 文本
      直接在用span包围并记录data-reactid
    class ReactTextUnit extends Unit {
        getMarkUp(rootId) {
            /* 存rootId */
            this._rootId = rootId;
            /* <span data-reactid="0">111</span> */
            return `<span data-reactid="${rootId}">${this.currentElement}</span>`;
        }
    }
    
    1. 原生标签
      通过字符串拼接的方式连接属性,对于children,通过递归的方式创建子单元,用一个字符串content来存生成的标签字符串,对于onClick等事件绑定,使用$(document).on()绑定事件解决字符串无法绑定的问题
    class ReactNativeUnit extends Unit {
        getMarkUp(rootId) {
            this._rootId = rootId;
            /* 提取数据 */
            let { type, props } = this.currentElement;
            /* 创建外围标签 */
            let tagStart = `<${type} data-reactid=${rootId}`, tagEnd = `</${type}>`;
            /* 遍历标签属性 */
            let content = "";
            for (let key in props) {
                /* 儿子结点 */
                if (key === "children") {
                    content += props[key].map((child, index) => {
                        let childUnit = createReactUnit(child);
                        return childUnit.getMarkUp(`${rootId}.${index}`)
                    }).join('');
                }
                /* {onClick:show} 事件 */
                else if (/^on[A-Z]/.test(key)) {
                    /* 绑定事件 */
                    let eventType = key.slice(2).toLowerCase();
                    $(document).on(eventType, `[data-reactid="${rootId}"]`, props[key]);
                }
                /* 普通属性 */
                else
                    tagStart += ` ${key}="${props[key]}" `;
            }
            /* 拼接返回 */
            return `${tagStart}>${content}${tagEnd}`;
        }
    }
    

    传入element

    (
    <h1 class="app" style="color:red;font-size:100px;">
        hello
        <button onClick={show}>123</button>
    </h1>
    )
    

    效果

    1. 组件
      babel解析组件的结果第一个值是Counter类,调用createElement后赋值到type上,生成单元时可以通过type获取到Counter,然后新建实例获得类中render方法的结果,对结果进行创建单元和调用getMarkUp方法,得到标签字符串markUp并返回,另外还可以通过创建实例的方式获取生命周期函数,组件挂载前直接调用,页面挂载后通过$(document).trigger('mounted')触发执行
    组件jsx格式:
    <Counter name="mike"/>
    解析结果:
    React.createElement(Counter, {
      name: "mike"
    });
    
    class ReactComponent extends Unit {
        getMarkUp(rootId) {
            this._rootId = rootId;
            /* 获取到Conponent类 */
            let { type: Component, props } = this.currentElement;
            let componentInstance = new Component(props);
            /* 挂载前生命周期函数 */
            componentInstance.componentWillMount &&
                componentInstance.componentWillMount();
            // 获取实例render返回值
            let element = componentInstance.render();
            let markUp = createReactUnit(element).getMarkUp(rootId);
            /* 挂载后生命周期 */
            $(document).on('mounted', () => {
                componentInstance.componentDidMount &&
                    componentInstance.componentDidMount();
            });
            return markUp;
        }
    }
    

    示例:

    class SubCounter {
      componentWillMount() {
        console.log("child  即将挂载");
      }
      componentDidMount() {
        console.log("child  挂载完成");
      }
      render() {
        return <h1 style="color:green" onClick={show}>888</h1>
      }
    }
    class Counter extends React.Component {
      constructor(props) {
        super(props);
        this.state = { number: 1 }
      }
      componentWillMount() {
        console.log("parent  即将挂载");
      }
      componentDidMount() {
        console.log("parent  挂载完成")
      }
      render() {
        return <SubCounter />;
      }
    }
    React.render(
      <Counter name="mike" />,
      document.getElementById('root')
    );
    

    效果:

  • 相关阅读:
    jQuery插件开发——元素拖拽和基于鼠标位置为中心缩放
    基于Quartz.Net的任务管理平台开发(3) —— 任务管理平台
    记录一下Linq实现多列排序的问题
    动态构造Lambda表达式
    MVC全局实现异常处理
    修改自增列起始值,仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时,才能为表'******'中的标识列指定显式值
    统计指定时间段内生日的用户
    SQL开关功能记录
    枚举对象实现 DropDownList 的转换操作二
    枚举对象实现 DropDownList 的转换操作一
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12990176.html
Copyright © 2011-2022 走看看