一、前言

从零到壹实现一个 React
setState 更新
React 生命周期

二、使用 webpack 搭建环境

a bundler for javascript and frinds 原则上只能打包 js 文件,通过 loader 机制,将非 js 文件打包到一起

  • 安装 webpack webpack-cli
  • 安装 babel-loader @babel/core @babel/preset-env
  • 处理 jsx,安装@babel/plugin-transform-react-jsx

webpack 配置

  1. module.exports = {
  2. entry: {
  3. main: './main.js',
  4. },
  5. mode: 'development',
  6. module: {
  7. rules: [
  8. {
  9. test: /\.js$/,
  10. use: {
  11. loader: 'babel-loader',
  12. options: {
  13. presets: ['@babel/preset-env'],
  14. plugins: [
  15. [
  16. '@babel/plugin-transform-react-jsx',
  17. { pragma: 'createElement' },
  18. ],
  19. ],
  20. },
  21. },
  22. },
  23. ],
  24. },
  25. optimization: {
  26. minimize: false,
  27. },
  28. };

三、在main.js中使用jsx语法表现视图

当前目录:
image.png

  1. function createElement(tarName, attributes, ...children) {
  2. let ele = document.createElement(tarName);
  3. for (const attribute in attributes) {
  4. ele.setAttribute(attribute, attributes[attribute]);
  5. }
  6. for (const child of children) {
  7. if (typeof child === 'string') {
  8. child = document.createTextNode(child);
  9. }
  10. ele.appendChild(child);
  11. }
  12. return ele;
  13. }
  14. document.body.appendChild(
  15. <div id="a" class="c">
  16. <div>1</div>
  17. <div>2</div>
  18. <div>3</div>
  19. </div>
  20. );
  21. // var a = createElement("div", {
  22. // id: "a",
  23. // "class": "c"
  24. // }, createElement("div", null, "1"), createElement("div", null, "2"), createElement("div", null, "3"));

这是就可以在js中去些jsx了

四、渲染自定义组件

将react处理的逻辑分离到rock-react.js中,现在的目录结构
image.png
在main.js 中使用,rock-react.js中处理
main.js代码如下:

  1. import { createElement, Component, renderDOM } from './rock-react.js';
  2. class MyComponent extends Component {
  3. render() {
  4. return (
  5. <div>
  6. <h1>我是MyComponent</h1>
  7. {this.children}
  8. </div>
  9. );
  10. }
  11. }
  12. renderDOM(
  13. <MyComponent id="a" class="c">
  14. <div>1</div>
  15. <div>2</div>
  16. <div>3</div>
  17. </MyComponent>,
  18. document.body
  19. );

rock-react.js 代码如下

  1. class ElementWrapper {
  2. constructor(type) {
  3. this.root = document.createElement(type);
  4. }
  5. setAttribute(name, value) {
  6. this.root.setAttribute(name, value);
  7. }
  8. appendChild(component) {
  9. this.root.appendChild(component.root);
  10. }
  11. }
  12. class TextWrapper {
  13. constructor(content) {
  14. this.root = document.createTextNode(content);
  15. }
  16. }
  17. export class Component {
  18. constructor(content) {
  19. debugger;
  20. this.props = Object.create(null);
  21. this.children = [];
  22. this._root = null;
  23. }
  24. setAttribute(name, value) {
  25. this.props[name] = value;
  26. }
  27. appendChild(component) {
  28. this.children.push(component);
  29. }
  30. get root() {
  31. if (!this._root) {
  32. this._root = this.render().root;
  33. }
  34. return this._root;
  35. }
  36. }
  37. export function createElement(type, attributes, ...children) {
  38. let ele;
  39. if (typeof type === 'string') {
  40. // 元素处理
  41. ele = new ElementWrapper(type);
  42. } else {
  43. // 自定义组件处理
  44. ele = new type();
  45. }
  46. // 属性处理
  47. for (const attribute in attributes) {
  48. ele.setAttribute(attribute, attributes[attribute]);
  49. }
  50. // children处理
  51. let insertChildren = (children) => {
  52. for (const child of children) {
  53. if (typeof child === 'string') {
  54. // 处理文本节点
  55. child = new TextWrapper(child);
  56. }
  57. // 子元素是数组,递归处理插入到元素上
  58. if (typeof child === 'object' && child instanceof Array) {
  59. insertChildren(child);
  60. } else {
  61. ele.appendChild(child);
  62. }
  63. }
  64. };
  65. insertChildren(children);
  66. return ele;
  67. }
  68. export function renderDOM(component, parentElement) {
  69. parentElement.appendChild(component.root);
  70. }

此时, 页面已经可以正确渲染自定义组件以及子标签
image.png

五、处理setState

1、 使用range替代根节点,重构rock-react.js

  1. const RENDER_TO_DOM = Symbol('render to dom');
  2. class ElementWrapper {
  3. constructor(type) {
  4. this.root = document.createElement(type);
  5. }
  6. setAttribute(name, value) {
  7. this.root.setAttribute(name, value);
  8. }
  9. appendChild(component) {
  10. let range = document.createRange();
  11. range.setStart(this.root, this.root.childNodes.length);
  12. range.setEnd(this.root, this.root.childNodes.length);
  13. component[RENDER_TO_DOM](range);
  14. }
  15. [RENDER_TO_DOM](range) {
  16. range.deleteContents();
  17. range.insertNode(this.root);
  18. }
  19. }
  20. class TextWrapper {
  21. constructor(content) {
  22. this.root = document.createTextNode(content);
  23. }
  24. [RENDER_TO_DOM](range) {
  25. range.deleteContents();
  26. range.insertNode(this.root);
  27. }
  28. }
  29. export class Component {
  30. constructor(content) {
  31. this.props = Object.create(null);
  32. this.children = [];
  33. this._root = null;
  34. }
  35. setAttribute(name, value) {
  36. this.props[name] = value;
  37. }
  38. appendChild(component) {
  39. this.children.push(component);
  40. }
  41. // 使用range,DOM API进行操作
  42. [RENDER_TO_DOM](range) {
  43. this.render()[RENDER_TO_DOM](range);
  44. }
  45. }
  46. export function createElement(type, attributes, ...children) {
  47. let ele;
  48. if (typeof type === 'string') {
  49. // 元素处理
  50. ele = new ElementWrapper(type);
  51. } else {
  52. // 自定义组件处理
  53. ele = new type();
  54. }
  55. // 属性处理
  56. for (const attribute in attributes) {
  57. ele.setAttribute(attribute, attributes[attribute]);
  58. }
  59. // children处理
  60. let insertChildren = (children) => {
  61. for (const child of children) {
  62. if (typeof child === 'string') {
  63. // 处理文本节点
  64. child = new TextWrapper(child);
  65. }
  66. // 子元素是数组,递归处理插入到元素上
  67. if (typeof child === 'object' && child instanceof Array) {
  68. insertChildren(child);
  69. } else {
  70. ele.appendChild(child);
  71. }
  72. }
  73. };
  74. insertChildren(children);
  75. return ele;
  76. }
  77. export function renderDOM(component, parentElement) {
  78. let range = document.createRange();
  79. // 首尾设置正确的值,有可能有注释节点
  80. range.setStart(parentElement, 0);
  81. range.setEnd(parentElement, parentElement.childNodes.length);
  82. range.deleteContents();
  83. component[RENDER_TO_DOM](range);
  84. }

2、实现最终的setState的更新

main.js 代码如下:

  1. const RENDER_TO_DOM = Symbol('render to dom');
  2. class ElementWrapper {
  3. constructor(type) {
  4. this.root = document.createElement(type);
  5. }
  6. setAttribute(name, value) {
  7. console.log(name.match(/^on([\s\S]+)$/), '///');
  8. if (name.match(/^on([\s\S]+)$/)) {
  9. this.root.addEventListener(
  10. RegExp.$1.replace(/^[\s\S]/, (c) => c.toLowerCase()),
  11. value
  12. );
  13. } else {
  14. if (name === 'className') {
  15. this.root.setAttribute('class', value);
  16. } else {
  17. this.root.setAttribute(name, value);
  18. }
  19. }
  20. }
  21. appendChild(component) {
  22. let range = document.createRange();
  23. range.setStart(this.root, this.root.childNodes.length);
  24. range.setEnd(this.root, this.root.childNodes.length);
  25. component[RENDER_TO_DOM](range);
  26. }
  27. [RENDER_TO_DOM](range) {
  28. range.deleteContents();
  29. range.insertNode(this.root);
  30. }
  31. }
  32. class TextWrapper {
  33. constructor(content) {
  34. this.root = document.createTextNode(content);
  35. }
  36. [RENDER_TO_DOM](range) {
  37. range.deleteContents();
  38. range.insertNode(this.root);
  39. }
  40. }
  41. export class Component {
  42. constructor(content) {
  43. this.props = Object.create(null);
  44. this.children = [];
  45. this._root = null;
  46. }
  47. setAttribute(name, value) {
  48. this.props[name] = value;
  49. }
  50. appendChild(component) {
  51. this.children.push(component);
  52. }
  53. // 使用range,DOM API进行操作
  54. [RENDER_TO_DOM](range) {
  55. this._range = range;
  56. this.render()[RENDER_TO_DOM](range);
  57. }
  58. rerender() {
  59. // 处理range的bug,先插入在删除
  60. // 保存老得range
  61. let oldRange = this._range;
  62. // 创建一个新的range,放到老得range的start位置
  63. let range = document.createRange();
  64. range.setStart(this._range.startContainer, oldRange.startOffset);
  65. range.setEnd(oldRange.startContainer, oldRange.startOffset);
  66. this[RENDER_TO_DOM](range);
  67. // 老得range挪到新range之后
  68. oldRange.setStart(range.endContainer, range.endOffset);
  69. oldRange.deleteContents();
  70. }
  71. setState(newState) {
  72. if (this.state === null || typeof this.state !== 'object') {
  73. this.state = newState;
  74. this.rerender();
  75. return;
  76. }
  77. let merge = (oldState, newState) => {
  78. for (const p in newState) {
  79. if (oldState[p] === null || typeof oldState[p] !== 'object') {
  80. oldState[p] = newState[p];
  81. } else {
  82. merge(oldState[p], newState[p]);
  83. }
  84. }
  85. };
  86. merge(this.state, newState);
  87. this.rerender();
  88. }
  89. }
  90. export function createElement(type, attributes, ...children) {
  91. let ele;
  92. if (typeof type === 'string') {
  93. // 元素处理
  94. ele = new ElementWrapper(type);
  95. } else {
  96. // 自定义组件处理
  97. ele = new type();
  98. }
  99. // 属性处理
  100. for (const attribute in attributes) {
  101. ele.setAttribute(attribute, attributes[attribute]);
  102. }
  103. // children处理
  104. let insertChildren = (children) => {
  105. for (const child of children) {
  106. if (typeof child === 'string') {
  107. // 处理文本节点
  108. child = new TextWrapper(child);
  109. }
  110. // 自节点为空,跳过这个处理
  111. if (child === null) {
  112. continue;
  113. }
  114. // 子元素是数组,递归处理插入到元素上
  115. if (typeof child === 'object' && child instanceof Array) {
  116. insertChildren(child);
  117. } else {
  118. ele.appendChild(child);
  119. }
  120. }
  121. };
  122. insertChildren(children);
  123. return ele;
  124. }
  125. export function renderDOM(component, parentElement) {
  126. let range = document.createRange();
  127. // 首尾设置正确的值,有可能有注释节点
  128. range.setStart(parentElement, 0);
  129. range.setEnd(parentElement, parentElement.childNodes.length);
  130. range.deleteContents();
  131. component[RENDER_TO_DOM](range);
  132. }

我们已经可以使用上面的代码,完成下面的小例子了
地址
image.png
此时,不足之处在于,每次更新,会对root跟节点全部更新,下面我们将要改造成局部改变,局部更新

六、虚拟DOM的原理和关键实现

1、去掉this.root这个DOM元素的代理,创建虚拟DOM

  1. const RENDER_TO_DOM = Symbol('render to dom');
  2. export class Component {
  3. constructor(content) {
  4. this.props = Object.create(null);
  5. this.children = [];
  6. this._root = null;
  7. }
  8. setAttribute(name, value) {
  9. this.props[name] = value;
  10. }
  11. appendChild(component) {
  12. this.children.push(component);
  13. }
  14. get vdom() {
  15. return this.render().vdom;
  16. }
  17. // 使用range,DOM API进行操作
  18. [RENDER_TO_DOM](range) {
  19. this._range = range;
  20. this.render()[RENDER_TO_DOM](range);
  21. }
  22. rerender() {
  23. // 处理range的bug,先插入在删除
  24. // 保存老得range
  25. let oldRange = this._range;
  26. // 创建一个新的range,放到老得range的start位置
  27. let range = document.createRange();
  28. range.setStart(this._range.startContainer, oldRange.startOffset);
  29. range.setEnd(oldRange.startContainer, oldRange.startOffset);
  30. this[RENDER_TO_DOM](range);
  31. // 老得range挪到新range之后
  32. oldRange.setStart(range.endContainer, range.endOffset);
  33. oldRange.deleteContents();
  34. }
  35. setState(newState) {
  36. if (this.state === null || typeof this.state !== 'object') {
  37. this.state = newState;
  38. this.rerender();
  39. return;
  40. }
  41. let merge = (oldState, newState) => {
  42. for (const p in newState) {
  43. if (oldState[p] === null || typeof oldState[p] !== 'object') {
  44. oldState[p] = newState[p];
  45. } else {
  46. merge(oldState[p], newState[p]);
  47. }
  48. }
  49. };
  50. merge(this.state, newState);
  51. this.rerender();
  52. }
  53. }
  54. class ElementWrapper extends Component {
  55. constructor(type) {
  56. super(type);
  57. this.type = type;
  58. this.root = document.createElement(type);
  59. }
  60. get vdom() {
  61. return this;
  62. /*{
  63. type: this.type,
  64. props: this.props,
  65. children: this.children.map((child) => child.vdom),
  66. };*/
  67. }
  68. get vchildren() {
  69. return this.children.map((child) => child.vdom);
  70. }
  71. // setAttribute(name, value) {
  72. // if (name.match(/^on([\s\S]+)$/)) {
  73. // this.root.addEventListener(
  74. // RegExp.$1.replace(/^[\s\S]/, (c) => c.toLowerCase()),
  75. // value
  76. // );
  77. // } else {
  78. // if (name === 'className') {
  79. // this.root.setAttribute('class', value);
  80. // } else {
  81. // this.root.setAttribute(name, value);
  82. // }
  83. // }
  84. // }
  85. // appendChild(component) {
  86. // let range = document.createRange();
  87. // range.setStart(this.root, this.root.childNodes.length);
  88. // range.setEnd(this.root, this.root.childNodes.length);
  89. // component[RENDER_TO_DOM](range);
  90. // }
  91. // 完成虚拟DOM到实体DOM的更新
  92. [RENDER_TO_DOM](range) {
  93. range.deleteContents();
  94. // 自己创建一个root
  95. let root = document.createElement(this.type);
  96. // 处理attributes
  97. for (const name in this.props) {
  98. let value = this.props[name];
  99. if (name.match(/^on([\s\S]+)$/)) {
  100. root.addEventListener(
  101. RegExp.$1.replace(/^[\s\S]/, (c) => c.toLowerCase()),
  102. value
  103. );
  104. } else {
  105. if (name === 'className') {
  106. root.setAttribute('class', value);
  107. } else {
  108. root.setAttribute(name, value);
  109. }
  110. }
  111. }
  112. // 处理children
  113. for (const child of this.children) {
  114. let childRange = document.createRange();
  115. childRange.setStart(root, root.childNodes.length);
  116. childRange.setEnd(root, root.childNodes.length);
  117. child[RENDER_TO_DOM](childRange);
  118. }
  119. range.insertNode(root);
  120. }
  121. }
  122. class TextWrapper extends Component {
  123. constructor(content) {
  124. super(content);
  125. this.content = content;
  126. (this.type = '#text'), (this.root = document.createTextNode(content));
  127. }
  128. get vdom() {
  129. return this;
  130. /*{
  131. type: '#text',
  132. content: this.content,
  133. };*/
  134. }
  135. [RENDER_TO_DOM](range) {
  136. range.deleteContents();
  137. range.insertNode(this.root);
  138. }
  139. }
  140. export function createElement(type, attributes, ...children) {
  141. let ele;
  142. if (typeof type === 'string') {
  143. // 元素处理
  144. ele = new ElementWrapper(type);
  145. } else {
  146. // 自定义组件处理
  147. ele = new type();
  148. }
  149. // 属性处理
  150. for (const attribute in attributes) {
  151. ele.setAttribute(attribute, attributes[attribute]);
  152. }
  153. // children处理
  154. let insertChildren = (children) => {
  155. for (const child of children) {
  156. if (typeof child === 'string') {
  157. // 处理文本节点
  158. child = new TextWrapper(child);
  159. }
  160. // 自节点为空,跳过这个处理
  161. if (child === null) {
  162. continue;
  163. }
  164. // 子元素是数组,递归处理插入到元素上
  165. if (typeof child === 'object' && child instanceof Array) {
  166. insertChildren(child);
  167. } else {
  168. ele.appendChild(child);
  169. }
  170. }
  171. };
  172. insertChildren(children);
  173. return ele;
  174. }
  175. export function renderDOM(component, parentElement) {
  176. let range = document.createRange();
  177. // 首尾设置正确的值,有可能有注释节点
  178. range.setStart(parentElement, 0);
  179. range.setEnd(parentElement, parentElement.childNodes.length);
  180. range.deleteContents();
  181. component[RENDER_TO_DOM](range);
  182. }

2、实现VDOM比对

const RENDER_TO_DOM = Symbol('render to dom');

// 空range处理
function replaceContent(range, node) {
  // if (!range) return;
  range.insertNode(node);
  range.setStartAfter(node);
  range.deleteContents();

  range.setStartBefore(node);
  range.setEndAfter(node);
}

export class Component {
  constructor(content) {
    this.props = Object.create(null);
    this.children = [];
    this._root = null;
    this._range = null;
  }
  setAttribute(name, value) {
    this.props[name] = value;
  }
  appendChild(component) {
    this.children.push(component);
  }

  get vdom() {
    return this.render().vdom;
  }

  // 使用range,DOM API进行操作
  [RENDER_TO_DOM](range) {
    this._range = range;
    this._vdom = this.vdom; // 这是一个getter
    this.render()[RENDER_TO_DOM](range);
  }

  update() {
    let isSameNode = (oldNode, newNode) => {
      // 类型不同
      if (oldNode.type !== newNode.type) return false;
      // 属性不同
      for (const name in newNode.props) {
        if (newNode.props[name] !== oldNode.props[name]) {
          return false;
        }
      }
      // 属性数量不同
      if (
        Object.keys(oldNode.props).length > Object.keys(newNode.props).length
      ) {
        return false;
      }
      // 文本的内容不同
      if (newNode.type === '#text') {
        if (newNode.content !== oldNode.content) return false;
      }
      return true;
    };
    let update = (oldNode, newNode) => {
      // 对比根节点,对比children
      // type, props, children
      // #text content
      if (isSameNode(oldNode, newNode)) {
        //取出oldNode的range替换 新旧节点不一样
        newNode[RENDER_TO_DOM](oldNode._range);
        return;
      }
      // 新旧节点一致
      newNode._range = oldNode._range;

      // 处理children
      let newChildren = newNode.vchildren;
      let oldChildren = oldNode.vchildren;

      if (!newChildren || !newChildren.length) {
        return;
      }

      let tailRange = oldChildren[oldChildren.length - 1]._range;

      for (let i = 0; i < newChildren.length; i++) {
        let newChild = newChildren[i];
        let oldChild = oldChildren[i];
        if (i < oldChildren.length) {
          update(oldChild, newChild);
        } else {
          let range = document.createRange();
          range.setStart(tailRange.endContainer, tailRange.endOffset);
          range.setEnd(tailRange.endContainer, tailRange.endOffset);
          newChild[RENDER_TO_DOM](range);
          tailRange = range;
        }
      }
    };
    let vdom = this.vdom;
    update(this._vdom, vdom);
    this._vdom = vdom;
  }

  setState(newState) {
    debugger;
    if (this.state === null || typeof this.state !== 'object') {
      this.state = newState;
      // this.rerender();
      this.update();
      return;
    }
    let merge = (oldState, newState) => {
      for (const p in newState) {
        if (oldState[p] === null || typeof oldState[p] !== 'object') {
          oldState[p] = newState[p];
        } else {
          merge(oldState[p], newState[p]);
        }
      }
    };
    merge(this.state, newState);
    this.update();
  }
}

class ElementWrapper extends Component {
  constructor(type) {
    super(type);
    this.type = type;
    this.root = document.createElement(type);
  }

  get vdom() {
    this.vchildren = this.children.map((child) => child.vdom);
    return this;
  }

  // 完成虚拟DOM到实体DOM的更新
  [RENDER_TO_DOM](range) {
    this._range = range;
    // range.deleteContents();
    // 自己创建一个root
    let root = document.createElement(this.type);

    // 处理attributes
    for (const name in this.props) {
      let value = this.props[name];
      if (name.match(/^on([\s\S]+)$/)) {
        root.addEventListener(
          RegExp.$1.replace(/^[\s\S]/, (c) => c.toLowerCase()),
          value
        );
      } else {
        if (name === 'className') {
          root.setAttribute('class', value);
        } else {
          root.setAttribute(name, value);
        }
      }
    }

    if (!this.vchildren) {
      this.vchildren = this.children.map((child) => child.vdom);
    }
    // 处理children
    for (const child of this.vchildren) {
      let childRange = document.createRange();
      childRange.setStart(root, root.childNodes.length);
      childRange.setEnd(root, root.childNodes.length);
      child[RENDER_TO_DOM](childRange);
    }

    replaceContent(range, root);
    // range.insertNode(root);
  }
}

class TextWrapper extends Component {
  constructor(content) {
    super(content);
    this.type = '#text';
    this.content = content;
    // this.root = document.createTextNode(content);
  }

  get vdom() {
    return this;
  }

  [RENDER_TO_DOM](range) {
    this._range = range;
    let root = document.createTextNode(this.content);
    replaceContent(range, root);
    // range.deleteContents();
    // range.insertNode(this.root);
  }
}

export function createElement(type, attributes, ...children) {
  let ele;
  if (typeof type === 'string') {
    // 元素处理
    ele = new ElementWrapper(type);
  } else {
    // 自定义组件处理
    ele = new type();
  }

  // 属性处理
  for (const attribute in attributes) {
    ele.setAttribute(attribute, attributes[attribute]);
  }
  // children处理
  let insertChildren = (children) => {
    for (const child of children) {
      if (typeof child === 'string') {
        // 处理文本节点
        child = new TextWrapper(child);
      }
      // 子节点为空,跳过这个处理
      if (child === null) {
        continue;
      }
      // 子元素是数组,递归处理插入到元素上
      if (typeof child === 'object' && child instanceof Array) {
        insertChildren(child);
      } else {
        ele.appendChild(child);
      }
    }
  };
  insertChildren(children);
  return ele;
}

export function renderDOM(component, parentElement) {
  let range = document.createRange();
  // 首尾设置正确的值,有可能有注释节点
  range.setStart(parentElement, 0);
  range.setEnd(parentElement, parentElement.childNodes.length);
  range.deleteContents();
  component[RENDER_TO_DOM](range);
}