Zustand 基础入门

447 阅读4分钟

zustand.jpg

安装

// NPM
npm install zustand

创建一个 store并使用它

// store.js
import { create } from 'zustand'

export const useStore = create(
  (set) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
    removeAllBears: () => set({ bears: 0 }),
    updateBears: (newBears) => set({ bears: newBears }),
	})
)

// component.jsx
import { useStore } from 'store'

function BearCounter () {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

create中的异步函数可直接使用async/await的方式去调用,不需要额外下载中间件。

多层state

如果state是简单结构时,可以直接像上面代码中直接赋值:

set((state) => ({ bears: state.bears + 1 }))

如果state是深层嵌套结构时,必须使用扩展运算符(...)先将原来数据拷贝,然后再做操作:

export const useStore = create(
  (set) => ({
    cats: {
      bigCats: 0,
      smallCats: 0
    },
    increaseBigCats: () => set(
    	(state) => {
        cats: {
          ...state.cats,
          bigCats: state.cats.bigCats + 1
        }
      }
    ),
	})
)

如果希望不使用扩展运算符,直接对state中的值操作需要使用immer

get方法使用

当我们想在state同级目录下使用state中的值时,可以使用get方法

export const useStore = create(
  (set, get) => ({
    cats: {
      bigCats: 0,
      smallCats: 0
    },
    summary: () => {
      const total = get().cats.bigCats + get().cats.smallCats
      return total
    }
	})
)

immer安装

// NPM
npm install immer

immer使用

export const useStore = create(
  immer(
  	(set) => ({
      cats: {
        bigCats: 0,
        smallCats: 0
      },
      increaseBigCats: () => set(
        (state) => {
          cats: {
            state.cats.bigCats++
          }
        }
      ),
    })
  )
)

Selector 使用

问题

当我们要使用store中值的时候需要按照如下方式:

const summary = catStore(state => state.summary)
const increaseBigCats = catStore(state => state.increaseBigCats)
const increaseSmallCats = catStore(state => state.increaseSmallCats)

只需要引入某几个数据的时候倒是无所谓,但如果我们需要引入多条数据时就会很麻烦。

下面的写法确实省事,不过只会引起页面重渲染。当项目中多处需要使用store的时候会导致越来越卡。

const { summary, increaseBigCats, increaseSmallCats } = catStore()

解决办法

详细内容可以参考官方文档,这里提供直接处理方式。

  1. 创建一个 createSelectors util
// utils/createSelectors.js
export const createSelectors = (_store) => {
    let store = _store
    store.use = {}
    for (let k of Object.keys(store.getState())) {
        ; (store.use)[k] = () => store((s) => s[k])
    }

    return store
}
  1. store文件中用createSelector包裹create
// store.js
import { createSelectors } from './createSelectors'

export const useStore = createSelectors(
	create(
  	...code
  )
)
  1. 组件中使用
// component.jsx
import { useStore } from 'store'

export const App () {
  const increaseBigCats = useStore.use.increaseBigCats()
  const increaseSmallCats = useStore.use.increaseSmallCats()
  const cats = useStore.use.cats()
  
  return (
  	<div>{cats.smallCats}</div>
    <div>
      <button onClick={increaseBigCats}>increaseBigCats</button>
      <button onClick={increaseSmallCats}>increaseSmallCats</button>
    </div>
  )
} 

useStore.use只能返回第一层的数据,如果state中结构比较复杂需要认为xxx.xxx.xxx的方式去调用

  1. 如果想一次性返回多个state可以使用下面的写法
// component.jsx
import { useStore } from 'store'
import { shallow } from 'zustand/shallow'

export const App = () => {
  const { increaseBigCats, increaseSmallCats, cats } = useStore(
        state => ({
            increaseBigCats: state.increaseBigCats,
            increaseSmallCats: state.increaseSmallCats,
            cats: state.cats,
        }),
        shallow
    )
  
  return (
  	<div>
    	<div>smallCats: { cats.smallCats }</div>
      <div>
        <button onClick={increaseBigCats}>increaseBigCats</button>
        <button onClick={increaseSmallCats}>increaseSmallCats</button>
      </div>
    </div>
  )
}

这里的shallowzustand/shallow提供给我们的比较函数。

上面写法里,useStore中返回的对象每次都是重新产生的,它会去帮我们比较每一次第一层的值是否一样。

如果两次比较的值都是一样的,它会默认两个对象是相等的,则不会使得组件重渲染。

如果不添加shallow函数,则每次store中的值改变后,会导致组件重渲染。

这里提供给我们的shallow只会比较第一层的值,如果有需要可以自己写一个比较函数

devtools使用

安装Redux DevTools

谷歌科学上网后直接在应用商店搜索下载即可

使用Redux DevTools

devtools有两个参数。

  • 原先 store中的内容
  • devtools配置,详情看这里
// store.js
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

export const useStore = create(
	devtools(
  	(set, get) => ({...code}),
    {
      // 配置文件
      ...options
    }
  )
)

数据持久化

zustand配置数据持久化不需要我们去手动的localStorage.getItem/setItem,他给我们提供了一个中间件persist

persist有两个参数

  • 原先store中的内容

  • persist配置项,这里的配置项是必填参数。完整文档查看这里

// store.js
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

export const useStore = create(
	persist(
  	(set, get) => ({...code}),
    {
      /**
      * 配置文件,这只展示常用的几个
      * name: 'self-store',  // storage唯一标识
      * storage:  createJSONStorage(() => sessionStorage),  // 修改存储位置
      * partialize: state => ({ xxx: state.xxx })  // 只缓存 xxx
      */
      ...options
    }
  )
)

清除缓存

// component.jsx
import { useStore } from 'store'

export const App () {
  return (
  	<div>
    	<button onClick={useStore.persist.clearStorage}>clear storage</button>
    </div>
  )
}

我们可以直接使用对应storepersist.clearStorage方法,即可清除对应缓存。

要注意的是,该方法只会清除对应storage,而不会清除对应memory

如果有reset的需求建议直接在store中添加一个reset方法。

immerdevtoolspersist顺序

当我们既要用immer又要用devtoolspersist时,按照以下顺序就可以了

// store.js
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
import { devtools, persist } from 'zustand/middleware'

export const useStore = create(
	immer(
  	devtools(
    	persist(
      	(set, get) => ({ ...code })
      )
    )
  )
)

subscribe

我对于subscribe的理解就是它相当于Vue中的watchEffect

文档中有写,最好把subscribeuseEffect结合使用,以便在卸载时自动取消订阅

// component.jsx
import { useEffect } from 'react'
import { useStore } from 'store'

export const App = () => {
  
  useEffect(() => {
    const unsub = useStore.subscribe((state, prevState) => {
      console.log('state >>> ', state)
      console.log('prevState >>> ', prevState)
    })
    
    return unsub
  }, [])
  
  return (
  	<div></div>
  )
}

subscribeWithSelector

如果说subscribewatchEffect,那subscribeWithSelector我觉得就是watch,可以订阅部分state的变化。具体详情可以去 github 上看,这里就不赘述了。

唯一要注意的是如果要使用subscribeWithSelector,需要在store中用subscribeWithSelector包裹原先的store,其他的都和subscribe大差不差。

// store.hs
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'

export const useStore = create(
  subscribeWithSelector(
    (set, get) => ({ ...code })
  )
)

subscribeWithSelector 在中间中的顺序

// store.js
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware'

export const useStore = create(
	immer(
  	devtools(
      subscribeWithSelector(
      	persist(
          (set, get) => ({ ...code })
        )
      )
    )
  )
)

若有问题欢迎指正。