useReducer
作用:和 useState 的作用类似,用来管理相对复杂的状态数据
案例:编写一个能够加减的函数
- 定义一个reducer函数(根据不同的action返回不同的新状态)
- 在组件中调用useReducer,并传入reducer函数和状态的初始值
- 事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
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 { useReducer } from 'react'
function reducer(state, action) { switch (action.type) { case 'INC': return state + 1 case 'DEC': return state - 1 default: return state } }
function App() { const [state, dispatch] = useReducer(reducer, 0) return ( <> {/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */} <button onClick={() => dispatch({ type: 'DEC' })}>-</button> {state} <button onClick={() => dispatch({ type: 'INC' })}>+</button> </> ) }
export default App
|
分派 action 时传参:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function reducer(state, action) { switch (action.type) { case 'UPDATE': return state + action.payload default: return state } }
function App() { const [state, dispatch] = useReducer(reducer, 0) return ( <> {state} <button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}> update to 100 </button> </> ) }
export default App
|
useReducer原理:
渲染性能优化
useMemo
作用:在组件每次重新渲染的时候缓存计算的结果
1 2 3
| useMemo(() => { }, [count])
|
案例:计算斐波那契数列,count1需要进行计算并重新渲染组件
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
| import { useState } from "react"; function fib(n) { console.log('计算函数执行了'); if (n < 3) { return 1 } return fib(n - 2) + fib(n - 1) } function App () { const [count1, setCount1] = useState(0) const [count2, setCount2] = useState(0) const result = fib(count1) console.log('组件更新'); return ( <div> <button onClick={() => setCount1(count1 + 1)}>改变count1:{count1}</button> <button onClick={() => setCount2(count2 + 1)}>改变count2:{count2}</button> {result} </div> ) } export default App;
|
上面的代码每次组件重新渲染都回重新执行一次计算函数,即使是count2也是如此,是没有意义的但是会消耗性能
1 2 3 4
| const result = useMemo(() => { return fib(count1) }, [count1])
|
useMemo 会缓存计算结果,而不会重新渲染,只有依赖项变化才会计算
React.memo
作用:允许组件在Props没有改变的情况下跳过渲染
React组件默认的渲染机制:只要父组件重新渲染子组件就会重新渲染
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 React, { useState } from 'react'
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> })
function App() { const [, forceUpdate] = useState()
const [count, setCount] = useState(0)
console.log('父组件重新渲染了') return ( <> <MemoSon /> <button onClick={() => forceUpdate(Math.random())}>update</button> <MemoSon count={count} /> <button onClick={() => setCount(count + 1)}>+{count}</button> </> ) }
export default App
|
子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染
props 的比较机制
对于props的比较,进行的是浅比较,底层使用 Object.is
进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性
prop是简单类型:Object.is(3, 3) => true
没有变化
prop是引用类型(对象 / 数组):Object([], []) => false
有变化,React 只关心引用是否变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { useState } from 'react'
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> })
function App() { const [list, setList] = useState([1, 2, 3]) return ( <> <MemoSon list={list} /> <button onClick={() => setList([1, 2, 3])}> {JSON.stringify(list)} </button> </> ) }
export default App
|
虽然两次的 list 状态都是 [1,2,3]
, 但是因为组件 App 俩次渲染生成了不同的对象引用 list,所以传给 MemoSon组件的 props 视为不同,子组件就会发生重新渲染
自定义比较函数
不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现
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
| import React, { useState } from 'react'
function arePropsEqual(oldProps, newProps) { console.log(oldProps, newProps) return ( oldProps.list.length === newProps.list.length && oldProps.list.every((oldItem, index) => { const newItem = newProps.list[index] console.log(newItem, oldItem) return oldItem === newItem }) ) }
const MemoSon = React.memo(function Son() { console.log('子组件被重新渲染了') return <div>this is span</div> }, arePropsEqual)
function App() { console.log('父组件重新渲染了') const [list, setList] = useState([1, 2, 3]) return ( <> <MemoSon list={list} /> <button onClick={() => setList([1, 2, 3])}> 内容一样{JSON.stringify(list)} </button> <button onClick={() => setList([4, 5, 6])}> 内容不一样{JSON.stringify(list)} </button> </> ) }
export default App
|
useCallback
作用:在组件多次重新渲染的时候缓存函数
当给子组件传递一个引用类型prop(例如函数)的时候,即使我们使用了memo
函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback
缓存回调函数
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { memo, useState } from 'react'
const MemoSon = memo(function Son() { console.log('Son组件渲染了') return <div>this is son</div> })
function App() { const [, forceUpate] = useState() console.log('父组件重新渲染了') const onGetSonMessage = (message) => { console.log(message) }
return ( <div> <MemoSon onGetSonMessage={onGetSonMessage} /> <button onClick={() => forceUpate(Math.random())}>update</button> </div> ) }
|
父组件每次渲染,传回去的onGetSonMessage
函数都是新的引用,所以子组件也会渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function App() { const onGetSonMessage = useCallback((message) => { console.log(message) }, [])
return ( <div> <MemoSon onGetSonMessage={onGetSonMessage} /> <button onClick={() => forceUpate(Math.random())}>update</button> </div> ) }
export default App
|
useCallback 缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用
React.forwardRef
作用:允许组件使用ref将一个DOM节点暴露给父组件
案例:通过 ref 获取到子组件内部的 input 元素让其聚焦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { forwardRef, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) { return <input {...props} type="text" ref={ref} /> }, [])
function App() { const ref = useRef(null)
const focusHandle = () => { ref.current.focus() }
return ( <div> <MyInput ref={ref} placeholder="请输入" /> <button onClick={focusHandle}>focus</button> </div> ) }
export default App
|
useInperativeHandle
作用:通过ref暴露子组件中的方法
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
| import { forwardRef, useImperativeHandle, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) { const inputRef = useRef(null) const focus = () => inputRef.current.focus()
useImperativeHandle(ref, () => { return { focus, } })
return <input {...props} ref={inputRef} type="text" /> })
function App() { const ref = useRef(null)
const focusHandle = () => ref.current.focus()
return ( <div> <MyInput ref={ref} /> <button onClick={focusHandle}>focus</button> </div> ) }
export default App
|
用 ref 将组件内部的方法暴露给父组件
Class API
类组件基础结构
类组件 是老版本的用法,现在已经不再推荐
类组件就是通过 JS 中的类来组织组件的代码
- 通过类属性 state 定义状态数据
- 通过 setState 方法来修改状态数据
- 通过 render 来写UI模版(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
| import { Component } from 'react'
class Counter extends Component { state = { count: 0, }
clickHandler = () => { this.setState({ count: this.state.count + 1, }) }
render() { return <button onClick={this.clickHandler}>+{this.state.count}</button> } }
function App() { return ( <div> <Counter /> </div> ) }
export default App
|
生命周期函数
组件从创建到销毁的各个阶段自动执行的函数就是生命周期函数
- componentDidMount:组件挂载完毕自动执行 - 异步数据获取
- componentWillUnmount: 组件卸载时自动执行 - 清理副作用
组件通信
父传子
通过 prop 绑定数据
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'
class Son extends Component { render() { const { count } = this.props return <div>this is Son, {count}</div> } }
class App extends Component { state = { count: 0, }
setCount = () => { this.setState({ count: this.state.count + 1, }) }
render() { return ( <> <Son count={this.state.count} /> <button onClick={this.setCount}>+</button> </> ) } }
export default App
|
子传父
通过 prop 绑定父组件中的函数,子组件调用
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
| import { Component } from 'react'
class Son extends Component { render() { const { msg, onGetSonMsg } = this.props return ( <> <div>this is Son, {msg}</div> <button onClick={() => onGetSonMsg('this is son msg')}> changeMsg </button> </> ) } }
class App extends Component { state = { msg: 'this is initail app msg', }
onGetSonMsg = (msg) => { this.setState({ msg }) }
render() { return ( <> <Son msg={this.state.msg} onGetSonMsg={this.onGetSonMsg} /> </> ) } }
export default App
|
zustand
zustand 是状态管理工具,是 Redux 的平替
store/index.js - 创建store
1 2 3 4 5 6 7 8 9 10 11 12
| import { create } from 'zustand'
const useStore = create((set) => { return { count: 0, inc: () => { set(state => ({ count: state.count + 1 })) } } })
export default useStore
|
app.js - 绑定组件
1 2 3 4 5 6 7 8
| import useStore from './store/useCounterStore.js'
function App() { const { count, inc } = useStore() return <button onClick={inc}>{count}</button> }
export default App
|
异步操作
对于异步的支持不需要特殊的操作,直接在函数中编写异步逻辑,最后只需要调用 set方法 传入新状态即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { create } from 'zustand'
const URL = 'http://geek.itheima.net/v1_0/channels'
const useStore = create((set) => { return { count: 0, ins: () => { return set(state => ({ count: state.count + 1 })) }, channelList: [], fetchChannelList: async () => { const res = await fetch(URL) const jsonData = await res.json() set({channelList: jsonData.data.channels}) } } })
export default useStore
|
切片模式
场景:当单个store比较大的时候,可以采用 切片模式 进行模块拆分组合,类似于模块
拆分并组合切片
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 { create } from 'zustand'
const createCounterStore = (set) => { return { count: 0, setCount: () => { set(state => ({ count: state.count + 1 })) } } }
const createChannelStore = (set) => { return { channelList: [], fetchGetList: async () => { const res = await fetch(URL) const jsonData = await res.json() set({ channelList: jsonData.data.channels }) } } }
const useStore = create((...a) => ({ ...createCounterStore(...a), ...createChannelStore(...a) }))
|
组件使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function App() { const {count, inc, channelList, fetchChannelList } = useStore() return ( <> <button onClick={inc}>{count}</button> <ul> {channelList.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ) }
export default App
|
简单的调试我们可以安装一个 名称为 simple-zustand-devtools 的调试工具
安装调试包
1
| npm i simple-zustand-devtools -D
|
配置调试工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import create from 'zustand'
import { mountStoreDevtool } from 'simple-zustand-devtools'
if (process.env.NODE_ENV === 'development') { mountStoreDevtool('channelStore', useChannelStore) }
export default useChannelStore
|
打开 React调试工具