1

系列文章

React系列(一)-- 2013起源 OSCON - React Architecture by vjeux

React系列(二)-- React基本语法实现思路

React系列(三)-- Jsx, 合成事件与Refs

React系列(四)--- virtualdom diff算法实现分析

React系列(五)--- 从Mixin到HOC

React系列(六)--- 从HOC再到HOOKS

前言

我们先不讲什么语法原理,先根据API效果强行模拟语法使用,实现一个简易版的React.

下面代码跟React源码没有关系,只是单纯模拟思路实现

render

第一步我们先用类创建一个元素返回,并且绑定点击事件,代码如下,可以正常看到一个按钮出现了.

class AddButton {
  // 创建Dom
  createDOM(domString) {
    const div = document.createElement("div");
    div.innerHTML = domString;
    return div;
  }

  // 输出实例
  render() {
    this.dom = this.createDOM(`<button>0</button>`);
    this.dom.addEventListener("click", () => console.log("click"), false);
    return this.dom;
  }
}

// 插入页面
document.body.appendChild(new AddButton().render());

state && setState

实现类状态和修改状态方法

class AddButton {
  // 内置状态
  constructor() {
    this.state = { num: 0 };
  }

  // 创建Dom
  createDOM(domString) {
    const div = document.createElement("div");
    div.innerHTML = domString;
    return div;
  }

  // 唯一修改状态更新视图
  setState(state) {
    this.state = state;
    this.dom = this.render();
  }

  // 修改数据
  handleAdd() {
    const num = this.state.num + 1;
    this.setState({
      num: num
    });
  }

  // 输出实例
  render() {
    this.dom = this.createDOM(`<button id="btn">${this.state.num}</button>`);
    this.dom.addEventListener("click", () => this.handleAdd(), false);
    return this.dom;
  }
}

// 插入页面
document.body.appendChild(new AddButton().render());

渲染之后看到this.dom输出已经发现改变了,但是视图并没有渲染,那是因为这是结尾一次性插入,下面就渲染视图这块往下走

重新渲染

我们现在把插入数据的操作内置到class里面,新增一个方法插入新元素移除旧元素.

class AddButton {
  // 内置状态
  constructor() {
    this.state = { num: 0 };
  }

  // 创建Dom
  createDOM(domString) {
    const div = document.createElement("div");
    div.innerHTML = domString;
    return div;
  }

  // 只是一个替换元素的方法
  changeDom() {
    const oDom = this.dom;
    this.dom = this.render();
    document.body.insertBefore(this.dom, oDom);
    document.body.removeChild(oDom);
  }

  // 唯一修改状态更新视图
  setState(state) {
    this.state = state;
    this.changeDom();
  }

  // 修改数据
  handleAdd() {
    const num = this.state.num + 1;
    this.setState({
      num: num
    });
  }

  // 输出实例
  render() {
    this.dom = this.createDOM(`<button id="btn">${this.state.num}</button>`);
    this.dom.addEventListener("click", () => this.handleAdd(), false);
    return this.dom;
  }
}

// 插入页面
document.body.appendChild(new AddButton().render());

现在效果虽然实现,但是还是得开头手动把元素插入视图

抽取公用类

我们先将一些共有方法提取到一个单独类,另外补全一下props属性传参

class React {
  // 内置状态
  constructor(props = {}) {
    // 实例
    this.wrapper = null
    // 状态
    this.state = {}
    // 属性
    this.props = {}
  }

  // 创建Dom
  createDOM(domString) {
    const div = document.createElement("div");
    div.innerHTML = domString;
    return div;
  }

  // 只是一个替换元素的方法
  changeDom() {
    const oDom = this.dom;
    this.dom = this._render();
    this.wrapper.insertBefore(this.dom, oDom);
    this.wrapper.removeChild(oDom);
  }

  // 唯一修改状态更新视图
  setState(state) {
    this.state = state;
    this.changeDom();
  }

  // 输出实例
  _render(wrapper) {
    if (wrapper) this.wrapper = wrapper;
    this.dom = this.createDOM(this.render());
    this.dom.addEventListener("click", () => console.log('添加自定义事件'), false);
    return this.dom;
  }
}

然后组件只需要直接继承Component然后处理自己逻辑即可

class AddButton extends React {
  constructor() {
    super();
    this.state = { num: 0 };
  }

  handleAdd() {
    const num = this.state.num + 1;
    this.setState({
      num: num
    });
  }

  render() {
    return `<button id="btn">${this.state.num}</button>`;
  }
}

上面做了几件事:

  1. 抽取通用的逻辑到React类
  2. 自定义组件AddButton

还有一个问题是点击事件暂时还是耦合进Component里面,下一章节实现

添加自定义事件

因为事件是由组件自定义的,所以我们思路是在组件实例定义好之后,通用类绑定事件

class React {
  // 内置状态
  constructor(props = {}) {
    // 实例
    this.wrapper = null
    // 状态
    this.state = {}
    // 属性
    this.props = {}
    // 事件
    this.event = {}
  }

  // 创建Dom
  createDOM(domString) {
    const div = document.createElement("div");
    div.innerHTML = domString;
    return div;
  }

  // 只是一个替换元素的方法
  changeDom() {
    const oDom = this.dom;
    this.dom = this._render();
    this.wrapper.insertBefore(this.dom, oDom);
    this.wrapper.removeChild(oDom);
  }

  // 唯一修改状态更新视图
  setState(state) {
    this.state = state;
    this.changeDom();
  }

  // 初始化事件
  initEvent() {
    const events = Object.keys(this.event)
    events.forEach(key => {
      this.dom.addEventListener(key, this.event[key].bind(this), false);
    })
  }

  // 输出实例
  _render(wrapper) {
    if (wrapper) this.wrapper = wrapper;
    this.dom = this.createDOM(this.render());
    // 需要创建实例后才能初始化
    this.dom && this.initEvent()
    return this.dom;
  }
}

同时组件代码也需要相对应的修改

class AddButton extends React {
  constructor() {
    super();
    this.state = { num: 0 };
    this.event = {
      click: this.handleAdd
    }
  }

  handleAdd() {
    const num = this.state.num + 1;
    this.setState({
      num: num
    });
  }

  render() {
    return `<button id="btn">${this.state.num}</button>`;
  }
}

ReactDom.render

大家都知道React会提供这么一个方法将组件插入一个指定元素,我们直接模拟

const ReactDom = {
  // 指定元素内插入组件实例
  render(component, wrapper) {
    wrapper.appendChild(component._render(wrapper));
  }
};

最终运行代码

我们上面已经实现了几个功能:

  1. 负责创建元素的React类,包括单向数据流,自定义状态,事件,替换元素等
  2. 负责挂载的ReactDom类
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
</head>

<body>
  <div id="root"></div>
  <script>
    class React {
      // 内置状态
      constructor(props = {}) {
        // 实例
        this.wrapper = null
        // 状态
        this.state = {}
        // 属性
        this.props = {}
        // 事件
        this.event = {}
      }

      // 创建Dom
      createDOM(domString) {
        const div = document.createElement("div");
        div.innerHTML = domString;
        return div;
      }

      // 只是一个替换元素的方法
      changeDom() {
        const oDom = this.dom;
        this.dom = this._render();
        this.wrapper.insertBefore(this.dom, oDom);
        this.wrapper.removeChild(oDom);
      }

      // 唯一修改状态更新视图
      setState(state) {
        this.state = state;
        this.changeDom();
      }

      // 初始化事件
      initEvent() {
        const events = Object.keys(this.event)
        events.forEach(key => {
          this.dom.addEventListener(key, this.event[key].bind(this), false);
        })
      }

      // 输出实例
      _render(wrapper) {
        if (wrapper) this.wrapper = wrapper;
        this.dom = this.createDOM(this.render());
        // 需要创建实例后才能初始化
        this.dom && this.initEvent()
        return this.dom;
      }
    }

    const ReactDom = {
      // 指定元素内插入组件实例
      render(component, wrapper) {
        wrapper.appendChild(component._render(wrapper));
      }
    };

    class AddButton extends React {
      constructor() {
        super();
        this.state = { num: 0 };
        this.event = {
          click: this.handleAdd
        }
      }

      handleAdd() {
        const num = this.state.num + 1;
        this.setState({
          num: num
        });
      }

      render() {
        return `<button id="btn">${this.state.num}</button>`;
      }
    }

    ReactDom.render(new AddButton(), document.getElementById("root"));
  </script>
</body>

</html>

至此,抛开实际思路不说,我们已经简单模拟出来React的基本语法实现了.


Afterward
621 声望62 粉丝

努力去做,对的坚持,静待结果