- Published on
dva 阵痛迁移至 zustand
前言
在进行移动端项目体积优化时发现项目中使用到了 dva,而 dva 又使用到了 immer, 这两个库的体积加起来达到了 117kb+16.2kb,同时 dva 本身的学习成本、ts 支持度也不够友好,所以通过当前社区火热的一个状态管理库 zustand 来替换 dva,而达到缩小体积、提高开发体验的目的。
zustand vs dva
dva 使用 Generator 函数,zustand 使用正常的方法,支持异步。
zustand 体积更小,更简洁的 API,更友好的 ts 支持。在命名空间、扩展性等功能上都有良好的支撑。
size
name | size |
---|---|
zustand | 3kb |
dva | 117kb |
api
下方 api 都会从 dva 的角度进行展开
创建仓库
dva
// 创建应用
const app = dva()
// 注册 Model
app.model({
namespace: 'count',
state: 0,
reducers: {
add(state) {
return state + 1
},
},
effects: {
*addAfter1Second(action, { call, put }) {
yield call(delay, 1000)
yield put({ type: 'add' })
},
},
})
zustand
import create from 'zustand'
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
声明 state
dva
const app = dva()
interface ModelState {
foo: number
}
interface Model {
state: ModelState
}
const INITIAL_STATE: ModelState = {
foo: 0,
}
const model: Model = {
namespace: 'count',
state: INITIAL_STATE,
reducers: {},
effects: {},
}
// 注册 Model
app.model(model)
zustand
import create from 'zustand'
interface State {
foo: number
}
type Store = State
const INITIAL_STATE: State = {
foo: 0,
}
const useStore = create<Store>((set) => ({
...INITIAL_STATE,
}))
声明 reducers
dva
const app = dva()
interface ModelState {
foo: number
}
interface Reducers {
setFoo(state: ModelState, payload: { foo: number }): void
}
interface Model {
state: ModelState
reducers: Reducers
}
const INITIAL_STATE: ModelState = {
foo: 0,
}
const model: Model = {
namespace: 'count',
state: INITIAL_STATE,
reducers: {
setFoo(state, payload) {
return {
...state,
foo: payload.foo,
}
},
},
effects: {},
}
// 注册 Model
app.model(Model)
zustand
import create from 'zustand'
interface State {
foo: number
}
interface Reducers {
setFoo(foo: number): void
}
const INITIAL_STATE: State = {
foo: 0,
}
type Store = State & Reducers
const useStore = create<Store>((set) => ({
...INITIAL_STATE,
setFoo(foo: number) {
set({
foo,
})
},
}))
声明 effects
dva
const app = dva()
interface ModelState {
foo: number
}
interface Reducers {
setFoo(state: ModelState, payload: { foo: number }): void
}
interface Effects {
asyncSetFoo(action: any, params: { call: any; put: any }): void
}
interface Model {
state: ModelState
reducers: Reducers
effects: Effects
}
const INITIAL_STATE: ModelState = {
foo: 0,
}
const model: Model = {
namespace: 'model',
state: INITIAL_STATE,
reducers: {
setFoo(state, payload) {
return {
...state,
foo: payload.foo,
}
},
},
effects: {
*asyncSetFoo(action, { call, put }) {
yield call(delay, 1000)
yield put({ type: 'setFoo', payload: { foo: 1 } })
},
},
}
// 注册 Model
app.model(Model)
zustand
import create from 'zustand'
interface State {
foo: number
}
interface Reducers {
setFoo(foo: number): void
}
interface Effects {
asyncSetFoo(): void
}
const INITIAL_STATE: State = {
foo: 0,
}
type Store = State & Reducers & Effects
const useStore = create<Store>((set) => ({
...INITIAL_STATE,
setFoo(foo: number) {
set({
foo,
})
},
async asyncSetFoo() {
await delay(1000)
set({
foo: 1,
})
},
}))
TS 支持度对比
dva
import React from 'react'
import { useSelector, useDispatch } from 'dva'
interface RootState {
foo: ModelState
}
const Component: React.FC = () => {
const model = useSelector<RootState, ModelState>((state) => state.foo) // selector 需要手动声明/关联
const dispatch = useDispatch()
const handleClick = () => {
dispatch({
type: 'model/effectName',
payload: {
// payload 需要手动声明/关联
foo: 1,
},
})
}
return <div onClick={handleClick}>{model.foo}</div>
}
export default Component
zustand
import React from 'react'
import useStore from './store'
const Component = () => {
const store = useStore() // 自动关联
return (
<div
onClick={() => {
store.asyncSetFoo() // 自动关联
}}
>
{store.foo}
</div>
)
}
export default Component
codesandbox
zustand 替换 dva 实操
实现 model 内的 call
// dva
export default {
namespace: 'model',
state: {
field: [],
},
reducers: {},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
const result = yield call(delay, 1000)
},
},
}
// zustand
const useStore = create((set, get) => ({
async asyncSetFoo() {
await delay(1000)
},
}))
实现 model 内的 put
同 model put
// dva
export default {
namespace: 'model',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
yield put('foo', { payload: {} })
},
},
}
// zustand
const useStore = create((set, get) => ({
foo() {},
async asyncSetFoo() {
get().foo()
},
}))
不同 model put
// dva
// model1
export default {
namespace: 'model1',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
yield put('foo', { payload: {} })
},
},
}
// model2
export default {
namespace: 'model2',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
yield put('model1/asyncFoo', { payload: {} })
},
},
}
// zustand
// store1
const useStore1 = create((set, get) => ({
foo() {},
async asyncSetFoo() {
get().foo()
},
}))
// store2
const useStore2 = create((set, get) => ({
foo() {},
async asyncSetFoo() {
await useStore1.getState().asyncSetFoo()
},
}))
dva zustand 共存 put
// dva
// model1
export default {
namespace: 'model1',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
// dva call zustand
yield call(useStore1.getState().asyncSetFoo)
},
},
}
// zustand
// store1
const useStore1 = create((set, get) => ({
foo() {},
async asyncSetFoo() {
// zustand use dva
getDvaApp()._store.dispatch({ type: 'model1/asyncFoo', payload: {} })
},
}))
实现 model 内的 select
同 model select
// dva
export default {
namespace: 'model',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
const model = yield select((state) => state.model)
console.log(model.field)
},
},
}
// zustand
const useStore = create((set, get) => ({
field: [],
foo() {},
async asyncSetFoo() {
console.log(get().field)
},
}))
不同 model select
// dva
// model1
export default {
namespace: 'model1',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
yield put('foo', { payload: {} })
},
},
}
// model2
export default {
namespace: 'model2',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
const model = yield select(state => state.model1)
console.log(model.field)
},
},
}
// zustand
// store1
const useStore1 = create((set, get) => ({
field: [],
foo() {},
async asyncSetFoo() {
get().foo()
},
}))
// store2
const useStore2 = create((set, get) => ({
field: [],
foo() {},
async asyncSetFoo() {
console.log(useStore1.getState().field)
},
}))
dva zustand 共存 select
// dva
// model1
export default {
namespace: 'model1',
state: {
field: [],
},
reducers: {
foo() {},
},
effects: {
*asyncFoo({ payload }, { call, put, select }) {
// dva call zustand
console.log(useStore1.getState().field)
},
},
}
// zustand
// store1
const useStore1 = create((set, get) => ({
field: [],
foo() {},
async asyncSetFoo() {
const result = getDvaApp()._store.getState().model
console.log(result.field)
},
}))
实现 model 内的 subscriptions
查看了下项目中使用的地方几乎为 0,大多数为监听 history 然后进行对应操作。
如果需要迁移可以考虑通过 subscribe api 及在全局组件上监听 history 实现
代码迁移
跟随上方代码进行对应 api 替换即可。需要注意的是单独的 model 必须整体迁移。