今日目标 ✔ 掌握如何进行状态和业务逻辑的复用。
✔ 了解 React 内置的其他 Hook。
基本需求 建议非 TS 环境下进行学习测试!
1 npx create-react-app test --template typescript
index.tsx
1 2 3 4 import ReactDOM from 'react-dom' import App from './App' ReactDOM.render(<App /> , document .querySelector('#root' ))
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { Component } from 'react' import Image from './components/Image' import Position from './components/Position' export default class App extends Component { render ( ) { return ( <div > <Image /> <Position /> </div > ) } }
components/Image.tsx
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 import React, { Component } from 'react' import dva from '../assets/dva.png' export default class Image extends Component { state = { x : 0 , y : 0 , } move = (e: MouseEvent ) => { this .setState({ x : e.pageX, y : e.pageY, }) } componentDidMount(): void { document .addEventListener('mousemove' , this .move) } componentWillUnmount(): void { document .removeEventListener('mousemove' , this .move) } render ( ) { return ( <div > <img src ={dva} width ='100' height ='100' alt ='' style ={{ position: 'absolute ', left: this.state.x , top: this.state.y , }} /> </div > ) } }
components/Position.tsx
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 import React, { Component } from 'react' export default class Position extends Component { state = { x : 0 , y : 0 , } move = (e: MouseEvent ) => { this .setState({ x : e.pageX, y : e.pageY, }) } componentDidMount(): void { document .addEventListener('mousemove' , this .move) } componentWillUnmount(): void { document .removeEventListener('mousemove' , this .move) } render ( ) { return ( <div > <h3 > x: {this.state.x} y: {this.state.y} </h3 > </div > ) } }
render-props 有一个叫做 render 的属性,可以通过 props 进行获取,官方文档 。
基本概述
基本使用
思路:将要复用的 state 和操作 state 的方法封装到一个组件中。
问题 1:渲染的 UI 内容不一样,该怎么办?
在使用组件时,添加一个值为函数的 prop,通常把这个 prop 命名为 render,在组件内部调用这个函数,函数的返回值就是需要渲染的 UI。
问题 2:如果获取组件内部的状态
在组件内部调用方法的时候,把状态当成参数进行传递。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { Component } from 'react' import Image from './components/Image' import Position from './components/Position' import Mouse from './components/Mouse' export default class App extends Component { render ( ) { return ( <div > <Mouse render ={(state) => <Image {...state } /> } /> <Mouse render ={(state) => <Position {...state } /> } /> </div > ) } }
components/Mouse.tsx
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 import { Component } from 'react' type State = { x : number y : number } type Props = { render : (state: State ) => React.ReactElement } export default class Mouse extends Component <Props > { state = { x : 0 , y : 0 , } move = (e: MouseEvent ) => { this .setState({ x : e.pageX, y : e.pageY, }) } componentDidMount(): void { document .addEventListener('mousemove' , this .move) } componentWillUnmount(): void { document .removeEventListener('mousemove' , this .move) } render ( ) { return this .props.render(this .state) } }
components/Image.tsx
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 import React, { Component } from 'react' import dva from '../assets/dva.png' type Props = { x : number y : number } export default class Image extends Component <Props > { render ( ) { return ( <div > <img src ={dva} width ='100' height ='100' alt ='' style ={{ position: 'absolute ', left: this.props.x , top: this.props.y , }} /> </div > ) } }
components/Position.tsx
1 2 3 4 5 6 7 8 9 10 11 12 type Props = { x : number y : number } export default function Position ({ x, y }: Props ) { return ( <h3 > x: {x} y: {y} </h3 > ) }
代码优化
注意:并不是该模式叫 render props 就必须规定传递的 prop 为 render ,实际上可以使用任意名称的 prop。
把 prop 是一个函数并且告诉组件要渲染什么内容的技术叫做:render props 模式。
推荐:使用 children 代替 render 属性。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { Component } from 'react' import Image from './components/Image' import Position from './components/Position' import Mouse from './components/Mouse' export default class App extends Component { render ( ) { return ( <div > <Mouse > {(state) => <Image {...state } /> }</Mouse > <Mouse > {(state) => <Position {...state } /> }</Mouse > </div > ) } }
components/Mouse.tsx
1 2 3 4 5 export default class Mouse extends Component <Props > { render ( ) { return this .props.children(this .state) } }
缺点分析 滚动位置的状态和业务逻辑的封装。
components/Mouse.tsx
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 import { Component } from 'react' type State = { left : number top : number } type Props = { children : (state: State ) => React.ReactElement } export default class Scroll extends Component <Props , State > { state = { top : 0 , left : 0 , } scroll = (e: Event ) => { this .setState({ left : window .pageXOffset, top : window .pageYOffset, }) } componentDidMount ( ) { window .addEventListener('scroll' , this .scroll) } componentWillUnmount ( ) { window .removeEventListener('scroll' , this .scroll) } render ( ) { return this .props.children(this .state) } }
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { Component } from 'react' import Image from './components/Image' import Position from './components/Position' import Mouse from './components/Mouse' import Scroll from './components/Scroll' export default class App extends Component { render ( ) { return ( <div style ={{ height: 3000 }}> <Mouse > {(state) => <Image {...state } /> }</Mouse > <Scroll > {(scrollState) => <Mouse > {(mouseState) => <Position {...scrollState } {...mouseState } /> }</Mouse > }</Scroll > </div > ) } }
components/Position.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Props = { x : number y : number left : number top : number } export default function Position ({ x, y, left, top }: Props ) { return ( <h3 style ={{ position: 'fixed ' }}> x: {x} y: {y} left: {left} top: {top} </h3 > ) }
高阶组件 HOC 概述
思路分析
高阶组件(HOC,Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件。
高阶组件的命名:withMouse
、withRouter
、withXXX
。
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态和业务逻辑,通过 prop 将复用的状态传递给被包装的组件。
1 2 const CatWithMouse = withMouse(Cat)const PositionWithMOuse = withMouse(Position)
1 2 3 4 5 6 7 8 9 10 const WithMouse = (Base ) => { class Mouse extends React .Component { render ( ) { return <Base {...this.state } /> } } return Mouse }
使用步骤
创建一个函数,名称约定以 with 开头。
指定函数参数(作为要增强的组件) 传入的组件只能渲染基本的 UI。
在函数内部创建一个类组件,提供复用的状态逻辑代码 ,并返回。
在内部创建的组件的 render 中,需要渲染传入的基本组件,增强功能,通过 props 的方式给基本组件传值。
调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中。
components/withMouse.tsx
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 import React from 'react' type Props = { [key: string ]: any } export default function withMouse (Base: any ) { class Mouse extends React .Component <Props > { state = { x : 0 , y : 0 , } move = (e: MouseEvent ) => { this .setState({ x : e.pageX, y : e.pageY, }) } componentDidMount(): void { document .addEventListener('mousemove' , this .move) } componentWillUnmount(): void { document .removeEventListener('mousemove' , this .move) } render ( ) { return <Base {...this.state } {...this.props } /> } } return Mouse }
Image.tsx
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 import React, { Component } from 'react' import dva from '../assets/dva.png' type Props = { x : number y : number name : string } export default class Image extends Component <Props > { render ( ) { return ( <div > name: {this.props.name} <img src ={dva} width ='100' height ='100' alt ='' style ={{ position: 'absolute ', left: this.props.x , top: this.props.y , }} /> </div > ) } }
Position.tsx
1 2 3 4 5 6 7 8 9 10 11 12 type Props = { x : number y : number } export default function Position ({ x, y }: Props ) { return ( <h3 > x: {x} y: {y} </h3 > ) }
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { Component } from 'react' import Image from './components/Image' import Position from './components/Position' import withMouse from './components/withMouse' const ImageHOC = withMouse(Image)const PositionHOC = withMouse(Position)export default class App extends Component { render ( ) { return ( <div style ={{ height: 3000 }}> <ImageHOC name ='ifer' /> <PositionHOC /> </div > ) } }
名字优化 components/withMouse.tsx
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 import React from 'react' type Props = { [key: string ]: any } function getDisplayName (Base: any ) { return Base.displayName || Base.name || 'Component' } export default function withMouse (Base: any ) { class Mouse extends React .Component <Props > { state = { x : 0 , y : 0 , } static displayName = `WithMouse${getDisplayName(Base)} ` move = (e: MouseEvent ) => { this .setState({ x : e.pageX, y : e.pageY, }) } componentDidMount(): void { document .addEventListener('mousemove' , this .move) } componentWillUnmount(): void { document .removeEventListener('mousemove' , this .move) } render ( ) { return <Base {...this.state } {...this.props } /> } } return Mouse }
缺点演示 组件结构嵌套过深,封装麻烦,相对 render-props 使用简单些。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { Component } from 'react' import Image from './components/Image' import Position from './components/Position' import withMouse from './components/withMouse' import withScroll from './components/withScroll' const ImageHOC = withMouse(Image)const ScrollPositionHOC = withScroll(withMouse(Position))export default class App extends Component { render ( ) { return ( <div style ={{ height: 3000 }}> <ImageHOC name ='ifer' /> <ScrollPositionHOC name ='xxx' /> </div > ) } }
components/withScroll.tsx
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 import React, { Component } from 'react' type Props = { [key: string ]: any } export default function withScroll (Base: React.ComponentType<any > ) { class Scroll extends Component <Props > { state = { top : 0 , left : 0 , } scroll = (e: Event ) => { this .setState({ left : window .pageXOffset, top : window .pageYOffset, }) } componentDidMount ( ) { window .addEventListener('scroll' , this .scroll) } componentWillUnmount ( ) { window .removeEventListener('scroll' , this .scroll) } render ( ) { return <Base {...this.state } {...this.props } /> } } return Scroll }
components/Position.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Props = { x : number y : number left : number top : number name : string } export default function Position ({ x, y, left, top, name }: Props ) { return ( <h3 > x: {x} y: {y} left: {left} top: {top} name: {name} </h3 > ) }
自定义 Hooks 官方文档
基本使用 App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { Component } from 'react' import Position from './components/Position' import Image from './components/Image' export default class App extends Component { render ( ) { return ( <div style ={{ height: 3000 }}> <Position /> <Image /> </div > ) } }
components/Image.tsx
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 import React, { useEffect, useState } from 'react' import dva from '../assets/dva.png' export default function Image ( ) { const [mouse, setMouse] = useState({ x : 0 , y : 0 , }) useEffect(() => { const move = (e: MouseEvent ) => { setMouse({ x : e.pageX, y : e.pageY, }) } document .addEventListener('mousemove' , move) return () => { document .removeEventListener('mousemove' , move) } }, []) return ( <div > <img src ={dva} width ='100' height ='100' alt ='' style ={{ position: 'absolute ', left: mouse.x , top: mouse.y , }} /> </div > ) }
components/Position.tsx
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 import { useEffect, useState } from 'react' export default function Position ( ) { const [mouse, setMouse] = useState({ x : 0 , y : 0 , }) useEffect(() => { const move = (e: MouseEvent ) => { setMouse({ x : e.pageX, y : e.pageY, }) } document .addEventListener('mousemove' , move) return () => { document .removeEventListener('mousemove' , move) } }, []) return ( <h3 > x: {mouse.x} y: {mouse.y} </h3 > ) }
代码优化 hooks/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { useState, useEffect } from 'react' export function useMouse ( ) { const [mouse, setMouse] = useState({ x : 0 , y : 0 , }) useEffect(() => { const move = (e: MouseEvent ) => { setMouse({ x : e.pageX, y : e.pageY, }) } document .addEventListener('mousemove' , move) return () => { document .removeEventListener('mousemove' , move) } }, []) return mouse }
components/Position.tsx
1 2 3 4 5 6 7 8 9 10 import { useMouse } from '@/hooks' export default function Position ( ) { const mouse = useMouse() return ( <h3 > x: {mouse.x} y: {mouse.y} </h3 > ) }
components/Image.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import dva from '../assets/dva.png' import { useMouse } from '@/hooks' export default function Image ( ) { const mouse = useMouse() return ( <div > <img src ={dva} width ='100' height ='100' alt ='' style ={{ position: 'absolute ', left: mouse.x , top: mouse.y , }} /> </div > ) }
class 组件性能优化 减轻 state
减轻 state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)。
注意:不做渲染的数据不要放在 state 中,比如定时器 id 等,Vue 中也一样不要把和渲染无关的数据放到 data 中。
对于这种需要在多个方法中用到的数据,可以直接挂载到 this 上,例如 this.xxx = 'bbb'
。
1 2 3 4 5 6 7 8 9 10 class Hello extends Component { componentDidMount ( ) { this .timerId = setInterval (() => {}, 2000 ) } componentWillUnmount ( ) { clearInterval (this .timerId) } render ( ) { … } }
避免不必要的重新渲染
默认情况下只要父组件更新,所有的后代组件也会重新进行渲染。
问题:子组件没有任何变化时也会重新渲染 (接收到的 props 没有发生任何的改变)。
如何避免不必要的重新渲染呢?
解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
。
作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染。
触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)。
问题演示 App.tsx
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 import { Component } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' export default class App extends Component { state = { name : 'ifer' , age : 18 , } handleClick = () => { this .setState({ age : this .state.age + 1 }) } render ( ) { console .log('app' ) const { name, age } = this .state return ( <div > App <button onClick ={this.handleClick} > +1</button > <hr /> <Child1 name ={name} /> <Child2 age ={age} /> </div > ) } }
components/Child1.tsx
1 2 3 4 5 6 7 8 import React, { Component } from 'react' export default class Child1 extends Component < { name: string }> { render ( ) { console .log('child1' ) return <div > Child1: {this.props.name}</div > } }
components/Child2.tsx
1 2 3 4 5 6 7 8 import React, { Component } from 'react' export default class Child2 extends Component < { age: number }> { render ( ) { console .log('child2' ) return <div > Child2: {this.props.age}</div > } }
问题解决 components/Child1.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import React, { Component } from 'react' export default class Child1 extends Component < { name: string }> { shouldComponentUpdate (nextProps: { name: string } ) { if (this .props.name !== nextProps.name) { return true } return false } render ( ) { console .log('child1' ) return <div > Child1: {this.props.name}</div > } }
纯组件
纯组件:React.PureComponent
与 React.Component
功能相似。
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较。
原理:纯组件内部通过分别对比前后两次 props 和 state 的值,来决定是否需要重新渲染组件。
注意:只有在性能优化的时候才使用纯组件,不建议所有组件无脑使用,因为纯组件也需要消耗性能才能进行对比。
1 2 3 4 5 class Hello extends React .PureComponent { render ( ) { return <div > 纯组件</div > } }
为什么不要修改原数据 shouldComponentUpdate
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 import React from 'react' export default class App extends React .Component { state = { age : 1 , } updateAge = () => { this .state.age += 1 this .setState({ age : this .state.age, }) } shouldComponentUpdate (nextProps: any , nextState: any ) { if (nextState.age !== this .state.age) { return true } else { return false } } render ( ) { const { age } = this .state return ( <div > <p > name: {age}</p > <button onClick ={this.updateAge} > 修改 name</button > </div > ) } }
React.memo 基本概述
是什么:React.memo 是一个高阶组件,用来记忆(memorize)组件。
场景:当你想要避免函数组件 props 没有变化而产生的不必要更新时,就要用到 React.memo
了。
作用:记忆组件上一次的渲染结果,在 props 没有变化时复用该结果,避免函数组件不必要的更新 。
参数(Child):需要被记忆的组件,或者说是需要避免不必要更新的组件。
返回值(MemoChild):React 记住的 Child 组件。
代码演示 App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { useState } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' export default function App ( ) { const [name] = useState('ifer' ) const [age, setAge] = useState(18 ) const handleClick = () => setAge(age + 1 ) console .log('app~~~' ) return ( <div > App <button onClick ={handleClick} > +1</button > <hr /> <Child1 age ={age} /> <Child2 name ={name} /> </div > ) }
components/Child1.tsx
1 2 3 4 5 function Child1 ({ age }: { age: number } ) { console .log('child1~~~' ) return <div > Child1: {age}</div > } export default Child1
components/Child2.tsx
1 2 3 4 export default function Child2 ({ name }: { name: string } ) { console .log('child2~~~' ) return <div > Child2: {name}</div > }
问题 1:点击按钮只修改了 Child1 组件中用到的 age,Child2 也被渲染了。
解决:使用 React.memo 包裹 Child2。
1 2 3 4 5 6 import React from 'react' function Child2 ({ name }: { name: string } ) { console .log('child2~~~' ) return <div > Child2: {name}</div > } export default React.memo(Child2)
问题 2:给 Child2 组件传递了一个函数试试,发现 React.memo()
失效。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { useState } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' export default function App ( ) { const [age, setAge] = useState(18 ) const handleClick = () => setAge(age + 1 ) const updateName = () => {} console .log('app~~~' ) return ( <div > App <button onClick ={handleClick} > +1</button > <hr /> <Child1 age ={age} /> {/* #1 */} <Child2 updateName ={updateName} /> </div > ) }
components/Child2.tsx
1 2 3 4 5 6 7 import React from 'react' function Child2 ({ updateName }: { updateName: () => void } ) { console .log('child2~~~' ) return <div > Child2</div > } export default React.memo(Child2)
浅层对比
解决方式 1: 手动比较。
components/Child1.tsx
1 2 3 4 5 6 7 8 9 10 11 import React from 'react' function Child1 ({ name, updateName }: { name: string ; updateName: () => void } ) { console .log('child1' ) return <div > Child1: {name}</div > } export default React.memo(Child1, (prevProps, nextProps ) => { return !(prevProps.name !== nextProps.name) })
解决方式 2: useRef。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { useState, useRef } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' export default function App ( ) { const [name] = useState('ifer' ) const [age, setAge] = useState(18 ) const handleClick = () => { setAge(age + 1 ) } const updateName = useRef(() => {}) console .log('app' ) return ( <div > App <button onClick ={handleClick} > +1</button > <hr /> <Child1 name ={name} updateName ={updateName.current} /> <Child2 age ={age} /> </div > ) }
useCallback
useCallback
Hook:记住函数的引用,在组件每次更新时返回相同引用的函数。
useMemo
Hook:记住任意数据(数值、对象、函数等),在组件每次更新时返回相同引用的数据【功能之一】。
场景:useCallback 可以在组件每次更新时都能获取到相同引用的函数,需要配合 React.memo 高阶函数一起使用 。
作用:记忆传入的回调函数,这个被记住的回调函数会一直生效,直到依赖项发生改变。
第一个参数:必选,需要被记忆的回调函数。
第二个参数:必选,依赖项数组,用于指定回调函数中依赖(用到)的数据(类似于 useEffect 的第二个参数)。
即使没有依赖,也得传入空数组([]),此时,useCallback 记住的回调函数就会一直生效。
返回值:useCallback 记住的回调函数,直到依赖项发生改变。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { useState, useCallback } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' export default function App ( ) { const [name] = useState('ifer' ) const [age, setAge] = useState(18 ) const handleClick = () => { setAge(age + 1 ) } const updateName = useCallback(() => {}, []) console .log('app' ) return ( <div > App <button onClick ={handleClick} > +1</button > <hr /> <Child1 name ={name} updateName ={updateName} /> <Child2 age ={age} /> </div > ) }
🤔 注意:useCallback 需要配置 React.memo 使用才有意义,不然反而性能更低,因为 useCallback 来包裹函数也是需要开销的。
useMemo 使用场景:类似于 useCallback,可以在组件更新期间保持任意数据引用相等,一般用来处理对象类型的数据。
对比:useCallback 只能记忆函数,而 useMemo 可以记忆任意数据。
作用:记忆任意数据,这个被记住的数据会一直生效,直到依赖项发生改变 。
第一个参数:必选,回调函数,该回调函数一上来就会被调用,并通过返回值指定需要被记住的数据 。
第二个参数:必选,依赖项数组,用于指定回调函数中依赖的数据。同样,没有依赖项时,传入空数组([])。
返回值:useMemo 记住的数据,直到依赖项发生改变。
如何选择使用哪一个?如果处理的是函数,推荐使用 useCallback,如果处理的是其他数据(比如对象),推荐使用 useMemo。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { useState, useMemo } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' export default function App ( ) { const [name] = useState('ifer' ) const [age, setAge] = useState(18 ) const handleClick = () => { setAge(age + 1 ) } const updateName = useMemo(() => { return () => {} }, []) console .log('app' ) return ( <div > App <button onClick ={handleClick} > +1</button > <hr /> <Child1 name ={name} updateName ={updateName} /> <Child2 age ={age} /> </div > ) }
🤔 大量计算得到的结果,可以使用 useMemo 进行优化。
1 2 3 4 5 6 7 8 9 10 11 import { useMemo } from 'react' export default function App ( ) { const total = useMemo(() => { let total = 0 for (let i = 0 ; i < 1000000000 ; i++) { total += i } return total }, []) return <div > {total}</div > }
useReducer 基本使用 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 import { useReducer } from 'react' type CountAction = { type : 'INCREMENT' payload : number } function counterReducer (state: number , action: CountAction ) { switch (action.type) { case 'INCREMENT' : return state + action.payload default : return state } } export default function App ( ) { const [count, dispatch] = useReducer(counterReducer, 0 ) return ( <div > <h3 > {count}</h3 > <button onClick ={() => dispatch({ type: 'INCREMENT', payload: 1 })}>+1</button > </div > ) }
抽离 reducer store/reducers/counter.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 type CountAction = { type : 'INCREMENT' payload : number } export default function counterReducer (state: number , action: CountAction ) { switch (action.type) { case 'INCREMENT' : return state + action.payload default : return state } }
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 import { useReducer } from 'react' import counterReducer from './store/reducers/counter' export default function App ( ) { const [count, dispatch] = useReducer(counterReducer, 0 ) return ( <div > <h3 > {count}</h3 > <button onClick ={() => dispatch({ type: 'INCREMENT', payload: 1 })}>+1</button > </div > ) }
进阶使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { useReducer } from 'react' import Child1 from './components/Child1' import Child2 from './components/Child2' import counterReducer from './store/reducers/counter' import GlobalContext from './context' export default function App ( ) { const [count, dispatchCount] = useReducer(counterReducer, 0 ) return ( <GlobalContext.Provider value ={{ count , dispatchCount , }} > <Child1 /> <Child2 /> </GlobalContext.Provider > ) }
components/Child1.tsx
1 2 3 4 5 6 7 8 9 10 11 12 import { useContext } from 'react' import GlobalContext from '../context' export default function Child1 ( ) { const { count, dispatchCount } = useContext(GlobalContext) as any return ( <div > <h3 > Child1: {count}</h3 > <button onClick ={() => dispatchCount({ type: 'INCREMENT', payload: 3 })}>+3</button > </div > ) }
components/Child2.ts
1 2 3 4 5 6 7 8 9 10 11 12 import { useContext } from 'react' import GlobalContext from '../context' export default function Child1 ( ) { const { count, dispatchCount } = useContext(GlobalContext) as any return ( <div > <h3 > Child2: {count}</h3 > <button onClick ={() => dispatchCount({ type: 'INCREMENT', payload: 3 })}>+3</button > </div > ) }
forwardRef 父组件如何获取子函数组件中的 DOM。
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { useRef } from 'react' import Test from './Test' export default function App ( ) { const childInputRef = useRef<HTMLInputElement>(null ) const handleClick = () => { childInputRef.current?.focus() } return ( <div > 父组件 <button onClick ={handleClick} > 聚焦</button > <hr /> <Test ref ={childInputRef} /> </div > ) }
Test.tsx
1 2 3 4 5 6 7 8 9 10 11 12 import { forwardRef } from 'react' const Test = forwardRef<HTMLInputElement>((props, ref ) => { return ( <div > <input type ='text' ref ={ref} /> </div > ) }) export default Test
useImperativeHandle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React, { forwardRef, useImperativeHandle, useRef } from 'react' const Test = forwardRef((props, ref ) => { const inputRef = useRef() useImperativeHandle(ref, () => ({ focus : () => { inputRef.current.focus() }, })) return ( <div > <input ref ={inputRef} /> </div > ) }) export default Test
useLayoutEffect 用法和 useEffect 一致,与 useEffect 的差别是执行时机,useLayoutEffect 是在浏览器绘制节点之前执行,会阻塞 DOM 的更新!
Slow 3G 环境下测试
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 import { useState, useEffect, useLayoutEffect } from 'react' export default function App ( ) { const [msg, setMsg] = useState('Hello' ) useLayoutEffect(() => { let i = 0 while (i <= 1000000000 ) { i++ } setMsg('World' ) }, []) return ( <div className ='App' > <h1 > {msg}</h1 > </div > ) }
优先使用 useEffect,因为它是异步执行的,不会阻塞渲染。
会影响到渲染的操作尽量放到 useLayoutEffect 中去,避免出现闪烁问题。
useLayoutEffect 和 componentDidMount 是等价的,会同步调用,阻塞渲染。
useLayoutEffect 在服务端渲染的时候使用会有一个 warning,因为它可能导致首屏实际内容和服务端渲染出来的内容不一致。
getDerivedStateFromProps App.jsx
1 2 3 4 5 6 7 8 9 10 11 12 import React from 'react' import Count from './Count' const App = () => { return ( <div > <Count count ={888} /> </div > ) } export default App
Count.jsx
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 import React from 'react' export default class Count extends React .Component { constructor (props ) { super (props) console .log('#1 constructor' ) this .state = { count : 0 , } } addCount = () => { this .setState({ count : this .state.count + 1 }) } static getDerivedStateFromProps (props, state ) { console .log('#2 getDerivedStateFromProps' ) return props } render ( ) { console .log('#3 render' ) const { count } = this .state return ( <div > <h2 > {count}</h2 > <button onClick ={this.addCount} > +1</button > </div > ) } componentDidMount ( ) { console .log('#4 componentDidMount' ) } }
getSnapshotBeforeUpdate 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 import React from 'react' export default class App extends React .Component { state = { count : 0 } addCount = () => { this .setState({ count : this .state.count + 1 }) } render ( ) { console .log('#1 render' ) const { count } = this .state return ( <div > <h2 > {count}</h2 > <button onClick ={this.addCount} > +1</button > </div > ) } getSnapshotBeforeUpdate ( ) { console .log('#2 getSnapshotBeforeUpdate' ) return 'xxx' } componentDidUpdate (prevProps, prevState, snapshot ) { console .log(prevProps, prevState, snapshot) console .log('#3 componentDidUpdate' ) } }
flushSync 改变了数据,想操作 DOM,App.js
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { useState } from 'react' import { flushSync } from 'react-dom' function App ( ) { const [count, setCount] = useState(0 ) const handleClick = () => { flushSync(() => { setCount(count + 1 ) }) console .log(document .querySelector('p' ).innerHTML) } return ( <div className ='App' > <p > {count}</p > <button onClick ={handleClick} > +1</button > </div > ) } export default App