React 源码系列-Component、PureComponent、function Component 分析

本文基于 React v16.8.6,源码见 github.com/lxfriday/re…html

如下是使用三种 Component 建立的一个组件react

import React, { Component, PureComponent } from 'react';
// function Component
function FuncComp() {
  return <div>FuncComp</div>
}
// Component
class Comp extends Component {
  render() {
    return (
      <div> component </div>
    );
  }
}
// Pure Component
class PureComp extends PureComponent {
  render() {
    return (
      <div> Pure Component </div>
    );
  }
}
console.log(<FuncComp />); console.log(<Comp />); console.log(<PureComp />); export default class extends Component { render() { return ( <div> <FuncComp /> <Comp /> <PureComp /> </div> ); } } 复制代码

生成元素的差别

通过 React.createElement 处理以后,三个组件的区别就是 type 不同了git

type 是刚才定义的 function 或者 classgithub

__proto__prototype 看不懂能够看下这篇文章 www.zhihu.com/question/34… js 中 __proto__prototype 的区别和关系api

Component

/** * 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.
  // ref 有好几个方式建立,字符串的不讲了,通常都是经过传入一个函数来给一个变量赋值 ref 的
  // ref={el => this.el = el} 这种方式最推荐
  // 固然还有种方式是经过 React.createRef 建立一个 ref 变量,而后这样使用
  // this.el = React.createRef()
  // ref={this.el}
  // 关于 React.createRef 就阅读 ReactCreateRef.js 文件了
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  // 若是你在组件中打印 this 的话,可能看到过 updater 这个属性
  // 有兴趣能够去看看 ReactNoopUpdateQueue 中的内容,虽然没几个 API,而且也基本没啥用,都是用来报警告的
  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 */
// 咱们在组件中调用 setState 其实就是调用到这里了
// 用法不说了,若是不清楚的把上面的注释和相应的文档看一下就行
// 一开始觉得 setState 一大堆逻辑,结果就是调用了 updater 里的方法
// 因此 updater 仍是个蛮重要的东西
Component.prototype.setState = function (partialState, callback) {
  (function () {
    if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
      {
        throw ReactError('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');
};

console.log('Component.prototype', Component.prototype);
复制代码

PureComponent

// 如下作的都是继承功能,让 PureComponent 继承自 Component
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;
}

// PureComponent 继承自 Component
var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();

console.log('pureComponentPrototype1', pureComponentPrototype);

pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
_assign(pureComponentPrototype, Component.prototype);
// 经过这个变量区别下普通的 Component
pureComponentPrototype.isPureReactComponent = true;

console.log('pureComponentPrototype2', pureComponentPrototype);
复制代码

三次 log 打印的结果:数组

函数的 prototype 属性对象上的 constructor 是不可枚举的,因此下面两句babel

pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
_assign(pureComponentPrototype, Component.prototype);
复制代码

给 PureComponent 从新指向构造函数以后,_assign 复制对象属性时, Component 构造函数不会覆盖 PureComponent 构造函数,看下面的例子就明白了。app

ComponentPureComponeny 的构造函数定义是同样的,PureComponent 继承自 Component,同时把 Component 的原型方法复制了一份,而且声明了一个下面这句dom

pureComponentPrototype.isPureReactComponent = true;
复制代码

表示是 PureReactComponent,这个标识起着很是关键的做用!!ide

React.createElement 生成元素流程

先看看为何会用到 createElement

从 babel 编译后的 js 代码能够看到,jsx 代码变成了 React.createElement(type, props, children)

createElement 进行溯源

实际是导出了 createElementWithValidation,其简化以后的代码

function createElementWithValidation(type, props, children) {
  var validType = isValidElementType(type); // 是合法的 reactElement

  var element = createElement.apply(this, arguments); // 调用 createElement 生成

  // The result can be nullish if a mock or a custom function is used.
  // TODO: Drop this when these are no longer allowed as the type argument.
  if (element == null) {
    return element;
  }
  
  // 中间一堆验证的代码 ...
  return element;
}
复制代码

createElement 简化后的代码

/** * Create and return a new ReactElement of the given type. * 根据 type 返回一个新的 ReactElement * See https://reactjs.org/docs/react-api.html#createelement */
  export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;
  // 判断是否传入配置,好比 <div className='11'></div> 中的 className 会被解析到配置中
  if (config != null) {
    // 验证 ref 和 key,只在开发环境下
    // key 和 ref 是从 props 单独拿出来的
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    // 赋值操做
    // self 呢就是为了之后正确获取 this
    // source 基原本说没啥用,内部有一些 filename, line number 这种
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    // 遍历配置,把内建的几个属性剔除后丢到 props 中
      //var RESERVED_PROPS = {
    // key: true,
    // ref: true,
    // __self: true,
    // __source: true
    // };
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  // children 只有一个,就直接赋值,是以多个参数的形式放在参数上的,则把他们都放到数组里面
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  // 判断是否有给组件设置 defaultProps,有的话判断是否有给 props 赋值,只有当值为
  // undefined 时,才会设置默认值
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  // ...一串warning,提醒不要从 props 直接访问 key 和 ref
  
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
复制代码

ReactElement

/** * Factory method to create a new React element. This no longer adheres to * the class pattern, so do not use new to call it. Also, no instanceof check * will work. Instead test $$typeof field against Symbol.for('react.element') to check * if something is a React Element. * * @param {*} type * @param {*} key * @param {string|object} ref * @param {*} self A *temporary* helper to detect places where `this` is * different from the `owner` when React.createElement is called, so that we * can warn. We want to get rid of owner and replace string `ref`s with arrow * functions, and as long as `this` and owner are the same, there will be no * change in behavior.帮助检测 this * @param {*} source An annotation object (added by a transpiler or otherwise) 包含定义的文件和行号 * indicating filename, line number, and/or other information. * @param {*} owner * @param {*} props * @internal */
// 这就是个工厂函数,帮助咱们建立 React Element 的
// 内部代码很简单,无非多了一个 $$typeof 帮助咱们标识
// 这是一个 React Element
var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };

  {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};
复制代码

依靠 ReactElement 最终生成一个元素,因此咱们看到最开始生成的元素只有 type不一样, type 指向这个组件

var element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };
复制代码

执行的差别

react-dom.development.js 中,ctor.prototype.isPureReactComponent 判断有没有这个标识,有就是 PureComponent,只会对 props 和 state 进行浅比较

function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) {
  var instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext);
    stopPhaseTimer();

    {
      !(shouldUpdate !== undefined) ? warningWithoutStack$1(false, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentName(ctor) || 'Component') : void 0;
    }

    return shouldUpdate;
  }

  // 重点看这里
  // PureComponent.prototype.isPureReactComponent === true
  // PureComponent 只会对 props 和 state 进行浅比较,对对象只作引用比对
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
  }

  return true;
}
复制代码

shallowEqual 作浅比较

PureComponent 不起做用场景

import React, { PureComponent } from 'react';
class PureComp extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      userInfo: {
        name: 'lxfriday',
        age: 24,
      },
      school: 'hzzz',
    };
  }
  handleChangeUserInfo = () => {
    const {
      userInfo,
    } = this.state;
    userInfo.sex = 'male';
    console.log('userInfo', userInfo);
    this.setState({ userInfo: userInfo });
  };
  handleChangeSchool = () => {
    this.setState({ school: 'zzzh' });
  };
  render() {
    const {
      userInfo,
      school,
    } = this.state;
    return (
      <div> <button onClick={this.handleChangeUserInfo}>change userInfo</button> <button onClick={this.handleChangeSchool}>change school</button> <br /> {JSON.stringify(userInfo)} <br /> {school} </div>
    );
  }
}
console.log(<PureComp />); export default PureComp; 复制代码

点击 change UserInfo 按钮,页面没有任何变化,可是 log 打印出了值,school 可正常变化

点击前

点击后

PureComponent 变成 ComponentuserInfo 可正常变化。

这就是 PureComponent 浅比对的特色,不会管检测对象深层次是否相同,从性能上能够得到巨大提高,固然前提是你须要知道 setState 设置的属性确实只须要浅比对就能够实现预设功能!!!若是你在嵌套的对象内更改了属性,结果可能就会超出预期了!!!


广告时间

欢迎关注,每日进步!!!

相关文章
相关标签/搜索