React源码分析(四)

207 阅读5分钟

React源码分析(一)

React源码分析(二)

React源码分析(三)

8.context

8.1.事例

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 13:25:37
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';
const PersonContext = React.createContext();
function getStyle(color) {
  return {
    border: `5px solid ${color}`,
    padding: '5px',
    marigin: '5px'
  }
}
class Person extends React.Component {
  state = {
    color: 'red'
  }

  changeColor = (color) => this.setState({ color })

  render() {
    const value = { color: this.state.color, changeColor: this.changeColor }
    return <PersonContext.Provider value={value}>
      <div style={{ ...getStyle(this.state.color), width: '200px' }}>
        Person
      <Head />
        <Body />
      </div>
    </PersonContext.Provider>
  }
}

class Head extends React.Component {
  static contextType = PersonContext;

  render() {
    return (
      <div style={getStyle(this.context.color)}>
        Head
        <Eye />
      </div>
    )
  }
}

class Body extends React.Component {
  static contextType = PersonContext;

  render() {
    return (
      <div style={getStyle(this.context.color)}>
        Body
        <Hand />
      </div>
    )
  }
}

class Hand extends React.Component {
  static contextType = PersonContext;

  render() {
    return (
      <div style={getStyle(this.context.color)}>
        Hand
        <button onClick={() => this.context.changeColor('red')}>变红</button>
        <button onClick={() => this.context.changeColor('green')}>变绿</button>
      </div>
    )
  }
}

function Eye() {
  return <PersonContext.Consumer>
    {content => <div style={getStyle(content.color)}>Eye</div>}
  </PersonContext.Consumer>
}

ReactDOM.render(<Person />, document.getElementById('root'));

8.2.实现

8.2.1.src/react.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:34:24
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 13:33:21
 * @Modified By: dfh
 * @FilePath: /day25-react/src/react.js
 */
import Component from './Component';
import { wrapToVdom } from './utils';
/**
 * 
 * @param {*} type 元素类型
 * @param {*} config 配置对象
 * @param {*} children 孩子或者孩子门
 */
function createElement(type, config, children) {
    let ref, key;
    if (config) {
        delete config.__source;
        delete config.__self;
        ref = config.ref;
        key = config.key;
        delete config.ref;
        delete config.key;
    }
    let props = { ...config };

    if (arguments.length > 3) {//children是一个数组
        props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
    } else {
        props.children = wrapToVdom(children);
    }
    return {
        type,
        props,
        ref,
        key
    }
}

function createRef() {
    return { current: null }
}

+ function createContext(initialValue) {
+   Provider._value = initialValue;
+   function Provider(props) {
+       const { value } = props;
+       if (Provider._value) {
+           Object.assign(Provider._value, value)
+       } else {
+           Provider._value = value;
+       }
+       return props.children;
+   }
+   function Consumer(props) {
+       return props.children(Provider._value);
+   }
+   return { Provider, Consumer };
+ }
const React = {
    createElement,
    Component,
    createRef,
+   createContext
}
export default React;
8.2.2.src/react-dom.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:34:32
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 13:40:33
 * @Modified By: dfh
 * @FilePath: /day25-react/src/react-dom.js
 */

import { REACT_TEXT } from './constants';
import { addEvent } from './event';

/**
 * 给跟容器挂载的时候
 * @param {*} vdom 需要渲染的虚拟DOM
 * @param {*} container 容器
 */
function render(vdom, container) {
    const dom = createDOM(vdom);
    //挂载真实DOM
    container.appendChild(dom);
    //调用生命周期方法componentDidMount
    dom.componentDidMount && dom.componentDidMount();
}

/**
 * 创建证实DOM
 * @param {*} vdom 虚拟DOM
 */
export function createDOM(vdom) {
    const {
        type,
        props,
        key,
        ref
    } = vdom;
    //创建真实DOM
    let dom;
    if (type === REACT_TEXT) {//是文本
        dom = document.createTextNode(props.content);
    } else if (typeof type === 'function') {//自定义函数组件
        if (type.isReactComponent) {//类组件
            return mountClassComponent(vdom);
        } else {//函数组件
            return mountFunctionComponent(vdom);
        }
    } else {//原生组件
        dom = document.createElement(type);
    }

    //使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
    updateProps(dom, {}, props);
    if (typeof props.children === 'object' && props.children.type) {//只有一个儿子,并且是虚拟DOM
        render(props.children, dom);//把儿子变成真实DOM,并且挂载到自己身上
    } else if (Array.isArray(props.children)) {//有多个儿子
        reconcileChildren(props.children, dom);
    }

    //将真实DOM挂载到虚拟DOM上,以便后面取
    vdom.dom = dom;
    //通过虚拟DOM创建真实DOM之后,虚拟DOM的ref属性的current属性等于真实DOM
    ref && (ref.current = dom);
    return dom;
}

/**
 * 把一个类型为自定义类组件的虚拟DOM转化为一个真实DOM并返回
 * @param {*} vdom 类型为自定义类组件的虚拟DOM
 */
function mountClassComponent(vdom) {
    const { type: Clazz, props } = vdom;
    //获取类的实例
    const classInstance = new Clazz(props);
  	//context的实现
+   if (Clazz.contextType) {
+       classInstance.context = Clazz.contextType.Provider._value;
+   }
    //让这个类组件的虚拟DOM的classInstance属性指向这个类组件的实例
    vdom.classInstance = classInstance;
    //调用生命周期方法componentWillMount
    if (classInstance.componentWillMount) {
        classInstance.componentWillMount();
    }
    //执行生命周期方法getDerivedStateFromProps
    if (Clazz.getDerivedStateFromProps) {
        const partialState = Clazz.getDerivedStateFromProps(classInstance.props, classInstance.state)
        if (partialState) {
            classInstance.state = { ...classInstance.state, ...partialState };
        }
    }
    //获取虚拟DOM
    const oldRenderVdom = classInstance.render();
    //将虚拟DOM挂载的组件实例上,以便后面DOM-diff时用
    classInstance.oldRenderVdom = vdom.oldRenderVdom = oldRenderVdom;
    //获取真实DOM
    const dom = createDOM(oldRenderVdom);

    if (classInstance.componentDidMount) {
        dom.componentDidMount = classInstance.componentDidMount;
    }
    //将真实dom挂到实例上上
    classInstance.dom = dom;

    return dom;
}

/**
 * 把一个类型为自定义函数组件的虚拟DOM转换为一个真实DOM并返回
 * @param {*} vdom 类型为自定义函数组件的虚拟DOM
 */
function mountFunctionComponent(vdom) {
    const { type: FunctionComponent, props } = vdom;
    const renderVdom = FunctionComponent(props);
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}

/**
 * 
 * @param {*} childrenVdom 孩子门的虚拟DOM
 * @param {*} parentDOM 要挂载到的真实DOM
 */
function reconcileChildren(childrenVdom, parentDOM) {
    for (let i = 0; i < childrenVdom.length; i++) {
        const child = childrenVdom[i];
        render(child, parentDOM);//把儿子挂载的自己身上
    }
}
/**
 * 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
 * @param {*} dom 真实DOM
 * @param {*} props 虚拟DOM属性
 */
function updateProps(dom, oldProps, props) {
    for (const key in props) {
        if (key === 'children') continue;//单独处理,不再此处处理
        if (key === 'style') {
            const styleObj = props.style;
            for (const attr in styleObj) {
                dom.style[attr] = styleObj[attr];
            }
        } else if (key.startsWith('on')) {//onClick=>onclick
            // dom[key.toLocaleLowerCase()]=props[key];
            addEvent(dom, key.toLocaleLowerCase(), props[key]);
        } else {//在JS中定义class使用的是className,所以不要改
            dom[key] = props[key];
        }
    }
}

/**
 * 对当前组件进行DOM-DIFF
 * @param {*} parentDOM 老得父真实DOM
 * @param {*} oldRenderVdom 老得虚拟DOM
 * @param {*} newRenderVdom 新的虚拟DOM
 * @param {*} nextDom 下一个真实DOM,主要用来插入找位置用
 */
export function compareTwoVdom(parentDOM, oldRenderVdom, newRenderVdom, nextDom) {
    if (!oldRenderVdom && !newRenderVdom) {//新老虚拟DOM都为null
        return null;
    } else if (oldRenderVdom && !newRenderVdom) {//新的虚拟DOM为NULL,老得存在
        const currentDOM = findDOM(oldRenderVdom);//找到此虚拟DOM对应的真实DOM
        currentDOM && parentDOM.removeChild(currentDOM);//移除此老得真实DOM
        //调用生命周期方法
        oldRenderVdom.classInstance && oldRenderVdom.classInstance.componentWillUnmount && oldRenderVdom.classInstance.componentWillUnmount()
    } else if (!oldRenderVdom && newRenderVdom) {//新的虚拟DOM存在,老得虚拟DOM为NULL
        const newDOM = createDOM(newRenderVdom);//获取真实DOM
        if (nextDom) {
            parentDOM.insertBefore(newDOM, nextDom);
        } else {
            parentDOM.appendChild(newDOM);
        }
    } else if (oldRenderVdom && newRenderVdom && oldRenderVdom.type !== newRenderVdom.type) {//新老虚拟DOM都存在,但是类型不同
        const oldDOM = findDOM(oldRenderVdom);//老得真实DOM
        const newDOM = createDOM(newRenderVdom);//新的真实DOM
        parentDOM.replaceChild(newDOM, oldDOM);
        //调用生命周期方法
        oldRenderVdom.classInstance && oldRenderVdom.classInstance.componentWillUnmount && oldRenderVdom.classInstance.componentWillUnmount()
    } else {//新老都有,类型也一样,要进行深度DOM-DIFF
        updateElement(oldRenderVdom, newRenderVdom);
    }
}

/**
 * 深度对比两个虚拟DOM
 * @param {*} oldRenderVdom 老得虚拟DOM
 * @param {*} newRenderVdom 新的虚拟DOM
 */
function updateElement(oldRenderVdom, newRenderVdom) {
    if (oldRenderVdom.type === REACT_TEXT) {//文本
        const currentDOM = newRenderVdom.dom = oldRenderVdom.dom;//复用老得真实DOM节点
        currentDOM.textContent = newRenderVdom.props.content;//直接修改老的DOM节点的文件就可以了
    } else if (typeof oldRenderVdom.type === 'string') {//说明是一个原生组件
        const currentDOM = newRenderVdom.dom = oldRenderVdom.dom;//复用老得真实DOM
        //先更新属性
        updateProps(currentDOM, oldRenderVdom.props, newRenderVdom.props);
        //比较儿子们
        updateChildren(currentDOM, oldRenderVdom.props.children, newRenderVdom.props.children);
    } else if (typeof oldRenderVdom.type === 'function') {
        if (oldRenderVdom.type.isReactComponent) {
            updateClassComponent(oldRenderVdom, newRenderVdom);//老新都是类组件,进行类组件更新
        } else {
            updateFunctionComponent(oldRenderVdom, newRenderVdom);//新老都是函数组件,进行函数组件更新
        }
    }
}

/**
 *  如果老得虚拟DOM节点和新的虚拟DOM节点都是函数的话,走这个更新逻辑
 * @param {*} oldVdom 老得虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateFunctionComponent(oldVdom, newVdom) {
    const parentDOM = findDOM(oldVdom).parentNode;//找到老得父节点
    const { type: FunctionComponent, props } = newVdom;
    const oldRenderVdom = oldVdom.oldRenderVdom;//老得的渲染虚拟DOM
    const newRenderVdom = FunctionComponent(props);//新的渲染虚拟DOM
    compareTwoVdom(parentDOM, oldRenderVdom, newRenderVdom);//比较虚拟DOM
    newVdom.oldRenderVdom = newRenderVdom;
}

/**
 * 如果老得虚拟DOM节点和新的虚拟DOM节点都是类组件的话,走这个更新逻辑
 * @param {*} oldVdom 老得虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateClassComponent(oldVdom, newVdom) {
    const classInstance = newVdom.classInstance = oldVdom.classInstance;//复用老得类的实例
    newVdom.oldRenderVdom = oldVdom.oldRenderVdom;//上一次类组件的渲染出来的虚拟DOM
    if (classInstance.componentWillReceiveProps) {//组件将要接受到新的属性
        classInstance.componentWillReceiveProps();
    }
    //触发组件的更新,把新的属性传递过去
    classInstance.updater.emitUpdate(newVdom.props);
}
/**
 * 深度比较孩子们
 * @param {*} parentDOM 父DOM 
 * @param {*} oldChildren 老得儿子们
 * @param {*} newChildren 新的儿子们
 */
function updateChildren(parentDOM, oldChildren, newChildren) {
    //孩子可能是数组或者对象(单节点是对象)
    oldChildren = Array.isArray(oldChildren) ? oldChildren : [oldChildren];
    newChildren = Array.isArray(newChildren) ? newChildren : [newChildren];
    //获取最大的长度
    const maxLen = Math.max(oldChildren.length, newChildren.length);
    for (let i = 0; i < maxLen; i++) {
        //在儿子们里查找,找到索引大于当前索引的
        const nextDOM = oldChildren.find((item, index) => index > i && item && item.dom)
        //递归比较孩子
        compareTwoVdom(parentDOM, oldChildren[i], newChildren[i], nextDOM && nextDOM.dom);
    }
}


/**
 * 查找此虚拟DOM对象的真实DOM
 * @param {*} vdom 
 */
export function findDOM(vdom) {
    const { type } = vdom;
    let dom;
    if (typeof type === 'function') {
        dom = findDOM(vdom.oldRenderVdom)
    } else {
        dom = vdom.dom;
    }
    return dom
}

const ReactDOM = {
    render
}

export default ReactDOM;

9.高阶组件

9.1.配置

  • 安装
npm install react-app-rewired customize-cra @babel/plugin-proposal-decorators- D
  • package.json
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  }
  • config-overrides.js
const {override,addBabelPlugin}=require('customize-cra');
module.exports=override(
	addBabelPlugin([
    '@babel/plugin-proposal-decorators',{'legacy':true}
  ])
)
  • jsconfig.json
{
  'compilerOptions':{
    'experimentalDecorators':true
  }
}

9.2.按理

9.2.1.属性代理
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 15:25:38
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';
/**
 * 高阶组件 有三中应用场景
 * 1.属性代理
 */
const widthLoading = msg => OldComponent => {
  return class extends React.Component {
    show = () => {
      const div = document.createElement('div')
      div.setAttribute('id', 'load');
      div.innerHTML = `
      <p style="position:absolute;top:50%;left:50%;z-index:10;background-color:gray">${msg}</p>
      `
      document.body.appendChild(div);
    }

    hide = () => {
      document.getElementById('load').remove();
    }
    render() {
      return <OldComponent show={this.show} hide={this.hide} />
    }
  }
}

@widthLoading('正在加载中...')
class Hello extends React.Component {
  render() {
    return <div>
      <p>hello</p>
      <button onClick={this.props.show}>显示</button>
      <button onClick={this.props.hide}>隐藏</button>
    </div>
  }
}
ReactDOM.render(<Hello />, document.getElementById('root'));
9.2.2.反向继承

基于反向方向继承可以对原有组件进行扩展

  • src/index.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 17:21:34
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from './react';
import ReactDOM from './react-dom';
/**
 * 高阶组件 有三中应用场景
 * 1.属性代理
 * 2.反向继承
 */
class Button extends React.Component {
  state = { name: '张三' }
  componentWillMount() {
    console.log('button componentWillMount');
  }

  componentDidMount() {
    console.log('button componentDidMount')
  }
  render() {
    return (<button name={this.state.name} title={this.props.title}></button>)
  }
}

const wrapper = Button => {
  return class extends Button {

    state = { number: 0 }

    componentWillMount() {
      console.log('WrapperButton componentWillMount');
    }

    componentDidMount() {
      console.log('WrapperButton componentDidMount');
    }

    handlerClick = () => {
      this.setState({ number: this.state.number + 1 });
    }

    render() {
      const renderElement = super.render();
      const newProps = {
        ...renderElement.props,
        ...this.state,
        onClick: this.handlerClick
      }
      return React.cloneElement(renderElement, newProps, this.state.number);
    }
  }
}

const WrapperButton = wrapper(Button)
ReactDOM.render(<WrapperButton title='标题' />, document.getElementById('root'));
  • src/react.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:34:24
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 17:21:21
 * @Modified By: dfh
 * @FilePath: /day25-react/src/react.js
 */
import Component from './Component';
import { wrapToVdom } from './utils';
/**
 * 
 * @param {*} type 元素类型
 * @param {*} config 配置对象
 * @param {*} children 孩子或者孩子门
 */
function createElement(type, config, children) {
    let ref, key;
    if (config) {
        delete config.__source;
        delete config.__self;
        ref = config.ref;
        key = config.key;
        delete config.ref;
        delete config.key;
    }
    let props = { ...config };

    if (arguments.length > 3) {//children是一个数组
        props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
    } else {
        props.children = wrapToVdom(children);
    }
    return {
        type,
        props,
        ref,
        key
    }
}

function createRef() {
    return { current: null }
}

function createContext(initialValue) {
    Provider._value = initialValue;
    function Provider(props) {
        const { value } = props;
        if (Provider._value) {
            Object.assign(Provider._value, value)
        } else {
            Provider._value = value;
        }
        return props.children;
    }
    function Consumer(props) {
        return props.children(Provider._value);
    }
    return { Provider, Consumer };
}

+ function cloneElement(oldElement, newProps, ...newChildren) {
+   let children = oldElement.props.children;
+   //children可能是undefined,对象,数组
+   if (children) {
+       if (!Array.isArray(children)) {//是一个对象
+           children = [children]
+       }
+   } else {//undefined
+       children = [];
+   }
+   children.push(...newChildren);
+   children = children.map(wrapToVdom);
+   if (children.length === 0) {
+       children = undefined;
+   } else if (children.length === 1) {
+       children = children[0];
+   }
+   newProps.children = children;
+   const props = { ...oldElement.props, ...newProps };
+   return { ...oldElement, props };
+ }
const React = {
    createElement,
    Component,
    createRef,
    createContext,
+   cloneElement
}
export default React;

10.render props

  • render props是值一种在react组件之间使用一个值为函数的props共享代码的简单技术
  • 具有render props组件接受一个函数,该函数返回一个react元素并调用它而不是实现自己的渲染逻辑
  • render props是一个用于告知组件需要渲染什么内容的函数props

10.1.原生实现

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 19:06:45
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handeMouseMove = event => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return <div onMouseMove={this.handeMouseMove}>
      <h1>移动鼠标</h1>
      <p>当前的鼠标位置是:({this.state.x},{this.state.y})</p>
    </div>
  }
}
ReactDOM.render(<MouseTracker />, document.getElementById('root'));

10.2.render属性

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 19:17:45
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';
/**
 * render props
 * 1.render props在React组件之间使用一个值为函数的props共享代码
 * 
 */
class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handeMouseMove = event => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return <div onMouseMove={this.handeMouseMove}>
      {this.props.render(this.state)}
    </div>
  }
}
ReactDOM.render(<MouseTracker render={props => <>
  <h1>移动鼠标</h1>
  <p>当前的鼠标位置是:({props.x},{props.y})</p>
</>} />, document.getElementById('root'));

10.3.children

children是一个渲染方法

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 19:21:16
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index-render-props1.js
 */
import React from 'react';
import ReactDOM from 'react-dom';

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handeMouseMove = event => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return <div onMouseMove={this.handeMouseMove}>
      {this.props.children(this.state)}
    </div>
  }
}
ReactDOM.render(<MouseTracker >{
  props => <>
    <h1>移动鼠标</h1>
    <p>当前的鼠标位置是:({props.x},{props.y})</p>
  </>
}</MouseTracker>, document.getElementById('root'));

10.4.hoc

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 19:35:21
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';

function widthTracker(OldComponent) {
  return class MouseTracker extends React.Component {
    constructor(props) {
      super(props);
      this.state = { x: 0, y: 0 };
    }

    handeMouseMove = event => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }

    render() {
      return <div onMouseMove={this.handeMouseMove}>
        <OldComponent {...this.state} />
      </div>
    }
  }
}

function Show(props) {
  return <>
    <h1>移动鼠标</h1>
    <p>当前的鼠标位置是:({props.x},{props.y})</p>
  </>
}

const MouseTracker = widthTracker(Show);
ReactDOM.render(<MouseTracker />, document.getElementById('root'));

11.shouldComponentUpdate

  • 当一个组件的propsstate变更,React会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的DOM,当它们不相同时React会更新改DOM
  • 如果渲染的组件非常多时,可以通过覆盖生命周期方法shouldComponentUpdate来进行优化
  • shouldComponentUpdate方法会在重新渲染前触发。其默认实现是返回true。如果组件不需要更新,可以在shouldComponentUpdate中返回false来跳过整个渲染过程。其包括该组件的render调用以及之后的操作。

11.1.事例-Component

存在的问题:无论点击Number1中的button,还是Number2中的butter,所有的render都会执行

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 19:41:39
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';

/**
* 默认情况下,只要状态改变,那么所有的组件不管属性有没有变化都会更新
*/
class Counter extends React.Component {
  state = { num1: 0, num2: 0 }

  addNum1 = () => {
    this.setState({ num1: this.state.num1 + 1 })
  }

  addNum2 = () => {
    this.setState({ num2: this.state.num2 + 1 })
  }

  render() {
    console.log('render Counter')
    return <div>
      <Number1 num={this.state.num1} add={this.addNum1} />
      <Number2 num={this.state.num2} add={this.addNum2} />
    </div>
  }
}

class Number1 extends React.Component {
  render() {
    console.log('render Number1')
    return <div>
      <button onClick={this.props.add}>Number1:{this.props.num}</button>
    </div>
  }
}

class Number2 extends React.Component {
  render() {
    console.log('render Number2')
    return <div>
      <button onClick={this.props.add}>Number2:{this.props.num}</button>
    </div>
  }
}
ReactDOM.render(<Counter />, document.getElementById('root'));

11.2.事例-PuerComponent

当点击Number1中的按钮时,只走render Counterrender Number1,但点击Number2中的按钮时只走render Counterrender Number2

/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 19:46:48
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
  state = { num1: 0, num2: 0 }

  addNum1 = () => {
    this.setState({ num1: this.state.num1 + 1 })
  }

  addNum2 = () => {
    this.setState({ num2: this.state.num2 + 1 })
  }

  render() {
    console.log('render Counter')
    return <div>
      <Number1 num={this.state.num1} add={this.addNum1} />
      <Number2 num={this.state.num2} add={this.addNum2} />
    </div>
  }
}

class Number1 extends React.PureComponent {
  render() {
    console.log('render Number1')
    return <div>
      <button onClick={this.props.add}>Number1:{this.props.num}</button>
    </div>
  }
}

class Number2 extends React.PureComponent {
  render() {
    console.log('render Number2')
    return <div>
      <button onClick={this.props.add}>Number2:{this.props.num}</button>
    </div>
  }
}
ReactDOM.render(<Counter />, document.getElementById('root'));

11.3.PuerComponent实现

/*
 * @Author: dfh
 * @Date: 2021-03-01 19:49:40
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 22:28:52
 * @Modified By: dfh
 * @FilePath: /day25-react/src/PureComponent.js
 */
import Component from './Component';

class PureComponent extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        const value=!shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
        console.log(value)
        return value;
    }
}

/**
 * 用浅比较比较obj1和obj2是否相等
 * 只要内存地址一样,就认为相等,不一样就不相等
 * @param {*} obj1 
 * @param {*} obj2 
 */
function shallowEqual(obj1, obj2) {
    if (obj1 === obj2) return true;
    if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) return false;
    const key1 = Object.keys(obj1);
    const key2 = Object.keys(obj2);
    if (key1.length !== key2.length) return false;
    for (let key of key1) {
        if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) return false;
    }
    return true;
}

export default PureComponent;