react组件
# 01. Component
packages/react/src/ReactBaseClasses.js
// 用于更新组件状态的基类帮助程序
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; // 如果一个组件有字符串引用,后面指定一个不同的对象。
this.updater = updater || ReactNoopUpdateQueue; // updater 对象上保存着更新租金的方法
}
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
- Component 底层 React 的处理逻辑是: 类组件执行构造函数过程中会在实例上绑定 props 和 context ,初始化置空 refs 属性,原型链上绑定setState、forceUpdate 方法。对于 updater,React 在实例化类组件之后会单独绑定 update 对象.
所以说 为什么调用 super(props),由此而来。
constructor(){
super()
console.log(this.props) // 打印 undefined 为什么?
}
绑定 props 是在父类 Component 构造函数中,执行 super 等于执行 Component 函数,此时 props 没有作为第一个参数传给 super() ,在 Component 中就会找不到 props 参数,从而变成 undefined。
- 调用super的原因:在ES6中,在子类的constructor中必须先调用super才能引用this
- super(props)的目的:在constructor中可以使用this.props
如何被实例化?源码如下:
packages/react-reconciler/src/ReactFiberClassComponent.new.js
function constructClassInstance (workInProgress, ctor, props) {
...
let instance = new ctor(props, context);
adoptClassInstance(workInProgress, instance);
...
}
function adoptClassInstance(workInProgress, instance) {
instance.updater = classComponentUpdater;
}
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
// setState 触发
}
enqueueReplaceState(inst, payload, callback) {
// replaceState 触发
update.tag = ReplaceState;
}
enqueueForceUpdate(inst, payload, callback) {
// forceUpdate 触发
update.tag = ForceUpdate;
}
}
enqueueSetState/enqueueReplaceState/enqueueForceUpdate 实现方式基本一致
- 更新过期时间
- 创建Update对象
- 为update对象绑定一些属性,比如 tag 、callback
- 创建的update对象入队 (enqueueUpdate)
- 进入调度过程
# 02. PureComponent
PureComponent 和Component用法差不多,唯一不同的是,纯组件PureComponent会进行浅比较,props 和state是否相同是否重新渲染组件。一般用于于性能优化,减少render次数。如果对象包含复杂的数据结构(比如对象和数组),会浅比较,如果深层次的改变,是无法作出判断的,React.PureComponent 认为没有变化,而没有渲染试图。我们可以借助于immetable.js,提高对象的比较性能,用 immetable.js 配合 shouldComponentUpdate 或者 react.memo来使用, 通过immetable.js里面提供的 is 方法来判断,前后对象数据类型是否发生变化
import React, { PureComponent } from "react";
class Index extends PureComponent {
constructor(props) {
super(props);
this.state = {
user: {
name: "wsh",
age: 18,
}
};
this.handerClick = this.handerClick.bind(this);
}
handerClick = () => {
const { user } = this.state;
user.age++;
// this.setState({ user: {...user} });
this.setState({ user }); // 不更新的原因:两次user对象,都指向同一个user,没有发生改变,所以不更新视图
};
render() {
const { user } = this.state;
return (
<div>
<div>
name: {user.name}
age: {user.age}
</div>
<button onClick={this.handerClick}>点击</button>
</div>
);
}
}
export default Index;
# 03. memo
React.memo 与PureComponent作用类似,可以用作性能优化.
React.memo 是高阶组件,函数组件和类组件都可以使用,PureComponent是 只能用作类组件
React.memo只能对props的情况确定是否渲染,而PureComponent是针对props和state
React.memo([组件本身],param),第一个参数是原始组件本身,第二个参数是通过对比props是否相等来对比是否渲染(pre,next)=> { return true or false } 与shouldComponentUpdate 相反,返回true,说明props没有发生变化,不渲染,返回false 选择组件
React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。 shouldComponentUpdate: 返回 true 组件渲染 , 返回 false 组件不渲染。
React.memo在一定程度上,等价于在组件外部使用shouldComponentUpdate,用于拦截新老props,确定组件是否更新。
import React, { Component, memo } from "react";
function TextMemo(props) {
console.log("子组件渲染");
if (props) {
return <div>hello react</div>;
}
}
const controllIsRender = (pre, next) => {
if (pre.age === next.age) {
return true;
} else if (next.age > 5) {
return true;
} else {
return false;
}
};
const NewMemo = memo(TextMemo, controllIsRender);
class Index extends Component {
constructor(props) {
super(props);
this.state = {
count: 1,
age: 1,
};
}
render() {
const { count, age } = this.state;
return (
<div>
<div>
改变count:当前值 {count}
<button onClick={() => this.setState({ count: count + 1 })}>count++</button>
<button onClick={() => this.setState({ count: count - 1 })}>count--</button>
</div>
<div>
改变age:当前后 {age}
<button onClick={() => this.setState({ age: age + 1 })}>age++</button>
<button onClick={() => this.setState({ age: age - 1 })}>age--</button>
</div>
<NewMemo age={age} count={count} />
</div>
);
}
}
export default Index;
# 04. forwardRef
# 场景一:隔代获取ref引用
- 正常情况下:react不允许ref 通过props 传递,因为在组件上已经带有ref这个属性,在组件进行调和过程中,已经被特殊处理了。
- forwardRef 出现可以解决这个问题,把ref转发到自定义的forwardRef定义的属性上,可以让ref通过props进行传递
import React, { Component, forwardRef } from "react";
function Son(props) {
const { grandRef } = props;
return <div>
<span ref={grandRef}>Son</span>
</div>;
}
class Father extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Son grandRef={this.props.grandRef} />;
}
}
const NewFather = forwardRef((props, ref) => (
<Father grandRef={ref} {...props} />
));
export default class GrandFather extends Component {
constructor(props) {
super(props);
}
node = null;
componentDidMount() {
console.log(this.node)
}
render() {
return <div>
<NewFather ref={(node) => this.node = node}/>
<Father/>
</div>;
}
}
# 场景二:高阶组件转发ref
由于属性代理的hoc,被包裹一层,如果是类组件,通过ref拿不到原始组件的实例的,可以借助forWardRef转发ref拿到对应的实例
import React, { Component, useRef, useEffect } from 'react'
function HOC(Comp){
class Wrap extends Component {
render() {
const {forwardRef, ...otherProps} = this.props;
return (
<Comp ref={forwardRef} {...otherProps}/>
)
}
}
return React.forwardRef((props, ref) => <Wrap forwardRef={ref} {...props}/>)
}
class Index extends Component{
componentDidMount(){
console.log('hello react')
}
render() {
return (
<div>hello forwardRef</div>
)
}
}
const HocIndex = HOC(Index);
export default () => {
const node = useRef(null);
useEffect(() => {
console.log(node.current.componentDidMount(), '--')
}, [])
return <div>
<HocIndex ref={node}/>
</div>
}
# 05. lazy
React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise ,该 Promise 需要 resolve 一个 default export 的 React 组件。
import React, { Component, Suspense } from 'react'
const UserInfo = React.lazy(() => new Promise((resolve)=>{
setTimeout(() => {
resolve(import('../Component/UserInfo'))
}, 1000)
}))
export default class Index extends Component {
render() {
return (
<Suspense fallback="loading...">
<UserInfo/>
</Suspense>
)
}
}
- import 原理如下: import() 函数是由TS39提出的一种动态加载模块的规范实现,其返回是一个 promise。在浏览器宿主环境中一个import()的参考实现如下:
function import(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
// toString(32) 32进制
const tmpGlobal = "__tempModuleLoadingVariable"+ Math.random().toString(32).substring(2);
script.type = "module";
script.textContent = `import * as m from "${url}"; window.${tmpGlobal} = m;`;
script.onload = () => {
resolve(window[tmpGlobal]);
delete(window[tmpGlobal]);
script.remove();
}
script.onerror = () => {
reject(new Error("Failed to load module script with URL " + url));
delete(window[tmpGlobal]);
script.remove();
}
document.documentElement.appendChild(script)
})
}
# 06. Fragment
不增加额外的dom节点,能够让一个组件返回多个元素 写法:
<React.Fragment></React.Fragment>
或者
<></>
二者区别:
- Fragment支持key元素,<></>不支持key元素
通过我们在map数据时,react底层会处理 默认在外部嵌套一个[Fragment]标签 例如:
[1,3,4].map(v => <span key={v}>{v}</span>)
等价于
<Fragment>
<span></span>
<span></span>
<span></span>
</Fragment>
# 07. Profiler
Profiler react 性能审查工具
react 有两个阶段为我们的应用工作
- render:React通过将渲染结果与先前的渲染进行比较来确定需要进行哪些DOM更改
- commit:React应用需要进行的任何更改。 从DOM中添加/删除并调用生命周期挂钩,例如componentDidMount和componentDidUpdate
profiler DevTools 是在commit 阶段收集性能数据的。各次 commit 会被展示在界面顶部的条形图中
Profiler 一般有两个参数:
- id:用来标识 Profiler的唯一性
- onRender:用于渲染完成,接受渲染参数
Profiler 需要一个 onRender 函数作为参数。 React 会在 profile 包含的组件树中任何组件 “提交” 一个更新的时候调用这个函数。 它的参数描述了渲染了什么和花费了多久。官网 (opens new window)
function onRenderCallback(
id, // 发生提交的 Profiler 树的 “id”
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) {
// 合计或记录渲染时间。。。
}
- id: string - 发生提交的 Profiler 树的 id。 如果有多个 profiler,它能用来分辨树的哪一部分发生了“提交”。
- phase: "mount" | "update" - 判断是组件树的第一次装载引起的重渲染,还是由 props、state 或是 hooks 改变引起的重渲染。
- actualDuration: number - 本次更新在渲染 Profiler 和它的子代上花费的时间。 这个数值表明使用 memoization 之后能表现得多好。(例如 React.memo,useMemo,shouldComponentUpdate)。 理想情况下,由于子代只会因特定的 prop 改变而重渲染,因此这个值应该在第一次装载之后显著下降。
- baseDuration: number - 在 Profiler 树中最近一次每一个组件 render 的持续时间。 这个值估计了最差的渲染时间。(例如当它是第一次加载或者组件树没有使用 memoization)。
- startTime: number - 本次更新中 React 开始渲染的时间戳。
- commitTime: number - 本次更新中 React commit 阶段结束的时间戳。 在一次 commit 中这个值在所有的 profiler 之间是共享的,可以将它们按需分组。
- interactions: Set - 当更新被制定时,“interactions” 的集合会被追踪。(例如当 render 或者 setState 被调用时)( 能用来识别更新是由什么引起的,实验性)。
# 08. StrictMode
作用:用来检测项目中潜在的问题,与 Fragment 一样, StrictMode 不会渲染任何可见的UI 。它为其后代元素触发额外的检查和警告
注意:
严格模式检查仅在开发模式下运行;它们不会影响生产构建。
可以为应用程序的任何部分启用严格模式
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
StrictMode 目前有助于
- 识别不安全的生命周期(UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps, UNSAFE_componentWillUpdate)
- 关于使用过时字符串 ref API 的警告
- 关于使用废弃的 findDOMNode 方法的警告
- 检测意外的副作用
- 检测过时的 context API