useReducer(用来状态管理
基础使用
作用: 让 React 管理多个相对关联的状态数据
import { useReducer } from 'react'
// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
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传参
做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数
// 定义reducer
import { useReducer } from 'react'
// 1. 根据不同的action返回不同的新状态
function reducer(state, action) {
console.log('reducer执行了')
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
case 'UPDATE':
return state + action.payload
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
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>
<button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}>
update to 100
</button>
</>
)
}
export default App
渲染性能优化
useMemo
使用比较少,比如说消耗非常大的计算才会使用
作用:它在每次重新渲染的时候能够缓存计算的结果
看个场景
下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费
// useMemo
// 作用:在组件渲染时缓存计算的结果
import { useState } from 'react'
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
const sumByCount = factorialOf(count)
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
useMemo缓存计算结果
思路: 只有count发生变化时才重新进行计算
import { useMemo, useState } from 'react'
function fib (n) {
console.log('计算函数执行了')
if (n < 3) return 1
return fib(n - 2) + fib(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sum = fib(count)
// 通过useMemo缓存计算结果,只有count发生变化时才重新计算
const sum = useMemo(() => {
return fib(count)
}, [count])
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
React.memo
作用:允许组件在props没有改变的情况下跳过重新渲染
组件默认的渲染机制
默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染,就是子组件会和父组件一起无脑渲染
// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染
import { useState } from 'react'
function Son() {
console.log('子组件被重新渲染了')
return <div>this is son</div>
}
function App() {
const [, forceUpdate] = useState()
console.log('父组件重新渲染了')
return (
<>
<Son />
<button onClick={() => forceUpdate(Math.random())}>update</button>
</>
)
}
export default App
使用React.memo优化
机制:只有props发生变化时才重新渲染
下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染
import React, { useState } from 'react'
// 或者从react引用,就直接memo( 子组件 )
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
const [, forceUpdate] = useState()
console.log('父组件重新渲染了')
return (
<>
<MemoSon />
<button onClick={() => forceUpdate(Math.random())}>update</button>
</>
)
}
export default App
props变化重新渲染
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
console.log('父组件重新渲染了')
const [count, setCount] = useState(0)
return (
<>
<MemoSon count={count} />
<button onClick={() => setCount(count + 1)}>+{count}</button>
</>
)
}
export default App
props的比较机制
对于props的比较,进行的是‘浅比较’,底层使用
Object.is
进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
// const [count, setCount] = useState(0)
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视为不同,子组件就会发生重新渲染
保证引用稳定
使用useMemo组件渲染过程中缓存一个值。
空数组表示:回调函数只在组件渲染完毕之后,执行一次
// const list =[1, 2, 3];
const list = useMemo(()=>{
return [1,2,3]
},[])
自定义比较函数
如果上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现
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
缓存回调函数
// useCallBack
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>
)
}
export default App
useCallback缓存函数
useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用
// useCallBack
import { memo, useCallback, 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 = useCallback((message) => {
console.log(message)
}, [])
return (
<div>
<MemoSon onGetSonMessage={onGetSonMessage} />
<button onClick={() => forceUpate(Math.random())}>update</button>
</div>
)
}
export default App
forwardRef
作用:允许组件使用ref将一个DOM节点暴露给父组件
如果不使用forwardRef是不会在父组件获取到子组件的:
import { forwardRef, useRef } from "react";
const Son = () => {
return <input></input>;
};
function App() {
const ref = useRef(null);
const focusHandle = () => {
console.log(ref);
};
return (
<div>
<Son ref={ref} />
<button onClick={focusHandle}>focus</button>
</div>
);
}
export default App;
import { forwardRef, useRef } from 'react'
// 其实这里面的回调函数就是子组件的render函数
const MyInput = forwardRef(function Input(props, ref) {
return <input {...props} type="text" ref={ref} />
}, [])
function App() {
const ref = useRef(null)
const focusHandle = () => {
console.log(ref.current.focus())
}
return (
<div>
<MyInput ref={ref} />
<button onClick={focusHandle}>focus</button>
</div>
)
}
export default App
useImperativeHandle
作用:如果我们并不想暴露子组件中的DOM而是想暴露子组件内部的方法
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
Class API(老用法,不再推荐
顾名思义,Class API就是使用ES6支持的原生Class API来编写React组件
基础体验
通过一个简单的 Counter 自增组件看一下组件的基础编写结构
// class API
import { Component } from 'react'
class Counter extends Component {
// 1. 通过类属性state定义状态数据
state = {
count: 0,
}
// 2. 定义事件回调修改状态数据
clickHandler = () => {
// 修改状态变量 触发UI组件渲染
this.setState({
count: this.state.count + 1,
})
}
// 通过render来写UI模版(JSX语法一致)
render() {
return <button onClick={this.clickHandler}>+{this.state.count}</button>
}
}
function App() {
return (
<div>
<Counter />
</div>
)
}
export default App
组件生命周期
组件卸载的时候自动执行,副作用清理的工作,例如清除定时器,清除事件绑定
组件通信
父传子
// class API
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,
})
}
// UI模版
render() {
return (
<>
<Son count={this.state.count} />
<button onClick={this.setCount}>+</button>
</>
)
}
}
export default App
子传父
在子组件标签身上绑定父组件中的函数,子组件中调用这个函数传递参数
// class API
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 })
}
// UI模版
render() {
return (
<>
<Son msg={this.state.msg} onGetSonMsg={this.onGetSonMsg} />
</>
)
}
}
export default App
zustand( 状态管理
快速上手
store/index.js - 创建store
- create的参数是一个函数,这个函数的返回值是一个对象
- set是用来修改数据的专门方法,必须用它来修改数据
- 语法1:需要用到老数据,参数是函数
- 语法2:不用老数据,参数值是一个对象
import { create } from 'zustand'
const useStore = create((set) => {
return {
count: 0,
inc: () => {
set(state => ({ count: state.count + 1 }))
// set({count:100})
}
}
})
export default useStore
app.js - 绑定组件
import useStore from './store/useCounterStore.js'
function App() {
const { count, inc } = useStore()
return <button onClick={inc}>{count}</button>
}
export default App
异步支持
对于异步操作的支持不需要特殊的操作,直接在函数中编写异步逻辑,最后把接口的数据放到set函数中返回即可
store/index.js - 创建store
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
app.js - 绑定组件
import { useEffect } from 'react'
import useChannelStore from './store/channelStore'
function App() {
const { channelList, fetchChannelList } = useChannelStore()
useEffect(() => {
fetchChannelList()
}, [fetchChannelList])
return (
<ul>
{channelList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
export default App
切片模式
场景:当我们单个store比较大的时候,可以采用一种切片模式
进行模块拆分再组合
拆分并组合切片
import { create } from 'zustand'
// 创建counter相关切片
const createCounterStore = (set) => {
return {
count: 0,
setCount: () => {
set(state => ({ count: state.count + 1 }))
}
}
}
// 创建channel相关切片
const createChannelStore = (set) => {
return {
channelList: [],
fetchGetList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({ channelList: jsonData.data.channels })
}
}
}
// 组合切片
// a是传过来的参数
const useStore = create((...a) => ({
// 展开运算符
...createCounterStore(...a),
...createChannelStore(...a)
}))
组件使用
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
对接DevTools
简单的调试我们可以安装一个 名称为 simple-zustand-devtools 的调试工具
安装调试包
npm i simple-zustand-devtools -D
配置调试工具
import create from 'zustand'
// 导入核心方法
import { mountStoreDevtool } from 'simple-zustand-devtools'
// 省略部分代码...
// 开发环境开启调试
if (process.env.NODE_ENV === 'development') {
mountStoreDevtool('channelStore', useChannelStore)
}
export default useChannelStore