用过Vue和React的小伙伴都知道,两个框架一个很重要的区别就是数据的传递。在Vue中数据是双向绑定的,而在React中的数据是严格单向传递的,今天我们来总结一下React中数据传递的方式
单向数据流
当前组件的state以props的形式流动时,只能流向比自己层级更低的组件:
- 父传子,直接传递props
- 子传父,由父组件传递含有自身context的函数,子组件调用函数,通过传参的形式传递内容
- 兄弟组件,子传父=>父传子,通过父组件中转,将消息传递给相邻的组件
以上的单向数据流是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,是因为它属于场景型优化而不是默认模式。