# React进阶
# 1、react fiber
对React-Fiber的理解,它解决了什么问题?
作用:为了使react渲染过程中可以被中断,可以将控制权交还给浏览器,可以让位给高优先级的任务,浏览器空闲后再恢复渲染。对于计算量比较大的js计算或者dom计算,就不会显得特别卡顿,而是一帧一帧的有规律的执行任务。
核心思想:Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
具体解析:
React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。
为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。
generator有类似的功能,为什么不直接使用?
要使用generator,需要将涉及到的所有代码都包装成generator *的形式,非常麻烦,工作量大
generator内部是有状态的
//同步执行,等待10s,较差
const tasks = []; //10个tast
function run(){
let task;
while(task = task.shift()){
execute(tast); //10s
}
}
//generator
const tasks = []; //10个tast
function *run(){
let task;
while(task = task.shift()){
if(hasHighPriorityTask()){
yield;
}
execute(tast); //10s
}
}
const iterator = run();
iterator.next();
function doWork(a, b, c){
const x = doExpendsiveWorkA(a);
yield;
const y = doExpendsiveWorkB(a);
yield;
const z = doExpendsiveWorkC(a);
return z;
}
//已经执行完了doExpendsiveWorkA和doExpendsiveWorkB,还未执行doExpendsiveWorkC。
//如果此时b被更新,那么在新的时间分片里,我们只能沿用之前获取的x、y的结果
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
如何判断当前是否有高优任务呢?
当前js的环境其实并没有办法判断是否有高优先任务。只能约定一个合理的执行时间,当超过了这个执行时间,如果任务仍然没有执行完成,中断当前任务,将控制权交还给浏览器。
合理的执行时间:人眼可识别每秒60帧,1000ms/60f = 16ms,浏览器提供方法requestIdleCallback,让浏览器在有空的时候执行回调,可传入一个参数,表示浏览器有多少时间供我们执行任务。
浏览器在一帧内要做什么事情:
- 处理用户输入事件
- js的执行
- requestAnimation调用
- 布局 layout
- 绘制 paint
有空时间:浏览器执行完上述事件后的空余时间,eg:上述执行时间6ms,剩余16ms-6ms=10ms可执行requestIdleCallback。
浏览器很忙怎么办?无空余时间
- requestIdleCallback入参有timeout参数超时时间,如果超过这个timeout后,回调还没执行,那么会在下一帧强制执行
- requestIdleCallback兼容性差,react通过messageChannel模拟实现了requestIdleCallback功能
- timeout超时后不一定要执行,react里预定了5个优先级
- Immediate:最高优先级,这个优先级的任务应该马上执行不能中断
- userBlocking:这些任务一般是用户交互的结果,需要即时得到反馈
- Normal:不需要用户立即就感受到的变化,比如网络请求
- Low:这些任务可以延后,但是最终也需要执行
- Idle:可以被无限期延后
# 2、高阶组件HOC
常见问题:平时用过高阶组件吗?什么是高阶组件?高阶组件能用来做什么?
# 1、什么是高阶组件
- 高阶函数:输入参数为函数,返回为函数
function helloWorld(){
const myName = sessionStorage.getItem('Jian')
console.log(`hello beautiful world, my mame is ${myName}`)
}
function byeWorld(){
const myName = sessionStorage.getItem('Jian')
console.log(`bye ugly world, my mame is ${myName}`)
}
//使用高阶函数
function helloWorld(myName){
console.log(`hello beautiful world, my mame is ${myName}`)
}
function byeWorld(myName){
console.log(`bye ugly world, my mame is ${myName}`)
}
function wrapWithUsername(wrappedFunc){
const tempFunction = () => {
const myName = sessionStorage.getItem('Jian')
wrappedFunc(myName);
}
return tempFunction;
}
const wrappedHello = wrapWithUsername(helloWorld);
const wrappedBye = wrapWithUsername(byeWorld);
wrappedHello();
wrappedBye();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
高阶组件,HOC(High order components):
是一个函数
入参:原来的react组件
返回值:新的react组件
是一个纯函数,不应该有任何的副作用
# 2、高阶组件
# 1、作用
- 代码复用,逻辑抽象
- 渲染劫持
- state的抽象和更改
- props的更改
# 2、写法
怎么写一个高阶组件?
- 普通方式
- 装饰器
- 多个高阶组件的组合
# 1、普通写法
//属性代理:操作props
import React, {Component} from 'react';
class Count extends Component {
state = {
number: 0
}
render(){
return (
<div>
{/* 对象会合并,hook不会 */}
{this.state.number}
<button onClick={()=>{this.setState({number: number + 1})}
</div>
)
}
}
function HocNumber (Comp) {
return class Temp extends Component {
render(){
let newProps = {
...this.props,
age: '18'
}
return <Comp {...newProps}/>
}
}
}
Count = HocNumber(Count);
export default Count;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 2、装饰器
//定义
import React, {Component} from 'react';
export const decoratorWithNameHeight = (height?: number) => {
//两个return用于获取传参
return (WrappedComponent: any) => {
return class extends Component<any, State> {
public state: State = {
name: ''
}
componentWillMount(){
let username = localStorage.getItem('myName');
this.setState({
name: username
})
}
render(){
return (
<div>
<WrappedComponent name={this.state.name} {...this.props}/>
<p>the height is {height || 0} </p>
</div>
)
}
}
}
}
//使用装饰器的高阶组件
import React, {Component} from 'react';
import {decoratorWithNameHeight} from "../index";
@decoratorWithNameHeight(180)
class UglyWorld extends Component<Props, any>{
render(){
return <div>bye ugly world! my name is {this.props.name}</div>
}
}
export default UglyWorld;
//使用
import {UglyWorld} from "../index"
<UglyWorld/>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 3、多个高阶组件的组合
export const decoratorWithWidth = (width?: number) => {
//两个return用于获取传参
return (WrappedComponent: any) => {
return class extends Component<any, any> {
render(){
return (
<div>
<WrappedComponent {...this.props}/>
<p>the width is {width || 0} </p>
</div>
)
}
}
}
}
//使用多个装饰器的高阶组件
import React, {Component} from 'react';
import {decoratorWithNameHeight, decoratorWithWidth} from "../index";
@decoratorWithWidth(100)
@decoratorWithNameHeight(180)
class UglyWorld extends Component<Props, any>{
render(){
return <div>bye ugly world! my name is {this.props.name}</div>
}
}
export default UglyWorld;
//使用:先展示height、再展示width
import {UglyWorld} from "../index"
<UglyWorld/>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 3、实现方法及技术作用
高级组件能用来做什么?从技术方面?
实现高阶组件的方法有两种,包括属性代理和反向继承
- 属性代理:继承component,渲染使用原组件
- 操作props
- 操作组件实例
- 抽象state和事件:将子组件的事件和state抽象至高阶组件中,通过props传值使用
- 使用场景:将表单中的数据和事件抽象到高阶组件中,统一处理
- 通过ref使用引用
- 反向继承/劫持:继承传入的组件,从而使用传入组件的属性和方法
- 渲染劫持
- 控制state:可读入传入组件的state对其修改,建议不修改
//属性代理:通过ref使用引用,对子组件DOM进行处理
//1. 父组件使用高阶组件
import React, {Component} from 'react'
import Hoc from './hoc/index'
class App extends Component {
refA = React.creatRef()
componentDidMount(){
//操作子组件的input
this.refA.current.InputRef.current.focus();
}
render(){
return (
<div>
<Hoc name="jian" ref={this.refA}/>
</div>
)
}
}
export default App;
//2.高阶组件使用ref
// app组件传入refA,挂载至Count组件上,this.refA.current可获取到Count组件
// this.refA.current.InputRef.current可获取到input的dom
import React, {Component} from 'react'
class Count extends Component {
state = {
number: 0
}
InputRef = React.createRef()
render(){
return (
<div>
{/* 对象会合并,hook不会 */}
{this.state.number}
<input type="text" ref={this.InputRef}/>
<button onClick={()=>{this.setState({number: number + 1})}
</div>
)
}
}
function HocNumber (Comp) {
class Temp extends Component {
render(){
let newProps = {
...this.props,
age: '18'
}
let { forwardRef } = this.props;
return <Comp {...newProps} ref={forwardRef}/>
}
}
return React.forwardRef((props, ref) => {
return <Temp forwardRef={ref} {...props}/>
})
}
Count = HocNumber(Count);
export default Count;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//属性代理:操作组件实例
export const refHoc = () => {
return (WrappedComponent: any) => {
return class extends Component<any, any>{
ref = null;
componentDidMount(){
console.log(this.ref.state);
}
render(){
return (
<WrappedComponent
{...this.props}
ref={(instance: any)=>{
this.ref = instance;
}}
)
}
}
}
}
import {refHoc} from "../refHoc";
interface Props {
name?: string;
}
interface State {
weight?: number:
height?: number;
}
@refHoc
class RefDemoComponent extends Component<Props, state>{
state: State = {
weight: 60,
height: 170
}
render(){
return <div>bye ugly world my name is {this.props.name}</div>
}
}
export default RefDemoComponent;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//劫持:劫持方法
import React from 'react';
export function hijackHoc<T extends {new (...args: any[]):any}>(
component: T
){
return class extends component {
//劫持点击事件,做处理
handleClick(){
console.log(this.handleClick)
super.handleClick();
}
render(){
const parent = super.render();
return React.cloneElement(parent, {
onClick: () => this.handleClick()
})
}
}
}
//使用
@hijackHoc
class HijackComponent extends Component<Props, state>{
state: State = {
weight: 60,
height: 170
}
handleClick(){
this.setState({
weight: this.state.weight + 1;
})
}
render(){
return (
<div onClick={()=> this.handleClick()}>
测试按钮 {this.state.weight}
</div>
)
}
}
export default RefDemoComponent;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//渲染劫持
import React, {Component} from 'react';
class Count extends Component {
render(){
return <div>count</div>
}
}
const MyContainer = (WrappedComponent) => {
return class Temp extends WrappedComponent{
render(){
const elementsTree = super.render()
let newProps = {}
if(this.props.isShow){
return super.render()
}else{
return null;
}
}
}
}
Count = MyContainer(Count)
export default Count;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
组件命名:Hoc创建的容器组件会与任何其他组件一样,为了方便调试,选择一个显示名称,表示他是HOC的产物,最常见的方式是用HOC保住被包装组件的显示名称,eg:高阶组件名为withSubscription,被包装的组件显示名称为CommentList,显示名称应为WithSubscription(CommentList)。
function withSubscription(WrappedComponent){
class WithSubScription extends React.Component {/* */}
WithSubScription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
return WithSubScription;
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
2
3
4
5
6
7
8
9
# 3、hooks
常见题目:
什么是react hooks?
Hook 是 React 16.8 的新增特性。React Hook 从具象上来说就为函数组件(纯函数)提供副作用能力的 React API,从抽象意义上来说是确定状态源的解决方案。其实就是可以让你在不编写class的情况下使用state以及其他的React特性,通常以use开头。
React hooks有什么优势?
- 避免地狱式嵌套,可读性提高:hoc组件套组件
- 比class更容易理解
- 使函数组件存在状态
- 解决Hoc和render props的缺点:数据来源无法追述
为什么不写class而转向了hooks的写法?(从类组件转向函数组件)
# 1、hooks优势
- class的缺点
组件间的状态逻辑很难复用
组件间如果有state的逻辑是相似的,class模式下基本使用高阶组件来解决。虽然能够解决问题,但是我们需要在组件外部再包一层元素,会导致层级非常冗余。
复杂业务的有状态组件会越来越复杂
监听和定时器的操作,被分散在多个区域:多个生命周期函数中存在同一业务逻辑,逻辑分散
this指向问题
class App extends React.Component<any, any>{
constructor(props){
super(props);
this.state = {
num: 1,
title: "Jian"
}
this.handleClick2 = this.handleClick1.bind(this);
}
handleClick1(){
this.setState({
num: this.state.num + 1
})
}
handleClick3 = () => {
this.setState({
num: this.state.num + 1
})
}
render(){
return (
<div>
{/* 考点1: render里bind每次都会返回一个新的函数,造成ChildComponent每次会重新渲染 */}
<ChildComponent onClick={this.handleClick1.bind(this)}></ChildComponent>
<ChildComponent onClick={()=> this.handleClick1()}></ChildComponent>
{/* 考点1解决方法:提取函数 */}
<ChildComponent onClick={this.handleClick2}></ChildComponent>
<ChildComponent onClick={this.handleClick3}></ChildComponent>
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- hooks的优点
- 利用业务逻辑的封装和拆分,可以非常自由的组合各种定义hooks(自己封装的用到的hooks的逻辑)
- 可以在无需修改组件结构的情况下,复用状态逻辑
- 定时器、监听等都被聚合到同一块代码下
# 2、使用事项
- 只能在函数内部的最外层调用hook,不要在循环、条件判断或者子函数中调用。
- 只能在react的函数组件中调用hook,不要在其他的js函数里调用
面试题:
为什么不能在循环、条件判断中调用?
状态实现是使用单项列表,在循环和条件中调用,会使状态偏移。
为什么useEffect的第二个参数是空数组,就相当于componentDidMount只执行一次?
子定义的hook怎样操作组件的?
手写代码实现useState
import React from 'react';
import ReactDom from "react-dom";
//使用方法
function Counter(){
const [count, setCount] = useState(0);
const onClick = () => {
setCount(count + 1)
}
//扩展使用多个state
const [name, setName] = useState('');
const onClickName = () => {
setName(`${name} ${Math.random()}`)
}
return (
<div>
<div>{{count}}</div>
<button onClick={onClick}>click</button>
<div>{{name}}</div>
<button onClick={onClickName}>click</button>
</div>
)
}
//手写useState,返回数组
//实际实现是使用单项链表,不是数组
let state: any[] = [];
let cursor = 0;
function useState<T>(initialState: T): [T, (newState: T) => void] {
const currentCursor = cursor;
state[currentCursor] = state[currentCursor] || initialState;
function setState(newState: T){
state[currentCursor] = newState;
render();
}
cursor++;
return [state[currentCursor], setState];
}
//替代原app.js中的render方法
export function render(){
ReactDom.render(
<React.StrictMode>
<Counter/>
</React.StrictMode>
document.getElementById("root");
)
//当渲染完成时,清空
cursor = 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
手写代码实现useEffect
import React from 'react';
import ReactDom from "react-dom";
//使用方法
function CounterEffect(){
effectCursor = 0; //初始清空
const [count, setCount] = useState(0);
const onClick = () => {
setCount(count + 1)
}
const [count1, setCount1] = useState(0);
const onClick1 = () => {
setCount1(count1 + 1)
}
//用法:入参两参数:回调,依赖项(空数组时只在组件加载时执行一次)
useEffect(() => {
console.log(`count 发生了改变 ${count}`)
}, [count])
useEffect(() => {
console.log(`count1 发生了改变 ${count1}`)
}, [count1])
return (
<div>
<div>{{count}}</div>
<button onClick={onClick}>click</button>
<div>{{count1}}</div>
<button onClick={onClick1}>click1</button>
</div>
)
}
//手写useEffect
const allDeps = Array <any[] | undefined> = [] //二维数组
let effectCursor: number = 0;
function useState(callback:()=>void, depArray?: any[]){
if(!depArray){
callback();
allDeps[effectCursor] = depArray;
effectCursor++;
return;
}
//本次依赖项和上一次存储依赖项数组对比,是否有变化
const deps = allDeps[effectCursor];
const hasChanged = deps
? depArray.some((el, i) => el !== deps[i])
: true;
if(hasChanged){
callback();
allDeps[effectCursor] = depArray;
}
effectCursor++;
}
//替代原app.js中的render方法
export function render(){
ReactDom.render(
<React.StrictMode>
<CounterEffect/>
</React.StrictMode>
document.getElementById("root");
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 3、常用API
# 1、useState
使用状态
import React, {useState, Component} from 'react';
class Temp extends Component{
state = {
count: 0
}
render(){
return (
<div>
{this.state.count}
<button onClick={()=>{
this.setState({
count: this.state.count + 1
})
}}>+</button>
</div>
)
}
}
//hooks写法
const Temp = () => {
const [count, setCount] = useState(0); //初始值为0
return (
<div>
{count}
<button onClick={()=>{setCount(count+1)}}>+</button>
</div>
)
}
//复合值,setValue无法合并对象
const Temp = () => {
const [counter, setValue] = useState(()=>{
return {name: 'jian', age: 18}
});
return (
<div>
{counter.name}{counter.age}
<button onClick={()=>{setValue({...counter, age: counter.age + 1})}}>+</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 2、useEffect
使用生命周期
import React, {useState, useEffect} from 'react';
const Temp = () => {
const [number, setNumber] = useState(0);
const [count, setCount] = useState(0);
//Similar to componentDidMount,只执行一次
useEffect(()=>{
console.log('useEffect')
}, [])
//Similar to componentDidMount and componentDidUpdate
useEffect(()=>{
console.log('useEffect')
//返回函数,清楚副作用,componentWillUnmount
return function xx(){
}
})
//Similar to componentDidUpdate,仅在依赖项更新时调用
useEffect(()=>{
console.log('useEffect')
}, [number])
return (
<div>
<h1>{number}</h1>
<h2>{count}</h2>
<button onClick={()=>{setNumber(number+1)}}>number+</button>
<button onClick={()=>{setCount(count+1)}}>number+</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
解析:useLayoutEffect阻塞渲染
# 3、useReducer
使用reducer保存全局复合数据
import React, {useState, useReducer} from 'react'
const initState = {
name: 'jian',
age: 18
}
function reducer(state, action){
switch(action.type){
case 'increment':
return {...state, age: age + 1}
case 'decrement':
return {...state, age: age - 1}
default:
return state
}
}
function Temp(){
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<h1>{state.name}</h1>
<h2>{state.age}</h2>
<button onClick={()=>{dispatch({type: 'increment'})}}>increment</button>
<button onClick={()=>{dispatch({type: 'decrement'})}}>decrement</button>
</div>
)
}
export default Temp;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 4、useContext
使用context解决父子数据传递问题:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
import React, {useState, useContext} from 'react'
let theme = {
black: {
background: "#000",
color: "#fff"
},
pink: {
background: "#eee",
color: "#000"
}
}
const ThemeContext = React.createContext()
//父组件
function App (){
return <ThemeContext.Provider value={theme.black}>
<Theme/>
</ThemeContext.Provider>
}
//子组件
function Theme(){
const ThemeCo = useContext(ThemeContext);
return (
<div style={background: ThemeCo.background, color: ThemeCo.color}>
</div>
)
}
export default App;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 5、useRef
获取dom元素,类似createRef,返回一个突变的ref对象,对象在组件的生命周期里一直存在
import React, {useRef} from 'react'
function App() {
const inputRef = useRef()
const getFoucs = () => {
inputRef.current.focus();
}
return <div>
<input type="text" ref={inputRef}/>
<button onClick={getFoucs}>click me</button>
</div>
}
export dafault App;
2
3
4
5
6
7
8
9
10
11
12
# 6、useCallback
性能优化,缓存方法 (函数)
import React, {useState, useCallback} from 'react'
/*
解析:父组件更改,Child每次在渲染
使用React.memo包裹可优化,对函数组件进行优化
类似:shouldComponentUpdate和PureComponent
*/
const Child = React.memo((getCount) => {
return <div onClick={getCount}>child</div>
})
function Parent(){
const [num, setNum] = useState(0);
const [val, setVal] = useState("");
/*
未使用useCallback包裹时,val和num变时都渲染
使用useCallback包裹时,只有依赖项变动才会渲染
*/
const getCount = useCallback(() => {
console.log(num)
}, [num]}
return (
<div>
<h1>num: {num}</h1>
<h2>val: {val}</h2>
<input type="text" onChange={(ev) => {setVal(ev.target.value)}}/>
<button onClick={()=> {setNum(num + 1)}}>+</button>
<Child getCount={getCount}/>
</div>
)
}
export default Parent;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 7、useMemo
缓存计算值,性能优化
import React, {useState, useMemo} from 'react'
const Child = React.memo((getCount) => {
return <div onClick={getCount}>child</div>
})
function Parent(){
/*
未使用useMemo包裹时,val和num变时都渲染
使用useMemo包裹时,只有依赖项变动才会渲染
*/
const expensive = useMemo(() => {
let sum = 0;
for(let i=0; i<num * 100; i++){
sum += i;
}
return sum;
}, [num]);
return (
<div>
<h1>num: {num}</h1>
<h2>val: {val}</h2>
<h3>{expensive()}</h3>
<input type="text" onChange={(ev) => {setVal(ev.target.value)}}/>
<button onClick={()=> {setNum(num + 1)}}>+</button>
<Child getCount={getCount}/>
</div>
)
}
export default Parent;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
解析:useCallback和useMemo的区别:使用 React.memo 封装函数子组件,使用 useCallback 封装作为 props 传递给子组件的回调函数 ,只有当依赖数据变更时,传递的回调函数才会视为变更,子组件才会驱动引发重新渲染,这有助于优化性能
# 8、useCustom
使用use开头封装函数,内部使用原hook方法
import React, {useState, useMemo} from 'react'
function useCutDown(init = 60){
const [count, setCount] = useState(init)
useEffect(()=>{
let timer = setInterval(()=> {
if(count < 1){
clearInterval(timer)
return
}
setCount(count -1)
}, 1000)
return ()=>{
clearInterval(timer)
}
}, [])
return count
}
function App(){
let Counter = useCutDown(120);
return <div>{Counter}</div>
}
export default App;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4、异步组件
异步模式:请求数据 -> 渲染组件
解决问题:优化性能、延迟加载
# 1、传统模式
渲染组件 -> 请求数据 -> 再渲染组件
function Index(){
const [userInfo, setUserInfo] = React.useState(0);
React.useEffect(()=>{
getUserInfo().then(res => {
setUserInfo(res)
})
}, [])
return <div>
<h1>{userInfo.name}</h1>
</div>
}
//2. 使用Suspense + 异步组件
function AsyncComponent(){
const userInfo = getUserInfo()
return <div>
<h1>{userInfo.name}</h1>
</div>
}
export default function Home(){
return <div>
<React.Suspense fallback={<div>loading...</div>}>
<AsyncComponent/>
</React.Suspense>
</div>
}
//3. 使用Suspense + lazy
//eg:import('./app.js').then()
code splitting和import
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 2、使用Suspense
//1. 使用Suspense + 异步组件
function AsyncComponent(){
const userInfo = getUserInfo()
return <div>
<h1>{userInfo.name}</h1>
</div>
}
export default function Home(){
return <div>
<React.Suspense fallback={<div>loading...</div>}>
<AsyncComponent/>
</React.Suspense>
</div>
}
//2. 使用Suspense + lazy
//code splitting和import,eg:import('./app.js').then()
const LazyComponent = React.lazy(() => import('./test.js'))
export default function Index(){
return <Suspense fallback={<div>loading...</div>} >
<LazyComponent />
</Suspense>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- suspense原理
export class Suspense extends React.Component{
state={ isRender: true }
componentDidCatch(e){
/* 异步请求中,渲染 fallback */
this.setState({ isRender:false })
const { p } = e
Promise.resolve(p).then(()=>{
/* 数据请求后,渲染真实组件 */
this.setState({ isRender:true })
})
}
render(){
const { isRender } = this.state
const { children , fallback } = this.props
return isRender ? children : fallback
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- lazy原理
function lazy(ctor){
return {
$$typeof: REACT_LAZY_TYPE,
_payload:{
_status: -1, //初始化状态
_result: ctor,
},
_init:function(payload){
if(payload._status===-1){ /* 第一次执行会走这里 */
const ctor = payload._result;
const thenable = ctor();
payload._status = Pending;
payload._result = thenable;
thenable.then((moduleObject)=>{
const defaultExport = moduleObject.default;
resolved._status = Resolved; // 1 成功状态
resolved._result = defaultExport;/* defaultExport 为我们动态加载的组件本身 */
})
}
if(payload._status === Resolved){ // 成功状态
return payload._result;
}
else { //第一次会抛出Promise异常给Suspense
throw payload._result;
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28