自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我們一起聊聊70 行代碼實(shí)現(xiàn) Zustand 核心功能

開(kāi)發(fā) 前端
React 和 Vue 除了自帶的狀態(tài)管理 API,同時(shí)還有一些功能強(qiáng)大的狀態(tài)管理庫(kù)可供選擇。Vue 常見(jiàn)的狀態(tài)管理庫(kù)有 Vuex 和 Pinia,React 狀態(tài)管理相對(duì)更多,有 redux、mobox、zustand、jotai 等等。

前端目前主流的開(kāi)發(fā)技術(shù)棧如 React、Vue 等都是狀態(tài)數(shù)據(jù)驅(qū)動(dòng) UI 更新(即 UI = f(state)),所以狀態(tài)管理是項(xiàng)目開(kāi)發(fā)的重要一環(huán)。

React 和 Vue 除了自帶的狀態(tài)管理 API,同時(shí)還有一些功能強(qiáng)大的狀態(tài)管理庫(kù)可供選擇。Vue 常見(jiàn)的狀態(tài)管理庫(kù)有 Vuex 和 Pinia,React 狀態(tài)管理相對(duì)更多,有 redux、mobox、zustand、jotai 等等。

在 React 中,redux 還是最熱門(mén)的狀態(tài)管理庫(kù),相信你肯定在 React 開(kāi)發(fā)中有使用過(guò)它。其他的狀態(tài)庫(kù),都有各自的設(shè)計(jì)理念,在某些場(chǎng)景和開(kāi)發(fā)規(guī)范,它們可能更適合你的項(xiàng)目。

本文將介紹 zustand 的核心實(shí)現(xiàn),zustand 庫(kù)和 redux 類(lèi)似,都參考了 flux 設(shè)計(jì)理念,它一些特點(diǎn)如下:

  • 易于上手,學(xué)習(xí)成本低
  • 輕量級(jí)設(shè)計(jì),gzip 壓縮后僅 1KB
  • TypeScript 友好,有助于提升代碼質(zhì)量和開(kāi)發(fā)體驗(yàn)
  • 強(qiáng)大的可擴(kuò)展性,通過(guò)中間件可以實(shí)現(xiàn)日志,數(shù)據(jù)持久化等能力
  • zustand 在設(shè)計(jì)上注重性能,采用高效的更新機(jī)制減少不必要的渲染,同時(shí)支持狀態(tài)分片。

基于上述特點(diǎn),zustand 還是比較受歡迎的,你可以看到 zustand 的使用量是排在前頭的。

圖片圖片

Zustand 的使用

zustand 的使用起來(lái)很簡(jiǎn)單, 使用 create 創(chuàng)建一個(gè) useStore,可以把狀態(tài)值和更新?tīng)顟B(tài)函數(shù)都保存在 state 中,隨后在組件中調(diào)用即可。

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 1,
  // 通過(guò) set 方法更新?tīng)顟B(tài)值,set 支持傳入函數(shù)和狀態(tài)對(duì)象值
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

function Counter() {
  const count = useStore((state) => state.count)
  const inc = useStore((state) => state.inc)
  return (
    <div>
      <p>{`Count: ${count}`}</p>
      <button onClick={inc}>+1</button>
    </div>
  )
}

代碼體驗(yàn)地址:https://code.juejin.cn/pen/7396472908036210698

同時(shí) zustand 核心代碼也可以在普通 JS 中調(diào)用,把上述功能用普通 JS 實(shí)現(xiàn)就如下:

<div>
  <p>Count: <span id="value"></span></p>
  <button id="btn">+1</button>
</div>
import { createStore } from 'zustand@4.5.4/vanilla'

const store = createStore((set) => ({
  count: 1,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

const { getState, setState, subscribe, getInitialState } = store

window.onload = () => {
  const value = document.querySelector('#value')
  value.innerHTML = getInitialState().count // 設(shè)置 store 中 count 值
  // 使用 subscribe 訂閱狀態(tài)變化,并更新數(shù)值
  subscribe((state) => {
    value.innerHTML = state.count
  })

  const btn = document.querySelector('#btn')
  btn.onclick = () => {
    // 觸發(fā)更新
    getState().inc()
  }
}

代碼體驗(yàn)地址:https://code.juejin.cn/pen/7396483548833644581

Zustand 的實(shí)現(xiàn)

Vanilla 版本

zustand 的核心實(shí)現(xiàn)非常簡(jiǎn)潔,我們先實(shí)現(xiàn)一個(gè)普通版本的 zustand,因?yàn)?react hook 版本也需要使用到它。從上面zustand 使用案例代碼可以看出,state 狀態(tài)值不能直接修改,要通過(guò) setState 來(lái)觸發(fā)修改,這個(gè)和 redux 一致,對(duì)于通知狀態(tài)變化則使用了發(fā)布訂閱模式。

核心實(shí)現(xiàn)大概如下:

圖片圖片

const create = (createState) => {
  let state
  let initialState
  const listeners = new Set()

  const setState = (partial, replace) => {
    // 判斷是否為函數(shù),為函數(shù)就調(diào)用,并傳入當(dāng)前狀態(tài)值
    const nextState = typeof partial === 'function'
      ? partial(state)
      : state
    
    // 對(duì)比狀態(tài)值是否有變化
    if (!Object.is(nextState, state)) {
      const previousState = state
      // 如果是替換整個(gè)狀態(tài)值,或者狀態(tài)值為基礎(chǔ)值或 null,則直接賦值,不然使用 Object.aasign 合并狀態(tài)值
      state = replace ?? (typeof nextState !== 'object' || nextState === null)
        ? nextState
        : Object.assign({}, state, nextState)
      
      // 觸發(fā)訂閱函數(shù)
      listeners.forEach((listener) => listener(state, previousState))
    }
  }

  const getState = () => state
  
  const getInitialState = () => initialState

  const subscribe = (listener) => {
    listeners.add(listener)
    // 返回一個(gè)取消訂閱的方法
    return () => {
      listeners.delete(listener)
    }
  }

  // 清空訂閱
  const destory = () => listeners.clear()

  const api = {
    setState,
    getState,
    getInitialState,
    subscribe,
    destory
  }

  // 調(diào)用 createState,createState 參數(shù)為 set、get 和 api 對(duì)象,函數(shù)返回狀態(tài)初始值
  initialState = (state = createState(setState, getState, api))

  return api
}

export default create

代碼體驗(yàn)地址:https://code.juejin.cn/pen/7396500100421255204

React Hook

接著基于普通版本實(shí)現(xiàn) React Hook 版本。在實(shí)現(xiàn)前,我們先了解一個(gè) React 自帶的 Hook - useSyncExternalStore[1]。

useSyncExternalStore 的作用是讓你可以訂閱外部的狀態(tài)源,當(dāng)外部狀態(tài)源發(fā)生變化時(shí),React 會(huì)觸發(fā)重選渲染。

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

useSyncExternalStore 調(diào)用第一個(gè)參數(shù) subscribe 訂閱數(shù)據(jù)源變化,當(dāng)數(shù)據(jù)源變化了,就觸發(fā)重新渲染,并調(diào)用 getSnapshot 返回最新的狀態(tài)值。

有了這個(gè) Hook 的支持,我們就可以輕松實(shí)現(xiàn) React Hook 版本 zustand。

import createImpl from './vanilla'
import useSyncExternalStoreExports from 'use-sync-external-store/shim/with-selector'

const { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports

const create = (createState) => {
  // 使用普通版本 zustand 創(chuàng)建一個(gè)支持發(fā)布訂閱的數(shù)據(jù)源
  const api = createImpl(createState)

  // zustand 版本 Hook,參數(shù)為狀態(tài)值選擇器和判斷狀態(tài)是否變化函數(shù)
  const useBearStore = (selector, equiltyFn?) => 
    // 和 useSyncExternalStore 類(lèi)似,不過(guò)支持傳入 selector,獲取部分?jǐn)?shù)據(jù)
    useSyncExternalStoreWithSelector(
      api.subcribe,
      api.getState,
      api.getInitialState,
      selector,
      equiltyFn,
    )

  // 把 api 合并到 Hook 對(duì)象上
  Object.assign(useBearStore, api)

  return useBearStore
}

export default create

至此我們已經(jīng)完成了 zustand 核心功能的代碼編寫(xiě)。

代碼體驗(yàn):https://code.juejin.cn/pen/7396503370447781951

拓展

useSyncExternalStoreWithSelector

React Hook 版本的 zustand 中使用到了 useSyncExternalStoreWithSelector[2],這個(gè) Hook 是基于 useSyncExternalStore 實(shí)現(xiàn)的,可以簡(jiǎn)單了解下它的實(shí)現(xiàn),簡(jiǎn)化版源碼如下(去除了服務(wù)端渲染等內(nèi)容):

// 相比于 useSyncExternalStore ,多了 selector 和 isEqual 參數(shù)
function useSyncExternalStoreWithSelector(
  subscribe,
  getSnapshot,
  getServerSnapshot,
  selector,
  isEqual?,
) {
  const [getSelection, getServerSelection] = useMemo(() => {
    let memoizedSnapshot; // 緩存的整個(gè)狀態(tài)值
    let memoizedSelection: Selection; // 緩存的使用 selector 選中的部分狀態(tài)值
  
    const memoizedSelector = (nextSnapshot: Snapshot) => {
      const prevSnapshot = memoizedSnapshot
      const prevSelection = memoizedSelection

      // 如果整體狀態(tài)值相等,直接返回緩存的 selector 選中的狀態(tài)值。
      if (is(prevSnapshot, nextSnapshot)) {
        return prevSelection;
      }

      // 使用 selector 函數(shù)獲取最新的狀態(tài)值
      const nextSelection = selector(nextSnapshot);

      // 有傳入判斷狀態(tài)是否相等函數(shù),相等的話(huà)就返回上次的 selector 選中值。
      if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
        // 記錄最新的整體狀態(tài)值
        memoizedSnapshot = nextSnapshot;
        return prevSelection;
      }

      // 記錄最新一次的更新值
      memoizedSnapshot = nextSnapshot;
      memoizedSelection = nextSelection;
      // 返回 selector 函數(shù)獲取最新的狀態(tài)值
      return nextSelection;
    };
      
    const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
    return [getSnapshotWithSelector, () => {}];
  }, [getSnapshot, getServerSnapshot, selector, isEqual]);
  
  // 調(diào)用 useSyncExternalStore 方法,第二參數(shù)不是整體獲取整個(gè)狀態(tài)值,而是 selector 的狀態(tài)值
  const value = useSyncExternalStore(
    subscribe,
    getSelection,
    getServerSelection,
  );
  return value
}

總結(jié)

可以看到 zustand 核心代碼還是很簡(jiǎn)潔的。通過(guò)實(shí)現(xiàn)核心代碼,我們可以更好地理解和使用 zustand。有興趣的同學(xué)可以繼續(xù)了解下 zustand 插件相關(guān)的內(nèi)容。

參考資料

[1]useSyncExternalStore: https://react.dev/reference/react/useSyncExternalStore

[2]useSyncExternalStoreWithSelector: https://github.com/facebook/react/blob/d17e9d1ce566276fc54a8ea27f4e9ea1fa434e62/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js

責(zé)任編輯:武曉燕 來(lái)源: 栗子前端
相關(guān)推薦

2022-10-28 07:27:17

Netty異步Future

2024-11-27 16:07:45

2021-08-27 07:06:10

IOJava抽象

2024-02-20 21:34:16

循環(huán)GolangGo

2023-08-04 08:20:56

DockerfileDocker工具

2022-05-24 08:21:16

數(shù)據(jù)安全API

2023-08-10 08:28:46

網(wǎng)絡(luò)編程通信

2023-09-10 21:42:31

2023-06-30 08:18:51

敏捷開(kāi)發(fā)模式

2023-07-18 07:56:20

2023-03-29 08:26:06

2023-06-07 14:07:00

架構(gòu)

2022-02-14 07:03:31

網(wǎng)站安全MFA

2022-06-26 09:40:55

Django框架服務(wù)

2023-04-26 07:30:00

promptUI非結(jié)構(gòu)化

2022-04-06 08:23:57

指針函數(shù)代碼

2023-12-28 09:55:08

隊(duì)列數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)

2022-11-12 12:33:38

CSS預(yù)處理器Sass

2024-02-26 00:00:00

Go性能工具

2023-07-27 07:46:51

SAFe團(tuán)隊(duì)測(cè)試
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)