一、useReducer

作用: 和useState的作用类似,用来管理相对复杂的状态数据,让 React 管理多个相对关联的状态数据

在实际开发中不是非用不可的hook,可以有选择的对它进行使用

image

useReducer-基础用法
1.定义一个reducer函数(根据不同的action返回不同的新状态)
// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {
  switch (action.type) {
    case 'INC':
      return state + 1
    case 'DEC':
      return state - 1
    default:
      return state
  }
}
2.在组件中调用useReducer,并传入reducer函数和状态的初始值
const [state, dispatch] = useReducer(reducer, 0)
3.事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
 
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
      {state}
<button onClick={() => dispatch({ type: 'INC' })}>+</button>

完整代码

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

更新流程

image

分派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 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

二、渲染性能优化

1、useMemo

作用:它在每次重新渲染的时候能够缓存计算的结果

useMemo - 基础语法
useMemo(()=>{
  // 根据count1返回计算的结果
},[count1])
第一个参数:函数的目的是返回计算的结果的,第二个参数是数组,表示count1发生变化的时候,第一个参数即函数就会重新执行,如果count1不发生变化,则永远使用的是缓存之后的结果。
说明:使用useMemo做缓存之后可以保证只有count1依赖项发生变化时才会重新计算

image

组件有两个状态count1和count2,现在的需求是基于count1进行斐波那契数列求和,意思是只要count1发生变化,fn这个函数才会重新执行,思考一下,当组件内部的count2状态发生变化的时候,fn会重新执行吗?也会执行,因为我们知道不管哪个状态数据发生变化,组件函数都会重新执行(函数执行时的参数仍然是count1,不是count2),达到重新渲染的目的,位于组件内部的fn也会一起执行,这显然是不合理的,因为fn只跟count1相关,但是count2变化的时候居然也会执行,显然是存在浪费的,针对这个场景,useMemo这个hook函数保证只有count1发生变化的时候,fn才会重新执行,达到缓存结果的目的

下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费

// useMemo
// 作用:在组件渲染时缓存计算的结果

import { useState } from 'react'

function factorialOf(n) {
  console.log('斐波那契函数执行了',n)
  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 (
    <>
      {sumByCount}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}
export default App

效果如下:我们发现点击+num后,factorialOf(n)也执行了

image

num 改变会执行 factorialOf(count),根本原因不是 num 和 factorialOf 有关系,而是:num 是 App 组件的 state,它一变,App 整个函数就会重新执行一遍,函数体里所有语句都会重跑,包括 const sumByCount = factorialOf(count) 这一行,此时factorialOf函数执行时的参数仍然是count,而不是num。

useMemo缓存计算结果

思路: 只有count发生变化时才重新进行计算

// useMemo
// 作用:在组件渲染时缓存计算的结果

import { useState,useMemo } from 'react'

function factorialOf(n) {
  console.log('斐波那契函数执行了',n)
  return n <= 0 ? 1 : n * factorialOf(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  // const sumByCount = factorialOf(count)
 // 通过useMemo缓存计算结果,只有count发生变化时才重新计算
  const sumByCount = useMemo(() => {
    return factorialOf(count)
  }, [count])
  const [num, setNum] = useState(0)

  return (
    <>
      {sumByCount}
      <button onClick={() => setCount(count + 1)}>+count:{count}</button>
      <button onClick={() => setNum(num + 1)}>+num:{num}</button>
    </>
  )
}
export default App

发现num值得改变不会重新执行factorialOf函数

2、React.memo

作用:允许组件在props没有改变的情况下跳过重新渲染

组件默认的渲染机制

默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染

React组件默认的渲染机制:只要父组件重新渲染子组件就会重新渲染

image

App是父组件,Son是子组件,如果因为状态的变化引发了App组件的重新渲染,那么Son组件就会跟着一起渲染

思考:如果Son组件本身并不需要做渲染更新,是不是存在浪费?是的,React.memo函数可以在props没有改变的时候跳过渲染
语法: 
const MemoComponent = React.memo(function SonComponent(props){
  // ...
})

说明:经过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

效果如下:

image

使用React.memo优化

机制:只有props发生变化时才重新渲染,下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染

// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染

import { useState,memo } from 'react'

// function Son() {
//   console.log('子组件被重新渲染了')
//   return <div>this is son</div>
// }
const MemoSon = memo(function Son(props){
  console.log('子组件被重新渲染了')
  return <div>this is son</div>
})
function App() {
  // useState() 不传初始值,返回 [undefined, setState函数]
  // 用数组解构的逗号占位,只取第二个元素(更新函数),忽略第一个状态值
  // 变量名 forceUpdate 是自定义的,它其实就是 setState
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
    <>
      <MemoSon />
       {/* 点击时调用 forceUpdate(Math.random()),传入一个随机数作为新 state */}
       {/* 因为每次 Math.random() 结果都不同,React 认为 state 变了 → 触发 App 重新渲染 */}
      <button onClick={() => forceUpdate(Math.random())}>update</button>
    </>
  )
}

export default App

效果如下:

image

3、props变化重新渲染

import React, { useState } from 'react'

const MemoSon = React.memo(function Son(props){
  console.log('子组件被重新渲染了',props.count)
  return <div>this is son</div>
})
function App() {
  console.log('父组件重新渲染了')

  const [count, setCount] = useState(0)
  return (
    <>
      <MemoSon count={count} />
      <button onClick={() => setCount(count + 1)}>+{count}</button>
    </>
  )
}

export default App

效果:

image

props的比较机制

对于props的比较,进行的是‘浅比较’,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性

机制: 在使用memo缓存组件之后,React会对每一个 prop 使用 Object.is 比较新值和老值,返回true,表示没有变化
比较对象结果
1 === 1 true
'a' === 'a' true
[1,2,3] === [1,2,3] false
{a:1} === {a:1} false
(1)、prop是简单类型,Object.is(3, 3) => true 没有变化
先来演示props发生变化的情况
import React, { useState } from 'react'

const MemoSon = React.memo(function Son(props) {
  console.log('子组件被重新渲染了',props.count)
  return <div>this is Son</div>
})

function App() {
  const [count, setCount] = useState(0)
  return (
    <>
      <MemoSon count={count} />
      <button onClick={() => setCount(count+1)}>
        Change Count
      </button>
    </>
  )
}

export default App

效果:

image

再来演示props不发生变化的情况

import React, { useState } from 'react'

const MemoSon = React.memo(function Son(props) {
  console.log('子组件被重新渲染了',props.count)
  return <div>this is Son</div>
})

function App() {
  const [count, setCount] = useState(0)
  const num = 100
  return (
    <>
      <MemoSon count={num} />
      <button onClick={() => setCount(count+1)}>
        Change Count
      </button>
    </>
  )
}

export default App

效果:初始化时子组件被渲染了一次,后面不会再被渲染

image

虽然在点击的时候,count发生变化,父组件重新渲染,但是父组件给子组件传递的值一直是100,子组件不再被渲染

(2)、prop是引用类型(对象 / 数组),Object.is([], []) => false 有变化,React只关心引用是否变化
import React, { useState } from 'react'

const MemoSon = React.memo(function Son(props) {
  console.log('子组件被重新渲染了',props.list)
  return <div>this is Son</div>
})

function App() {
  const [count, setCount] = useState(0)
  const list = [1,2,3]
  return (
    <>
      <MemoSon list={list} />
      <button onClick={() => setCount(count+1)}>
        Change Count
      </button>
    </>
  )
}

export default App

效果:

image

点击按钮,父组件会重新渲染,list始终是数组1,2,3,看起来子组件不会发生渲染,而实际上子组件也会跟着渲染,因为点击按钮会引发父组件的重新渲染,function App函数会重新执行,代码const list = [1,2,3]也会重新执行,而list每次都是一个全新的引用,引用不相等就会重新渲染。

那有没有办法保证list引用是稳定的呢?使用useMemo实现组件渲染过程中缓存一个值,
import React, { useState,useMemo } from 'react'

const MemoSon = React.memo(function Son(props) {
  console.log('子组件被重新渲染了',props.list)
  return <div>this is Son</div>
})

function App() {
  const [count, setCount] = useState(0)
  // 创建一个 list,它的值是 [1, 2, 3],并且这个数组在整个组件生命周期中只创建一次,
  // 之后每次重新渲染都复用同一个数组引用。 依赖数组 [] 表示"永不重新计算"。
  const list = useMemo(()=>{
      return [1,2,3]
  },[])
  return (
    <>
      <MemoSon list={list} />
      <button onClick={() => setCount(count+1)}>
        Change Count
      </button>
    </>
  )
}

export default App
效果如下:

image

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视为不同,子组件就会发生重新渲染

点击按钮时 setList([1, 2, 3]) 传入的是一个全新的数组(虽然内容一样),React.memo 做的是浅比较,发现 list 的引用变了 → 认为 props 变了 → MemoSon 依然会重新渲染。也就是说,这里的 memo 没起到优化作用。

怎么才能真正生效?自定义比较函数(第二个参数)

import React, { useState } from 'react'

const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
},
  (prevProps, nextProps) => {
    // 返回 true 表示"props 相等",跳过渲染
    return JSON.stringify(prevProps.list) === JSON.stringify(nextProps.list)
  })

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

这样即使引用变了,只要内容一样,就跳过渲染。

4、自定义比较函数

如果上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现

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

5、useCallback

上一小节我们说到,当给子组件传递一个引用类型prop的时候,即使我们使用了memo 函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback缓存回调函数

// useCallBack

import { memo, useState } from 'react'

const Input = memo(function Input({onChange}) { // memo 期望:props 不变时跳过渲染
  console.log('子组件组件渲染了')
  // Input 接收一个 prop:onChange
  return <input type="text" onChange={(e)=>onChange(e.target.value)}></input>
})

function App() {
  // 传给子组件的函数(函数为引用类型)
  // 父组件 App 每次重新渲染,changeHandler 都会被重新创建成一个新函数(引用变了)
  const changeHandler = (value) => console.log(value)
  // 触发父组件重新渲染的函数
  const [count,setCount] = useState(0)

  return (
    <div>
      <Input onChange={changeHandler} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

export default App

效果如下:

image

useCallback缓存函数

useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用

// useCallBack

import { memo, useState,useCallback } from 'react'

const Input = memo(function Input({onChange}) { // memo 期望:props 不变时跳过渲染
  console.log('子组件组件渲染了')
  // Input 接收一个 prop:onChange
  return <input type="text" onChange={(e)=>onChange(e.target.value)}></input>
})

function App() {
  // 传给子组件的函数(函数为引用类型)
  //把 (value) => console.log(value) 这个函数缓存起来,
  // 依赖数组 [] 表示它只在首次渲染时创建一次,之后每次重新渲染都复用同一个函数引用。
  const changeHandler = useCallback((value) => console.log(value),[]) 
  // 触发父组件重新渲染的函数
  const [count,setCount] = useState(0)

  return (
    <div>
      <Input onChange={changeHandler} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

export default App

效果如下:

image

三、forwardRef+useImperativeHandle

1、forwardRef

作用:允许子组件使用ref将一个DOM节点暴露给父组件

image

语法实现
import { forwardRef,useRef } from 'react'

const Input = forwardRef((props,ref) => { // memo 期望:props 不变时跳过渲染
  return <input type="text" ref = {ref}></input>
})

function App() {
  const inputRef = useRef(null)
  return (
    <div>
      <Input ref={inputRef} />
    </div>
  )
}

export default App

在父组件中使用useRef这个hook函数得到一个inputRef对象,把inputRef通过ref绑定到子组件,子组件使用forwardRef进行包裹,在子组件里面把inputRef再次通过ref标识绑定到你想要获取的input元素上去,看起来像透传的机制,在父组件中就可以通过inputRef对象拿到子组件的input元素了。

import { forwardRef,useRef } from 'react'

const Input = forwardRef((props,ref) => { // memo 期望:props 不变时跳过渲染
  return <input type="text" ref = {ref}></input>
})

function App() {
  const inputRef = useRef(null)
  const showRef = () =>{
    console.log(inputRef)
    inputRef.current.focus()
  }
  return (
    <div>
      <Input ref={inputRef} />
      <button onClick={showRef}>focus</button>
    </div>
  )
}

export default App

效果如下:

image

image

2、useImperativeHandle

作用:如果我们并不想暴露子组件中的DOM而是想暴露子组件内部的方法

image

focus方法在子组件内部自行实现的,现在想要在父组件中通过ref调用子组件的focus方法实现子组件input输入框的聚焦

image

完整代码如下:

import { forwardRef,useImperativeHandle,useRef } from 'react'

const Input = forwardRef((props,ref) => { // memo 期望:props 不变时跳过渲染
  const inputRef = useRef(null)
  // 实现聚焦逻辑函数
  const focusHandler = ()=>{
    inputRef.current.focus()
  }
  // 暴露函数给父组件使用
  useImperativeHandle(ref,()=>{
    return {focusHandler}
  })
  return <input type="text" ref = {inputRef}></input>
})

function App() {
  const sonRef = useRef(null)
  const focus = () =>{
    console.log(sonRef.current)
    sonRef.current.focusHandler()
  }
  return (
    <div>
      <Input ref={sonRef} />
      <button onClick={focus}>focus</button>
    </div>
  )
}

export default App

效果:

image

image

四、Class API

类组件就是通过JS中的类来组织组件的代码

image

1. 通过类属性state定义状态数据
2. 通过setState方法来修改状态数据
3. 通过render来写UI模版(JSX语法一致)

顾名思义,Class API就是使用ES6支持的原生Class API来编写React组件

通过一个简单的 Counter 自增组件看一下组件的基础编写结构

import { Component } from 'react'

class Counter extends Component{
  // 定义状态变量
  state = {
    count: 0
  }
  // 事件回调
  clickHandler = ()=>{
    this.setState({
      count: this.state.count + 1
    })
  }
  //UI模板(JSX)
  render(){
    return <button onClick={this.clickHandler}>{this.state.count}</button>
  }
}
function App() {
  return (
    <div>
      <Counter/>
    </div>
  )
}

export default App

效果:

image

类组件的生命周期函数

概念:组件从创建到销毁的各个阶段自动执行的函数就是生命周期函数

image

1. componentDidMount:组件挂载完毕自动执行 - 异步数据获取
2. componentWillUnmount: 组件卸载时自动执行 - 清理副作用(清除定时器、清除事件绑定)
3、componentDidUpdate:组件更新时自动执行
import { Component, useState } from 'react'

class Son extends Component{
  // 声明周期函数
  // 组件渲染完毕自动执行一次
  componentDidMount(){
    console.log('组件渲染完毕,请求发送起来')
    // 开启定时器
    this.timer = setInterval(()=>{
      console.log('定时器运行中')
    },1000)
  }
  // 组件卸载的时候执行
  componentWillUnmount(){
    console.log('组件Son被卸载了')
    // 清除定时器
    clearInterval(this.timer)
  }  //UI模板(JSX)
  render(){ //render函数是必须要有的
    return <div>i am Son</div>
  }
}
function App() {
  const [show,setShow] = useState(true)
  return (
    <div>
      {show && <Son/>}
      <button onClick={()=>setShow(false)}>卸载子组件</button>
    </div>
  )
}

export default App
效果如下:

image

组件通信

概念:类组件和Hooks编写的组件在组件通信的思想上完全一致

1. 父传子:通过prop绑定数据

2. 子传父:通过prop绑定父组件中的函数,子组件调用

3. 兄弟通信:状态提升,通过父组件做桥接

父传子:直接通过props,子组件标签身上绑定父组件中的数据即可

import { Component, useState } from 'react'

class Son extends Component{
  // 使用this.props.msg
  render(){ //render函数是必须要有的
    return <div>i am Son{this.props.msg}</div>
  }
}
class Parent extends Component{
  state = {
    msg: 'this is parent msg'
  }
  render(){ //render函数是必须要有的
    return <div>i am Parent<Son msg={this.state.msg}/></div>
  }
}
function App() {
  const [show,setShow] = useState(true)
  return (
    <div>
      <Parent/>
    </div>
  )
}

export default App

效果:

image

案例2

// 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

子传父:在子组件标签身上绑定父组件中的函数,子组件中调用这个函数传递参数

import { Component, useState } from 'react'

class Son extends Component{
  // 使用this.props.msg
  render(){ //render函数是必须要有的
    return <div>
      <div>i am Son{this.props.msg}</div>
      <button onClick={()=>this.props.onGetSonMsg('我是Son组件中的数据')}>sendMsgToParent</button>
    </div>
  }
}
class Parent extends Component{
  state = {
    msg: 'this is parent msg'
  }
  getSonMsg = (sonMsg)=>{
    console.log(sonMsg)
  }
  render(){ //render函数是必须要有的
    return <div>i am Parent<Son msg={this.state.msg} onGetSonMsg={this.getSonMsg}/></div>
  }
}
function App() {
  const [show,setShow] = useState(true)
  return (
    <div>
      <Parent/>
    </div>
  )
}

export default App

效果如下:

image

案例2

// 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

import { create } from 'zustand'

const useStore = create((set) => {
  return {
    count: 0,
    inc: () => {
      set(state => ({ count: state.count + 1 }))
    }
  }
})

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 })
    }
  }
}

// 组合切片
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

打开 React调试工具

image

 

posted on 2026-09-17 11:20  周文豪  阅读(5)  评论(0)    收藏  举报