React API:react API和各个API的作用
本节不用关心这些是如何映射到dom中,本节先介绍每一项api含义
- JSX=>JS
- createElement
- Component
- Ref
- createContext
- ConcurrentMode
- Suspense
- Hooks
1.JSX=>JS (props&函数组件&类组件&组件为什么要一定大写开头)
在线试一试: www.babeljs.cn/repl
jsx→js
jsx
<div id="dxx" key="key">
<span id="xyl">1</span>
<span class="xka">2</span>
</div>
js
"use strict";
React.createElement("div", {
id: "dxx",
key: "key"
}, React.createElement("span", {
id: "xyl"
}, "1"), React.createElement("span", {
class: "xka"
}, "2"));
大写→变量
jsx
function Comp(){
return <a>123</a>
}
<Comp id="dxx" key="key">
<span id="xyl">1</span>
<span class="xka">2</span>
</Comp>
js
"use strict";
function Comp() {
return React.createElement("a", null, "123");
}
React.createElement(Comp, {
id: "dxx",
key: "key"
}, React.createElement("span", {
id: "xyl"
}, "1"), React.createElement("span", {
class: "xka"
}, "2"));
小写→翻译成字符串
jsx
function comp(){
return <a>123</a>
}
<comp id="dxx" key="key">
<span id="xyl">1</span>
<span class="xka">2</span>
</comp>
js
"use strict";
function comp() {
return React.createElement("a", null, "123");
}
React.createElement("comp", {
id: "dxx",
key: "key"
}, React.createElement("span", {
id: "xyl"
}, "1"), React.createElement("span", {
class: "xka"
}, "2"));
2.React.createElement() & ReactElement & cloneElement & createFactory & isValidElement解析
React.createElement()
首先我们从github克隆下来react的源码库,我们先来分析下react源码库的文件布局
react工程根目录下有packages文件夹,其间放置的是react的各个包,我们暂时把着力点放于react目录下。内部是react源码实现。
抛出去一些非必要的检测,和warn代码,核心的react代码其实只有几百行。react源码本身并不复杂,负责渲染的react-dom才是最复杂的。
createElement方法位于ReactElement.js文件内,实现如下:
/**
* dxx 1.2-02 四》》React.createElement() 解析
* @param {*} type 节点类型 (原生节点:字符串,自己声明主键:classComponent/functionalComponent,react原生组件:就是个标志Fragment、StrictMode、Suspense)
* @param {*} config 所有写在html标签上的 attr属性 都会变成key value的形式存到这个里面,我们要从这里面筛选出真的是props的内容以及key、ref这些属性
* @param {*} children 标签中间放置的内容
*/
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;
//hasValidRef和hasValidKey方法用来校验config中是否存在ref和key属性,有的话就分别赋值给key和ref变量
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
//将config.__self和config.__source分别赋值给self和source变量,如果不存在则为null
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
//并通过一个for in循环,把config中的属性添加的props对象中,添加的时候会过滤掉key、ref、__self、__source这几个值
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)//判断是不是内键的props
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
//分别对组件的children和defaultProps进行处理。
const childrenLength = arguments.length - 2;//arguments.length来获取参数个数,arguments.length-2是为了排除掉type和config这两个属性
//React.createElement会把第2个参数之后的所有参数都看做是子元素,并最终赋值给props.children属性
//因此React.createElement方法也支持React.createElement(type,config,child,child,...child)这样的方法调用
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;//props中的children属性,在这一步会被覆盖掉
}
// Resolve default props
//createElement方法中对于defaultProps的处理
//如果存在defaultProps,那么遍历他并判断props中是否存在该属性,如果props中该属性的值是undefined,就把defaultProps中的值赋给props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
//判断如果在开发模式下就会调用Object.defineProperty方法去定义props对象下的key和ref属性
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
这里面有一些开发环境下检测,和外部调用方法,可能会使阅读者精力分散,我们来稍微改动精简下代码,使功能一致,同时更好阅读:
export function createElement(type, config, ...children) {
const {ref = null, key = null} = config || {};
const {current} = ReactCurrentOwner;
const {defaultProps} = type || {};
const props = assignProps(config, defaultProps, children);
return new ReactElement({
type,
key: '' + key,
ref,
current,
props,
});
}
经过精简和简化后,createElement仅有30行代码。我们来逐行解析下。
/**
*
* @param type {string | function | object}
* 如果type是字符串,那就是原生dom元素,比如div
* 如果是function或者是Component的子类 则是React组件
* object 会是一些特殊的type 比如fragment
* @param config {object}
* props 和key 还有ref 其实都是在config里了
* @param children
* 就是由其他嵌套createElement方法返回的ReactElement实例
* @returns {ReactElement}
*
*/
export function createElement(type, config, ...children) {
// 给config设置一个空对象的默认值
// ref和key 默认为null
const {ref = null, key = null} = config || {};
// ReactCurrentOwner负责管理当前渲染的组件和节点
const {current} = ReactCurrentOwner;
// 如果是函数组件和类组件 是可以有defaultProps的
// 比如
// function A({age}) {return <div>{age}</div>}
// A.defaultProps = { age:123 }
const {defaultProps} = type || {};
// 把defaultProps和props 合并一下
const props = assignProps(config, defaultProps, children);
// 返回了一个ReactElement实例
return new ReactElement({
type,
key: '' + key,
ref,
current,
props,
});
}
ref和key不用多说,大家都知道是干啥的。之前我在想,key明明传的是数字,为啥最后成了字符串,症结就在上面的ReactELement构造函数传参的key那里了,key:''+key。
assignProps是抽象了一个方法,合并defaultProps和传入props的方法,提供代码,其实在cloneElement方法里,也有一段类似代码,但是react并没有抽象出来,相对来说,会有代码冗余,暂且提炼出来。
重点在new ReactElement()。
react的代码里,ReactElement是个工厂函数,返回一个对象。但是我个人觉得比较奇怪。
第一、工厂函数生成实例,这个工厂函数不该大写开头。 第二、使用构造函数或者类来声明ReactElement难道不是一个更好,更符合语义的选择? 在这里,为了便于理解,把ReactElement从工厂函数,改变成了一个类,createElement返回的就是一个ReactElement类的实例。
ReactElement
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
/**
* 用来标识我们的Element是什么类型的,我们在写jsx代码时候,所有的节点都是通过ReactElement创建的,
* 那么他的$$typeof就永远都是REACT_ELEMENT_TYPE,这个要记住,因为后续的源码解析中会经常被用到
* 那是不是所有的节点都是REACT_ELEMENT_TYPE?
* 大部分情况是的,但是也是会有一些特殊情况的,这些特殊情况会和平台有关,eg,在reactDom里面有一个api叫React.createTotal返回的对象,他的type就是totalType不是REACT_ELEMENT_TYPE
*/
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
//记录节点类型:class function
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if (__DEV__) {
// 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;
};
2、3的重点在于,在react中,jsx标签的本质就是ReactElement,createElement会对组件或者dom的type和props经过一层封装处理,最后返回了ReactElement的实例。
总结
createElement可谓是React中最重要的API了,他是用来创建ReactElement的,但是很多同学却从没见过也没用过,这是为啥呢?因为你用了JSX,JSX并不是标准的js,所以要经过编译才能变成可运行的js,而编译之后,createElement就出现了:
// jsx
<div id="app">content</div>
// js
React.createElement('div', { id: 'app' }, 'content')
cloneElement就很明显了,是用来克隆一个ReactElement的
createFactory是用来创建专门用来创建某一类ReactElement的工厂的,
export function createFactory(type) {
const factory = createElement.bind(null, type);
factory.type = type;
return factory;
}
他其实就是绑定了第一个参数的createElement,一般我们用JSX进行编程的时候不会用到这个API
isValidElement顾名思义就是用来验证是否是一个ReactElement的,基本也用不太到
3.Component vs PureComponent:组件
最重要的概念组件:依托于react上的表现React.Component,我们写的组件更多是继承与React.Component
没看源码前觉得:Component这个base class给我们提供了各式各样的功能,帮助我们去运行render function,然后最终把我们写的DOM标签、子组件之类的渲染出来,渲染到浏览器里面,变成想要的页面
看了源码之后发现:不止有Component还有PureComponent:区别就是提供了我们简便的shouldComponentUpdate的一个实现,保证我们组件在props没有任何变化的情况下,能够减少不必要的更新
图一
图二
Component
Component function
/**
* Base class helpers for the updating state of a component.
* dxx 1.Component是一个function,类的声明的一个方式,接收三个参数
* @props 组件内部使用
* @context 组件内部使用
* @updater
*/
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
//使用过stringRef:最终会把我们想要获取的那个实例挂载ref上面
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState
/**
* @param {object|function} partialState:dxx2.我们要更新的state可以是对象或者方法
* @param {?function} callback Called after state is updated.
* @final
* @protected
*/
Component.prototype.setState = function(partialState, callback) {
/**
* dxx 3.这一段代码都是在判断我们这个对象是不是object||function||null
* 否则就给个提醒
*/
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.',
);
/**
* dxx 4.这里才是重点 调用了我们初始化时候传入的update对象,
* enqueueSetState方法是在reactDom里面去实现的,和react没有什么关系,因为不同平台更新state走的渲染流程是不一样的,作为参数是为了区分不同平台
*/
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate:强制ReactComponent去更新一遍
/**
* @param {?function} callback Called after update is complete.
* @final
* @protected
*/
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
PureComponent:继承于Component
}
/**
* dxx 6ComponentDummy去实现了一个类似于简单的继承的方式
*/
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
/**
* Convenience component with default shallow equality check for sCU.
* dxx 5.继承于Component
* 参数和Component一致
*/
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;
}
// 6ComponentDummy去实现了一个类似于简单的继承的方式
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
//把Component.prototype属性copy到pureComponentPrototype上面
Object.assign(pureComponentPrototype, Component.prototype);
//7.唯一的区别:在isPureReactComponent通过这个属性标识是不是PureReactComponent
pureComponentPrototype.isPureReactComponent = true;
总结
这两个类基本相同,唯一的区别是PureComponent的原型上多了一个标识
组件就是一个函数或者一个 Class(当然 Class 也是 function),它根据输入参数,并最终返回一个 React Element,而不需要我们直接手写无聊的 React Element。
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
这是检查组件是否需要更新的一个判断,ctor就是你声明的继承自Component or PureComponent的类,他会判断你是否继承自PureComponent,如果是的话就shallowEqual比较state和props。
顺便说一下:React中对比一个ClassComponent是否需要更新,只有两个地方。一是看有没有shouldComponentUpdate方法,二就是这里的PureComponent判断
4.createRef & ref
对节点进行实例操作,获取子节点实例
三种使用ref实例:stringRef(废弃)、function、createRef
新的ref用法,React即将抛弃
源码位置ReactContext.js
class App extends React.Component{
constructor() {
this.ref = React.createRef()
}
render() {
return <div ref={this.ref} />
// or
return <div ref={(node) => this.funRef = node} />
}
}
Ref在更新过程中发挥着什么作用,后续讲解
5.forwardRef :实现ref的传递
ref获取某个节点实例,但是组件是function component的话没有this实例,通过ref传进去就会报错,开源组件,使用人不知道是什么组件
源码地址:forwardRef.js
forwardRef是用来解决HOC组件传递ref的问题的,所谓HOC就是Higher Order Component,比如使用redux的时候,我们用connect来给组件绑定需要的state,这其中其实就是给我们的组件在外部包了一层组件,然后通过...props的方式把外部的props传入到实际组件。forwardRef的使用方法如下:
const TargetComponent = React.forwardRef((props, ref) => (
<TargetComponent ref={ref} />
))
这也是为什么要提供createRef作为新的ref使用方法的原因,如果用string ref就没法当作参数传递了。
这里只是简单说一下使用方法,后面讲ref的时候会详细分析。
6.Context :跨多层组件进行信息更新
childContextType 17版本被废弃
对下层所有组件影响太大,导致下层所有组件即便是没有任何更新的情况下,他每一次更新的过程当中仍然要进行完整的渲染,所以对性能的损耗会非常大
createContext
源码位置:ReactContext.js