深入理解React – 3

用过Vue和React的小伙伴都知道,两个框架一个很重要的区别就是数据的传递。在Vue中数据是双向绑定的,而在React中的数据是严格单向传递的,今天我们来总结一下React中数据传递的方式

单向数据流

当前组件的state以props的形式流动时,只能流向比自己层级更低的组件:

  1. 父传子,直接传递props
  2. 子传父,由父组件传递含有自身context的函数,子组件调用函数,通过传参的形式传递内容
  3. 兄弟组件,子传父=>父传子,通过父组件中转,将消息传递给相邻的组件

以上的单向数据流是React数据中的基础传递方式,在实际项目中,组件嵌套的层级都会很深,我们就需要用到更加方便的信息传递方式了

状态集中管理

最经典的就是Redux啦,作为集中式状态管理容器,他提供可预测的状态管理,设计模式遵循flux模式,他的数据流也是严格单向的。

另外一个就是React内置的contextAPI啦,contextAPI使用起来更方便,在中小型的项目中也就没必要使用Redux啦。 ContextAPI一般需要createContext, useContext, context.Provider三大件,createContext创建一个全局环境,用provider包裹需要用到的组件,useContext在组件中调用。

发布订阅模型

接下来介绍状态管理的大杀器Zustand,最近的项目中一直在使用这个工具,比context API 和 Redux方便了无数倍

首先为什么选择Zustand呢?

按照官方的话来说Zustand是一个轻量,无样板,基于hooks的状态管理库。他的核心优势是:

  • API极简, 没有reducer/action/dispatch
  • 基于selector的精确订阅,性能模型清洗
  • 天然支持函数组合 (slice pattern)
  • 不侵入React架构,随用随取

对于常见的三种state,我们是有三种心智模型的

  • UI State – useState / Zustand
  • Client State – Zustand
  • Server State – React Query

核心区别Zustand管理的是本地真相,React Query管理的是远程真相

实践

1. 按业务域区分slice

const createToastSlice = (set) => ({
  toast: null,
  setToast: (toast) => set({ toast }),
})
const createModalSlice = (set) => ({
  modalType: null,
  setModalType: (modalType) => set({ modalType }),
})

2. 组合Store – useBoundStore

export const useBoundStore = create((...a) => ({
  ...createModalSlice(...a),
  ...createToastSlice(...a),
}))

3. 持久化 persist + partialize

export const useBoundStore = create()(
  persist(
    (set) => ({
      modalType: null,
      toast: null,
      userData: {},

      setModalType: (modalType) => set({ modalType }),
      setToast: (toast) => set({ toast }),
      setUserData: (userData) => set({ userData }),
    }),
    {
      name: 'userStore',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({ userData: state.userData}),
    }
  )
)

4. 订阅

4.1 基础版订阅

const toast = useBoundStore((state) => state.toast);

4.2 进阶版订阅 selector + shallow

const { modalType, setModalType } = useBoundStore(
  (state) => ({
    modalType: state.modalType,
    setModalType: state.setModalType,
  }),
  shallow
)

4.3 终极版订阅 *** 亮点

更加推荐的架构是不让组件直接接触store,而是通过语义化store做一个中间层

// 封装domain hook
export function useModalStore() {
  return useBoundStore(
    (s) => ({
      type: s.modalType,
      open: s.setModalType,
      close: () => s.setModalType(null),
    }),
    shallow
  )
}
// 组件中使用
const { type, open, close } = useModalStore()

踩过的坑:

1. 直接解构整个store

    const { modalType, setModalType } = useStore()

    等价于订阅整个store,任意的状态变化都会导致这个组件重新渲染

    2. 把服务端数据塞进 Zustand

    // 不推荐
    const users = await fetchUsers()
    set({ users })

    带来的问题是需要自己处理缓存,失效,刷新等机制,并且状态很快就失真了,因此还是建议直接使用react query了

    3. 为什么使用shallow

    selector 是必须的,因为它决定了订阅粒度;shallow 只在 selector 返回对象或数组时才有意义,用来避免引用变化导致的无效 rerender,官方没有强制推荐 shallow,是因为它属于场景型优化而不是默认模式。


    Posted

    in

    by

    Tags: