简单手写实现React类组件的state更新

简介: 简单手写实现React类组件的state更新

简单手写实现React类组件的state更新


继续学习写源码系列,本系列是学习系列。

  1. 简单手写实现 React 的 createElement 和 render,里面有准备工作,不再赘述。
  2. 简单手写实现 react 的函数组件
  3. 简单手写实现 react 的类组件

本文的目标是,手写实现React类组件的 state 更新。

TL;DR

  • setState两件事,更新state,更新DOM
  • 更新DOM,麻烦些,需要将旧DOM挂到组件实例上,新DOM重新生成,然后替换

准备工作

先将index.js加入类组件

/// import React from './source/react';
// import ReactDOM from './source/react-dom';
import React from 'react';
import ReactDOM from 'react-dom';
class Count extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 1 };
  }
  handleClick = () => {
    this.setState({ number: this.state.number + 1 });
  };
  render() {
    return (
      <div>
        {this.state.number} <br />
        <button onClick={this.handleClick}>增加</button>
      </div>
    );
  }
}
const reactElement = <Count />;
ReactDOM.render(reactElement, document.getElementById('root'));

加了 state,点击数据变化。

分析 state

  1. this.statethis.setState肯定在Component类上定义了,不然没法直接使用
  2. setState应该做了两件事:更新state和更新DOM

实现 state

1. 更新state

这没啥,在原始类上,直接更改

class Component {
  static isClassComponent = true;
  constructor(props) {
    this.props = props;
    this.state = {};
  }
  setState(partialState) {
    this.state = { ...this.state, ...partialState };
    // this.updateDom()
  }
}

2. 更新DOM

同样,也需要在原始类上,这边,暂不做DOM diff,只是单纯的将组件的新DOM替换旧DOM

于是:

  1. 必须知道组件生成的真实 DOM 的父节点
  2. state 修改之后,需要用新生成的 DOM,替换掉旧 DOM

问题在于,原始类里此时并不知道父节点,也不知道旧 DOM,更不知道新 DOM,=。=

想想怎么办呢????

回归到最开始的步骤,ReactDOM.render(<Count/>,root);

<Count/>实际上就是React.createElement({type: class Count..,props://}),既然是类,那么可以将真实的 DOM 以属性的形式添加到实例上,真实的 DOM 挂载之后,父节点可以通过 parentNode 获取。而新的 DOM,可以通过之前的方法createDOM(vdom)去生成。

2.1 更新 react-dom.js

// {type:class xx,props:{}}
const handleClassType = (vdomClassType) => {
  const { type: classFn, props } = vdomClassType;
  const instance = new classFn(props);
  // 这里添加
  // 这里将本身的vdom绑定到实例上,再在vdom转化成dom的时候,再把dom挂上来
  instance.curRenderVdom = vdomClassType;
  const vdomReturn = instance.render(props);
  return createElementDOM(vdomReturn);
};
export function createDOM(vdom) {
  const type = getType(vdom);
  const getDom = typeMap.get(type);
  let DOM = getDom(vdom);
  // 这里添加
  // 这里DOM挂到vdom上,这样从instance.curRenderVdom.dom就可以拿到现存dom了
  type !== 'text' && (vdom.dom = DOM);
  console.log(vdom);
  return DOM;
}

2.2 更新 react.js

setState(partialState) {
    this.state = { ...this.state, ...partialState };
    this.updateDom();
  }
  updateDom() {
    // 找到组件挂载的元素,也就是父元素
    // 重新生成组件的DOM,然后替换掉 现在的DOM,就更新啦
    const curDom = this.curRenderVdom.dom;
    const parentNode = curDom.parentNode;
    console.log('setState', this.curRenderVdom, curDom);
    // render是类组件必须有的,返回JSX,本质就是React.createElement(),返回值跟参数七七八八,也是有{type:class xx,props:{}}
    const newVdom = this.render();
    const newDom = createDOM(newVdom);
    // 这里注意,需要手动将curRenderVdom替换
    this.curRenderVdom = newVdom;
    // 替换
    parentNode.replaceChild(newDom, curDom);
  }

index.js里面的reactreact-dom重新换成我们自己写的,页面正常显示,✌️~~

补充

这里,React.createElement(参数),参数的类型大约四种,不同的种类,生成 DOM 的方式略有差别,所以分开了,贴上所有react-dom.js的代码。 对了,还有事件属性,需要额外处理,也一并在下面补充了,不再赘述。

function render(vdom, container) {
  // 本质就是挂载,因container本身存在于文档之中,所以挂载操作会触发渲染
  mount(vdom, container);
}
function mount(vdom, container) {
  // 将第一个参数变成真实DOM,插入到第二个参数挂载元素上
  const DOM = createDOM(vdom);
  container.append(DOM);
}
// vdom有四种类型
// 1. "直接就是字符串"
const handleTextType = (vdom) => document.createTextNode(vdom || '');
// 2. {type:'div',props:{...}}  type是字符串,组件根元素是原生标签
const handleElementType = (vdom) => createElementDOM(vdom);
// 3. {type:function X(){ return <h1/>},props:{...}},type是函数,返回值才是vdom
const handleFunctionType = (vdom) => {
  const { type: fn, props } = vdom;
  const vdomReturn = fn(props);
  return createElementDOM(vdomReturn);
};
// 4. {type: class x ...{ render(){return <h1/>} },props:{///}},type是函数,
// 但是静态属性有isClassComponent,实例的render函数返回值才是vdom
const handleClassType = (vdom) => {
  const { type: classFn, props } = vdom;
  const instance = new classFn(props);
  // 这里将本身的vdom 绑定到实例上(不是vdomReturn,不然找不到curRenderVdom)
  instance.curRenderVdom = vdom;
  console.log('执行');
  const vdomReturn = instance.render(props);
  return createElementDOM(vdomReturn);
};
// 根据vdom得到类型,从而根据类型,调用相应的方法生成真实DOM
const getType = (vdom) => {
  const isTextNode =
    typeof vdom === 'string' || typeof vdom === 'number' || vdom == null;
  if (isTextNode) return 'text';
  const isObject = typeof vdom === 'object';
  const isElementNode = isObject && typeof vdom.type === 'string';
  if (isElementNode) return 'element';
  const isFn = isObject && typeof vdom.type === 'function';
  return isFn && vdom.type.isClassComponent ? 'class' : 'function';
};
const typeMap = new Map([
  ['text', handleTextType],
  ['element', handleElementType],
  ['function', handleFunctionType],
  ['class', handleClassType],
]);
export function createDOM(vdom) {
  const type = getType(vdom);
  const getDom = typeMap.get(type);
  // @ts-ignore
  let DOM = getDom(vdom);
  // vdom和DOM一一对应,这里的vdom是四种类型
  type !== 'text' && (vdom.dom = DOM);
  console.log(vdom);
  return DOM;
}
function createElementDOM(vdom) {
  const { type, props } = vdom;
  let DOM = document.createElement(type);
  if (props) {
    updateProps(DOM, props);
    const { children } = props;
    children && updateChildren(DOM, children);
  }
  return DOM;
}
function updateProps(DOM, props) {
  // 正常遍历就好,特殊的特殊处理
  for (const key in props) {
    if (key === 'children') continue;
    // 事件处理!!
    if (/on[A-Z]+/.test(key)) {
      DOM[key.toLowerCase()] = props[key];
      continue;
    }
    if (key === 'style') {
      updateStyle(DOM, props[key]);
      continue;
    }
    DOM[key] = props[key];
  }
  function updateStyle(DOM, styleObj) {
    for (const key in styleObj) {
      DOM.style[key] = styleObj[key];
    }
  }
}
function updateChildren(DOM, children) {
  // 单个节点,直接插入(挂载)到DOM上; 多个节点,遍历插入
  const isOneChildren = !Array.isArray(children);
  isOneChildren
    ? mount(children, DOM)
    : children.forEach((child) => mount(child, DOM));
}
const ReactDOM = {
  render,
};
export default ReactDOM;


目录
相关文章
|
2月前
|
前端开发
React查询、搜索类功能的实现
React查询、搜索类功能的实现
17 0
|
3月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
40 0
|
1月前
react+typescript给state和props定义指定类型
react+typescript给state和props定义指定类型
16 1
|
2月前
|
存储 前端开发 中间件
React组件间的通信
React组件间的通信
15 1
|
2月前
|
前端开发 应用服务中间件 数据库
react服务端组件
react服务端组件
21 0
|
2月前
|
前端开发 JavaScript
快速上手React:从概述到组件与事件处理
快速上手React:从概述到组件与事件处理
|
3月前
|
前端开发 JavaScript API
React组件生命周期
React组件生命周期
74 1
|
3月前
|
资源调度 前端开发 JavaScript
React组件
React组件
41 0
|
3月前
|
存储 前端开发 JavaScript
探索 React Hooks 的世界:如何构建出色的组件(下)
探索 React Hooks 的世界:如何构建出色的组件(下)
探索 React Hooks 的世界:如何构建出色的组件(下)
|
3月前
|
缓存 前端开发 API
探索 React Hooks 的世界:如何构建出色的组件(上)
探索 React Hooks 的世界:如何构建出色的组件(上)
探索 React Hooks 的世界:如何构建出色的组件(上)