React16源码: Component与PureComponent源码实现

概述

  • Component 就是组件, 这个概念依托于最直观的在react上的一个表现,那就是 React.Component
  • 我们写的组件大都是继承于 React.Component 这个baseClass 而写的类
  • 这个类代表着我们使用 react 去实现的一个组件
  • 那么在react当中不仅仅只有 Component 这一个baseClass,还有另外一个叫 PureComponent
  • PureComponentComponent 唯一区别
    • 提供了简便的 shouldComponentUpdate 的一个实现
    • 保证组件在 props 没有任何变化的情况下减少不必要的更新

源码分析

1 ) API 位置定位

  • 在入口文件 React.js里面,可看到

    js 复制代码
    import {Component, PureComponent} from './ReactBaseClasses';
  • 之后,我们定位到 ReactBaseClasses.js 里面

ReactBaseClasses.js 源码

js 复制代码
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import invariant from 'shared/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';

import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';

const emptyObject = {};
if (__DEV__) {
  Object.freeze(emptyObject);
}

/**
 * Base class helpers for the updating state of a component.
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

/**
 * Sets a subset of the state. Always use this to mutate
 * state. You should treat `this.state` as immutable.
 *
 * There is no guarantee that `this.state` will be immediately updated, so
 * accessing `this.state` after calling this method may return the old value.
 *
 * There is no guarantee that calls to `setState` will run synchronously,
 * as they may eventually be batched together.  You can provide an optional
 * callback that will be executed when the call to setState is actually
 * completed.
 *
 * When a function is provided to setState, it will be called at some point in
 * the future (not synchronously). It will be called with the up to date
 * component arguments (state, props, context). These values can be different
 * from this.* because your function may be called after receiveProps but before
 * shouldComponentUpdate, and this new state, props, and context will not yet be
 * assigned to this.
 *
 * @param {object|function} partialState Next partial state or function to
 *        produce next partial state to be merged with current state.
 * @param {?function} callback Called after state is updated.
 * @final
 * @protected
 */
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

/**
 * Forces an update. This should only be invoked when it is known with
 * certainty that we are **not** in a DOM transaction.
 *
 * You may want to call this when you know that some deeper aspect of the
 * component's state has changed but `setState` was not called.
 *
 * This will not invoke `shouldComponentUpdate`, but it will invoke
 * `componentWillUpdate` and `componentDidUpdate`.
 *
 * @param {?function} callback Called after update is complete.
 * @final
 * @protected
 */
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

/**
 * Deprecated APIs. These APIs used to exist on classic React classes but since
 * we would like to deprecate them, we're not going to move them over to this
 * modern base class. Instead, we define a getter that warns if it's accessed.
 */
if (__DEV__) {
  const deprecatedAPIs = {
    isMounted: [
      'isMounted',
      'Instead, make sure to clean up subscriptions and pending requests in ' +
        'componentWillUnmount to prevent memory leaks.',
    ],
    replaceState: [
      'replaceState',
      'Refactor your code to use setState instead (see ' +
        'https://github.com/facebook/react/issues/3236).',
    ],
  };
  const defineDeprecationWarning = function(methodName, info) {
    Object.defineProperty(Component.prototype, methodName, {
      get: function() {
        lowPriorityWarning(
          false,
          '%s(...) is deprecated in plain JavaScript React classes. %s',
          info[0],
          info[1],
        );
        return undefined;
      },
    });
  };
  for (const fnName in deprecatedAPIs) {
    if (deprecatedAPIs.hasOwnProperty(fnName)) {
      defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
    }
  }
}

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

export {Component, PureComponent};

2 )源码分析

2.1 Component

  • 从上面看到,Component 是一个function,它是一个使用function去进行类的声明的一个方式

  • 它接收三个参数,一个是 props,一个是 context,还有一个是 updater

  • 关于 props 和 context 可以发现, 在这里面进行赋值

    • this.props = props
    • this.context = context
    • 就是我们在组件内部去使用的时候,可以直接去用
  • 然后再声明了一个 refs,它是一个 emptyObject

    • 参考 stringRef, 最终会把获取的节点的实例挂载在 this.refs 上面
    • 就是我们 stringRef 使用的 key 挂载在上面
  • 关于 updater 出场率并不高,先跳过

  • 往下看,找到 Component.prototype.setState

    js 复制代码
    Component.prototype.setState = function(partialState, callback) {
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    • 这个就是我们使用的最多的一个API, 它是用来更新我们组件的状态
    • 它接收的两个参数
      * 一个是 partialState, 就是我们要更新的一个新的state,可以是一个对象,也可以是一个方法
      * 第二个是 callback, 那么callback就是在我们state真正的更新完之后才会执行这个callback
    • 这个方法里面第一段代码是一个提醒
      • 就是它判断我们的对象是不是object或者是function, 或者是不等于null
      • 如果都不满足,它就会给出一个错误提醒
        * 这个其实不太重要代码
    • 下面一段才是重点 this.updater.enqueueSetState
      • 调用 setState,其实在 Component 对象里面, 什么事情都没有做
      • 它只是调用了初始化Component时, 传入的这个update对象上面的一个 enqueueSetState 方法
      • 这个方法是在 react-dom 里面去实现的,跟react的代码是没有任何关系的
      • 这样做的原因是,不同的平台,比如说react-dom和react-native
      • 它们用的react的核心是一模一样的,也就是说,Component 这个API是一模一样的
      • 但是,具体的涉及到更新state之后如何进行渲染,而这个渲染的流程在跟平台有关的
      • 所以这里是一种类似于适配器或外观模式的一种设计模式,我更倾向于它是一个适配器
      • react-dom平台跟react-native平台,它们的实现渲染的方式肯定是不一样的
      • updater 它作为一个参数,让不同的平台传参进来,定制它自己的一个实现方式
      • 所以,这就是为什么要封装传参
  • 接下来,进入到 Component.prototype.forceUpdate

    js 复制代码
    Component.prototype.forceUpdate = function(callback) {
      this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
    };
    • 这个跟 setState 其实是一样的,它是调用了 this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); 这个方法
    • 这个API不是很常用,它是强制 react Componet 更新一遍,即便你的state没有进行更新
  • 接下来,在 DEV 环境的判断下,有两个即将被废弃的API

    • 一个是 isMounted
    • 另外一个是 replaceState
  • 到这里为止,可以看到 Component 的定义已经结束了, 它只有这么一点东西

  • 这个 Component 的定义,只是用来帮助我们去承载一些信息的

  • 没有任何的其他含义,也没有任何关于生命周期相关的一些方法, 就是这么简洁

2.2 PureComponent

  • 其实可以认为它是继承于 Component

  • 其实也没多少可以继承的东西,它们接收的东西也是一模一样的

    js 复制代码
    /**
     * Convenience component with default shallow equality check for sCU.
     */
    function PureComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
    pureComponentPrototype.constructor = PureComponent;
    // Avoid an extra prototype jump for these methods.
    Object.assign(pureComponentPrototype, Component.prototype);
    pureComponentPrototype.isPureReactComponent = true;
    
    export {Component, PureComponent};
    • 使用这个 ComponentDummy 实现了一个类似于简单的继承的方式
    • 它 new了一个 ComponentDummy,它是一个空的类
    • 它的 constructor 指向 PureComponent 自己, 这其实就是一个实继继承的过程
    • 之后把 Componen.prototype 上面的属性拷贝到 pureComponentPrototype 上面
    • 唯一的一个区别是 pureComponentPrototype.isPureReactComponent = true;
    • 通过这个属性来标识,继承之这个类的组件,它是一个 PureComponent
    • 在后续更新的过程中,react-dom 主动的去判断它是不是一个 PureComponent
    • 然后去根据 props 是否更新来判断这个组件是否需要更新
相关推荐
放逐者-保持本心,方可放逐14 天前
react 框架应用+总结+参考
前端·前端框架·react
白泽来了14 天前
我开源了一个短视频应用(Go+React)|DouTok2.0 项目介绍
微服务·开源·go·react
星辰大海141215 天前
react 基础学习笔记
前端·javascript·笔记·学习·react·1024程序员节
canonical-entropy16 天前
从React Hooks看React的本质
前端·前端框架·react·hooks·1024程序员节
不知名靓仔16 天前
React常用前端框架合集
前端框架·react
廖秋林17 天前
Vite React 项目绝对路径配置
javascript·typescript·react
长河18 天前
React18-useEffect函数
前端·react·1024程序员节
Casta-mere24 天前
【Next.js 项目实战系列】08-数据处理
typescript·react·next.js·issue tracker
任风雨1 个月前
React1-基础概念
前端·react
职教育人1 个月前
前端三大框架对比与选择
前端·vue.js·react·angular