History Of state management
Flux 架构
Action -> Dispatcher -> Store -> View
视图(view)发送动作(action)到派发器(dispatcher) 派发器(dispatcher)发送动作(action)到每一个存储(store) 存储(store)发送数据(data)到视图(view
单向数据流的架构,特别使用于 React 响应式编程的概念。这个概念是前端模块化的重要理念。
V1.0 Redux
Redux 是平台无关的实现, 以 React-Redux 为例,它可以让 React 组件访问 state 片段和 dispatch actions 更新 store,从而同 Redux 集成起来。
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
V1.1 Rematch
Rematch 的作用是无需再定义冗余的 action types 为什么要将 action creators 和 reducers 区分对待呢?可以只需要一个吗?改变其中一个可以不影响另一个吗?
import React, { useEffect } from 'react'
import { Dispatch } from './store'
import { useDispatch } from 'react-redux'
const Count = () => {
const dispatch = useDispatch<Dispatch>()
useEffect(() => {
dispatch.count.incrementAsync(2)
(property) incrementAsync: (payload: number) => void
}, [])
return <div>example</div>
}
Rematch 只是使用方式的变化,变得更加应用
V1.1.2 Zustand
zustand 是 Rematch 的更完善的升级版本,它是一个更加轻量级的状态管理库,他使用 external storage, 而不是 context , 通过 Hooks 的方式来使用。
Va.b.c Xstate & virtuoso
对于实现状态管理,内部使用了 state machine 或者 react stream 的实现,xstate 和 virtuoso 就是这两种思路和实现的代表。
V12. XStream & virtuoso
Redux 主要的诟病是性能问题,只能通过 selector 解决, 我们可以看看 useSelector 干了什么? 调用此 Hook API 时会在 store 上注册监听器。 当 Store::state 变化时,组件会 checkForUpdates,利用 equalityFn 判断是否进行更新。 两个 feature:订阅 Store,当 state 变化时,自动 mapState,返回的 childState 会被渲染到视图上 equalityFn 等效于 shouldComponentUpdate
所以使用 redux 有两个重要的技巧,
- immutable
- memorise
下面介绍 reselect 的基本原理, 只有当传入的参数引用不一致的时候会重新计算,比如变成了 exampleState1,即该 obj 的引用变了。 react redux 都是推崇不可变数据,一旦要变数据,则该部分的 state 需要重新生成。 即引用变了。 完美契合 reselect。
V2.0 bottom up Or Top down
jotai 是 bottom up 的代表,既然 useSelector 是必要的过程,为什么不通过反向来,由小的 Atom 组成成大的 State 么?
这种是可以利用 atom 去 build shared value, 避免了,嵌套的 context 变更。
https://react-community-tools-practices-cheatsheet.netlify.app/state-management/poimandres/
Valtio
One big aspect is the mental model. We have two state-updating models. One is for immutable updates and the other for mutable updates. While JavaScript itself allows mutable updates, React is built around immutable states. Hence, if we mix the two models, we should be careful not to confuse ourselves. One possible solution would be to clearly separate the Valtio state and React state so that the mental model switch is reasonable. If it works, Valtio can fit in. Otherwise, maybe stick with immutable updates. The major benefit of mutable updates is we can use native JavaScript functions.
On the other hand, a disadvantage of proxy-based render optimization can be less predictability. Proxies take care of render optimization behind the scenes and sometimes it’s hard to debug the behavior. Some may prefer explicit selector-based hooks. In summary, there’s no one-size-fits-all solution. It’s up to developers to choose the solution that fits their needs.
How to select tools for state management ?
-
Question 1 data-centric Or component centric
jotai 可以用来 component 共享数据, 我认为大多数情况下需要使用 Server 的 Frontend App 不应该使用 Jotai
-
Question 2, with Or without external storage
如果简单应用可以使用 Context, use Zustand For React Redux Store With Persistence
-
Question 3 What’s the Update Style
Where does the state reside? In React, there are two approaches. One is the module state, and the other is the component state. A module state is a state that is created at the module level and doesn’t belong to React. A component state is a state that is created in React component life cycles and controlled by React. Zustand and Valtio are designed for module states. On the other hand, Jotai is designed for component states. For example, consider Jotai atoms. The following is a definition of countAtom: const countAtom = atom(0);
This countAtom variable holds a config object, and it doesn’t hold a value. The atom values are stored in a Provider component. Hence, countAtom can be reused for multiple components. Implementing the same behavior is tricky with module states. With Zustand and Valtio, we would end up using React Context. On the other hand, accessing component states from outside React is technically not possible. We’ll likely need some sort of module state to connect to the component states. Whether we use module states or component states depends on the app requirements. Usually, using either module states or component states for global states fulfills the app requirements, but in some rare cases, using both types of states may make sense. What is the state updating style? There is a major difference between Zustand and Valtio. Zustand is based on the immutable state model, while Valtio is based on the mutable state model. The contract in the immutable state model is that objects cannot be changed once created. Suppose you have a state variable such as state = { count: 0 }. If you want to update the count in the immutable state model, you need to create a new object. Hence, incrementing the count by 1 should be state = { count: state.count + 1 }. In the mutable state mode, it could be ++state.count. This is because JavaScript objects are mutable by nature. The benefit of the immutable model is that you can compare the object references to know whether anything has changed. It helps improve performance for large, nested objects. Because React is mostly based on the immutable model, Zustand with the same model has compatibility. Thus, Zustand is a very thin library. On the other hand, Valtio, with the mutable state model, requires filling the gap between the two models. In the end, Zustand and Valtio take different state updating styles. The mutable updating style is very handy, especially when an object is deeply nested.