# React进阶

# 1、react fiber

  1. 对React-Fiber的理解,它解决了什么问题?

    作用:为了使react渲染过程中可以被中断,可以将控制权交还给浏览器,可以让位给高优先级的任务,浏览器空闲后再恢复渲染。对于计算量比较大的js计算或者dom计算,就不会显得特别卡顿,而是一帧一帧的有规律的执行任务。

    核心思想:Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

    具体解析:

    React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。

    为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:

    • 分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;

    • 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。

  2. 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的结果
1
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
  1. 如何判断当前是否有高优任务呢?

    当前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();
1
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;
1
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/>
1
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/>
1
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;
1
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;
1
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;
1
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;
1
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';
}
1
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的缺点
  1. 组件间的状态逻辑很难复用

    组件间如果有state的逻辑是相似的,class模式下基本使用高阶组件来解决。虽然能够解决问题,但是我们需要在组件外部再包一层元素,会导致层级非常冗余。

  2. 复杂业务的有状态组件会越来越复杂

  3. 监听和定时器的操作,被分散在多个区域:多个生命周期函数中存在同一业务逻辑,逻辑分散

  4. 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>
    )
  }
}
1
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的优点
  1. 利用业务逻辑的封装和拆分,可以非常自由的组合各种定义hooks(自己封装的用到的hooks的逻辑)
  2. 可以在无需修改组件结构的情况下,复用状态逻辑
  3. 定时器、监听等都被聚合到同一块代码下

# 2、使用事项

  1. 只能在函数内部的最外层调用hook,不要在循环、条件判断或者子函数中调用。
  2. 只能在react的函数组件中调用hook,不要在其他的js函数里调用

面试题:

  1. 为什么不能在循环、条件判断中调用?

    状态实现是使用单项列表,在循环和条件中调用,会使状态偏移。

  2. 为什么useEffect的第二个参数是空数组,就相当于componentDidMount只执行一次?

  3. 子定义的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;
}
1
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");
  )
}
1
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

资料:相关API解释(强于官方版本) (opens new window)

# 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>
  )
}
1
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>
  )
}
1
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阻塞渲染

  1. useEffect和useLayoutEffect的区别 (opens new window)

# 3、useReducer

解析:手写reducer原理 (opens new window)

使用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;
1
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;
1
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;
1
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;
1
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;
1
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;
1
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
1
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>
}
1
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
   }
}
1
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; 
            }
         }
    }
}
1
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
Last Updated: 2/12/2023, 8:30:43 PM