资讯专栏INFORMATION COLUMN

React源码解析之React.Component()/PureComponent()

Cristalven / 1523人阅读

摘要:只涉及了,其他均没有自己实现。这种组件的复用性是最强的。所以会新建,只继承的原型,不包括,以此来节省内存。

一、React.Component()

GitHub:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactBaseClasses.js

用法:

class A extends React.Component {
  constructor(props){
    super(props)
    this.state={ }
  } 

  componentWillMount(){ }
  
  render() {
    return { }
  }  
}

源码:

/**
 * 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;
  //我在工作中没用到context,可以参考下这个:
  //https://www.cnblogs.com/mengff/p/9511419.html
  //是React封装的全局变量API
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  //如果在组件中用了 ref="stringa" 的话,用另一个obj赋值
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  //虽然给updater赋了默认值,但真正的updater是在renderer中注册的
  this.updater = updater || ReactNoopUpdateQueue;
}
//原型上赋了一个flag
Component.prototype.isReactComponent = {};

/** 使用setState来改变Component内部的变量

 * Sets a subset of the state. Always use this to mutate
 * state. You should treat `this.state` as immutable.

 * this.state并不是立即更新的,所以在调用this.setState后可能 不能 拿到新值

 * There is no guarantee that `this.state` will be immediately updated, so
 * accessing `this.state` after calling this method may return the old value.
 *
 * 不能保证this.state是同步的(它也不是异步的),使用回调获取最新值
 *
 * 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内部变量的API,
// 也是开发中非常常用且重要的API

// https://www.jianshu.com/p/7ab07f8c954c
// https://www.jianshu.com/p/c19e259870a5

//partialState:要更新的state,可以是Object/Function
//callback: setState({xxx},callback)
Component.prototype.setState = function(partialState, callback) {
  // 判断setState中的partialState是否符合条件,
  // 如果不符合则抛出Error
  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.",
  );
  //重要!state的更新机制
  //在react-dom中实现,不在react中实现
  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.
 *
 * 在Component的深层次改变但未调用setState时,使用该方法
 *
 * 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.
 *
 * forceUpdate不调用shouldComponentUpdate方法,
 * 但会调用componentWillUpdate和componentDidUpdate方法
 *
 * This will not invoke `shouldComponentUpdate`, but it will invoke
 * `componentWillUpdate` and `componentDidUpdate`.
 *
 * @param {?function} callback Called after update is complete.
 * @final
 * @protected
 */
//强制Component更新一次,无论props/state是否更新
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};

解析:
(1)Component()本质是一个类:

class Component {
  constructor(props, context, updater){
    this.props = props
    this.context = context
    this.refs = emptyObject
    this.updater = updater || ReactNoopUpdateQueue
  }
}

(2)setState()是 Component 原型上的方法,其本质是调用ReactNoopUpdateQueue.js中的enqueueSetState()方法,之后的文章会分析enqueueSetState()的,不要急

(3)forceUpdate()(2)

(4)我以为React.Component()里面实现componentWillMount()render()等内部方法,其实并没有

React.Component()只涉及了props /context /refs /updater /isReactComponent /setState /forceUpdate ,其他均没有自己实现。

二、PureComponent

GitHub:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react/src/ReactBaseClasses.js

什么是 PureComponent:
可以看下这篇文章的第一点:小知识11点(2018.9.4 ) :

复用性强的组件:如果一个组件的渲染只依赖于外界传进去的 props 和自己的 state,而并不依赖于其他的外界的任何数据,也就是说像纯函数一样,给它什么,它就吐出(渲染)什么出来。这种组件的复用性是最强的。即 Pure Component 或称 Dumb Component。

用法:

class A extends React.PureComponent { 
  //同React.Component() 
}

源码:

function ComponentDummy() {}

//ComponentDummy的原型 继承 Component的原型
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;
}

//PureComponent是继承自Component的,下面三行就是在继承Component

//将Component的方法拷贝到pureComponentPrototype上
// 用ComponentDummy的原因是为了不直接实例化一个Component实例,可以减少一些内存使用
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());

//PureComponent.prototype.constructor = PureComponent
pureComponentPrototype.constructor = PureComponent;

// Avoid an extra prototype jump for these methods.
//避免多一次原型链查找,因为上面两句已经让PureComponent继承了Component
//下面多写了一句Object.assign(),是为了避免多一次原型链查找

// Object.assign是浅拷贝,
// 将Component.prototype上的方法都复制到PureComponent.prototype上
// 也就是pureComponent的原型上
// 详细请参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Object.assign(pureComponentPrototype, Component.prototype);

// 唯一的区别就是在原型上添加了isPureReactComponent属性去表示该Component是PureComponent
pureComponentPrototype.isPureReactComponent = true;

export {Component, PureComponent};

解析:
(1)重点看最后三行做了什么:(减少内存消耗,减少原型链查找次数)

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy())
新建了空方法ComponentDummy ,并继承Component的原型;
PureComponent.prototype等于ComponentDummy的实例

这样做的目的是:

如果让PureComponent.prototype直接等于Component的实例对象的话(继承原型),会多继承Componentconstructor,但是PureComponent已经有自己的constructor了,这样就会多消耗一些内存。

所以会新建ComponentDummy,只继承Component的原型,不包括constructor,以此来节省内存。

pureComponentPrototype.constructor = PureComponent

原型的constructor等于自身,覆盖掉Component.prototypeconstructor(Component)

①、② 就是让PureComponent继承Component,那么为什么还要多写一句Object.assign(pureComponentPrototype, Component.prototype)呢?

PureComponentprototype浅拷贝Componentprototype的所有属性

不写 ③ 的话:

pureComponentPrototype.__proto__=== ComponentDummy.prototype //true
//也就是
PureComponent.prototype.__proto__=== Component.prototype //true

这样就多了一层隐式原型的查找,为了减少一次原型链查找,所以写了

Object.assign(pureComponentPrototype, Component.prototype)

这样的话:
Component.prototype中的方法在PureComponent.prototype中都有,无需再从__proto__上查找了。

(2)pureComponentPrototype.isPureReactComponent = true

ReactFiberClassComponent.js中,有对isPureReactComponent的判断:

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

注意:(重要)

(1)整个React中判断 Component类 是否需要更新,只有两个地方:
一 是看有没有shouldComponentUpdate方法

二 就是ReactFiberClassComponent.js中的checkShouldComponentUpdate()中对PureComponent的判断

(2)PureComponentComponent唯一的区别:
PureComponent是自带了一个简单的shouldComponentUpdate来优化更新机制的。

(完)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105711.html

相关文章

  • 基于React版本16.4的源码解析(一)

    摘要:本次分析的源码采用的是的版本核心接口提供了处理的工具集我们先来看看做了什么事情即当为空时,返回不为时调用,最终返回一个数组这里说一下,可以通过传入的对所有子组件进行操作,具体使用方法看下图参数通过配合的例子把父组件的赋值给每个子组件我们先不 本次分析的源码采用的是16.4.1的版本 核心接口 showImg(https://segmentfault.com/img/bVbeT9f?w=...

    joywek 评论0 收藏0
  • React 源码漂流(一) 起航

    摘要:在前端开发过程中,源码解读是必不可少的一个环节,我们直接进入主题,注意当前版本号。注意包文件仅仅是的必要的功能性的定义,它必须要结合一起使用下是,原生环境下是。 在前端开发过程中,源码解读是必不可少的一个环节,我们直接进入主题,注意当前 React 版本号 16.8.6。 注意:react 包文件仅仅是 React components 的必要的、功能性的定义,它必须要结合 React...

    Mr_zhang 评论0 收藏0
  • React源码解析React.createRef()/forwardRef()

    摘要:一作用获取目标的实例使用源码可修改的不可变的对象没见过这种写法初始化对象,属性初始值为解析源码比较简单,就是返回了带有属性的二作用从父组件中获取子组件是的实例使用是没有实例的,因为它是,所以没有,所以不能通过来拿到实例将的传给子组件,并绑定 showImg(https://segmentfault.com/img/remote/1460000019877636); 一、React.cr...

    aisuhua 评论0 收藏0
  • 谈一谈创建React Component的几种方式

    摘要:用这种方式创建组件时,并没有对内部的函数,进行绑定,所以如果你想让函数在回调中保持正确的,就要手动对需要的函数进行绑定,如上面的,在构造函数中对进行了绑定。 当我们谈起React的时候,多半会将注意力集中在组件之上,思考如何将页面划分成一个个组件,以及如何编写可复用的组件。但对于接触React不久,还没有真正用它做一个完整项目的人来说,理解如何创建一个组件也并不那么简单。在最开始的时候...

    mylxsw 评论0 收藏0
  • 如何优化你的超大型React应用 【原创精读】

    摘要:往往纯的单页面应用一般不会太复杂,所以这里不引入和等等,在后面复杂的跨平台应用中我会将那些技术一拥而上。构建极度复杂,超大数据的应用。 showImg(https://segmentfault.com/img/bVbvphv?w=1328&h=768); React为了大型应用而生,Electron和React-native赋予了它构建移动端跨平台App和桌面应用的能力,Taro则赋...

    cfanr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<