本文基於 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
/** * 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 繼承自 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
Component
和 PureComponeny
的構造函數定義是同樣的,PureComponent
繼承自 Component
,同時把 Component
的原型方法複製了一份,而且聲明瞭一個下面這句dom
pureComponentPrototype.isPureReactComponent = true;
複製代碼
表示是 PureReactComponent
,這個標識起着很是關鍵的做用!!ide
先看看爲何會用到 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
作淺比較
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
變成 Component
,userInfo
可正常變化。
這就是 PureComponent
淺比對的特色,不會管檢測對象深層次是否相同,從性能上能夠得到巨大提高,固然前提是你須要知道 setState
設置的屬性確實只須要淺比對就能夠實現預設功能!!!若是你在嵌套的對象內更改了屬性,結果可能就會超出預期了!!!
廣告時間
歡迎關注,每日進步!!!