hydrogen-store
v0.4.0
Published
>
Downloads
31
Readme
hydrogen-store
一个简单的全局状态管理。
- 简单使用与 useState 用法基本一样,近乎无学习成本。😄
- 无需添加 Provider,不使用 useContext,数据变更不会触发其他模块的重新渲染。😄
- 可以更方便的使用 reducer,类 redux 的方式管理数据 😄
- 配合 hydrogen-store-redux-plugin 可以使用 redux-devtools 进行调试 😄
- 配合 hydrogen-store-effect-plugin 可以使用 effect 进行异步管理 😄
- 配合 hydrogen-store-immer-plugin 可以像 vuex 一样写 reducer😄
用法
仅需要把 useState 换成 useStore,即可立即跨模块共享全局数据,可以无需添加 Provider 等处理。同时做了性能优化,仅订阅使用的模块会触发更新,不会有使用 useContext 的性能问题
类型声明
type UseStore = <S>(
moduleName?: string,
autoMerge?: boolean,
willUpdate?: boolean,
) => [S, SetStore<SetStoreAction<S>>, ToolMethods<S>]
用法
import { useStore } from 'hydrogen-store'
// 使用useStore,并指定模块名称
function useHome() {
const [state = {}, setState] = useStore('home')
// ...
}
function useAddress() {
const [addressState = {}, setAddressState] = useStore('address')
// 直接setState一个值
const handleChangeState = () => {
setAddressState({ a: 100 })
}
// 使用更新函数
const handleChangeStateByFn = () => {
setAddressState((prevState) => ({ a: prevState?.a ? 1 : 2 }))
}
// ...
}
因为 useStore 用来处理的是全局数据,有时会需要初始化默认 state, 可以在 app 入口处或模块入口处注册初始数据
import { registerModule, useStore } from 'hydrogen-store'
registerModule('home', {
state: { a: 1 },
})
function TestStore() {
const [state, setState] = useStore('home')
console.log(state) // {a: 1}
// ...
}
useStore 的第二个参数 autoMerge,为是否进行自动合并。默认开启,会对 object 格式的数据进行默认自动合并处理。
当 autoMerge 为 true 时,setState 相当于 class 组件的 this.setState, autoMerge 为 false 则与 React.useState 的 setState 相同。
不论 useStore 的 autoMerge 是 true/false,你都可以在 setState 的时候,通过 setState 第二个参数控制当前是否进行合并
import { registerModule, useStore } from 'hydrogen-store'
registerModule('home', {
state: { a: 1 },
})
function TestStore() {
// 开启state自动合并
const [state, setState] = useStore('home', true)
const handleChangeState = () => {
setState({ b: 100 })
// 更新前:{a: 1},更新后: { a: 1, b: 100 }
}
// 不论useStore是否配置第二个参数,在setState的时候,仍可以通过第二个参数控制当前是否进行合并
const handleChangeStateMerge = () => {
setState({ b: 100 }, true)
// 更新前:{a: 1},更新后: { a: 1, b: 100 }
setState({ b: 100 }, false)
// 更新前:{a: 1},更新后的值为: { b: 100 }
}
}
方便的使用 reducer
支持添加自定义 reducer 函数,对 state 进行更统一的管理。
使用 switch case 的 reducer 是看起来很让人头疼的,直接使用 reducers 则清爽了很多
reducer 格式为
类型声明
;(state: S, payload: any) => S
触发 reducer 则可以从第三个参数直接调用对应方法,或者使用 dispatch 进行触发
registerModule('home', {
state: { a: 1 },
reducers: {
updateTitle: (state, payload) => ({ ...state, title: payload }),
updatePageBg: (state, payload) => ({ ...state, pageBg: payload }),
},
})
function TestStore() {
const [state = {}, setState, { updateTitle, dispatch }] = useStore('home')
const handleUpdateTitle = () => {
// 直接调用返回的方法
updateTitle('title')
}
const handleUpdatePageBg = () => {
// 通过dispatch触发
dispatch('updatePageBg', '#fff')
}
}
用法 redux devtool
即使因为种种原因你不想使用 redux 了,但是否时常会怀念 redux-devtool 的方便?hydrogen-store 可以方便开启 redux-devtool。仅需引入一个插件
import { usePlugins } from 'hydrogen-store'
import reduxPlugin from 'hydrogen-store-redux-plugin'
usePlugins([reduxPlugin])
好了,现在可以愉快的使用 redux devtool 进行调试了。hydrogen-store 本身不依赖 redux,使用 redux 和不使用 redux 对于业务代码完全没有影响,你可以通过环境变量/打包配置等开启或禁用它。
使用独立实例时需配置 devtoolId,用来在 devtool 中加以区分
创建 Store 实例
如你所见,以上示例代码都是全局使用默认导出的 全局单一 Store 实例,我们可以创建一个独立的 Store 实例吗?当然可以!
import { createStore } from 'hydrogen-store'
import reduxPlugin from 'hydrogen-store-redux-plugin'
export const singleStore = createStore({
// 创建时定义模块
modules: {
test: {
state: { a: 1 },
reducers: {
testAction: (state, payload) => ({ ...state, ...payload }),
},
},
address: {
state: { addressList: [] },
},
},
plugins: [reduxPlugin],
// 每个store会连接不同的 redux devtool instance, 我们可以定义不同的id加以区分
devtoolId: 'Test Next Store',
})
// 独立实例同样可以手动单独注册模块
singleStore.registerModule('home', {
state: { a: 1 },
reducers: {
updateTitle: (state, payload) => ({ ...state, title: payload }),
updatePageBg: (state, payload) => ({ ...state, pageBg: payload }),
},
})
function Home() {
const [state = {}, setState, boundMethods] = singleStore.useStore('home', true)
// ...
}
用法 createApp
如果你喜欢将 store 存放在 context 中并使用 Provider,或者 SSR 等需要不在整个端共享 Store, 可以使用 createApp。与不使用 hydrogen-store 直接使用 react context 存储数据对比
- hydrogen-store 带来的各种方便开发、管理的能力,如 reducer、使用 redux devtool 等等进行调试。
- createApp 创建的 store 在创建之后引用就不会变化,因此未使用数据的变更不会触发所有组件更新,你可以安全的使用 useStore hook 获取数据
import { createContext } from 'react'
import { createApp } from 'hydrogen-store'
const { Provider, useStore } = createApp({
modules: {
Address: {
state: { addressList: [] },
reducers: {
testAction: (state, payload) => ({ ...state, ...payload }),
},
},
},
plugins: [reduxPlugin],
// 每个store会连接不同的 redux devtool instance, 我们可以定义不同的id加以区分
devtoolId: 'Address Store',
})
function Child() {
const [{ addressList }, setAddressState] = useStore('Address')
}
用法 createContainer 创建单个模块
如果不希望使用全局 store,也可以创建单个模块
// AddressContainer.js
import { createContext } from 'react'
import { createContainer } from 'hydrogen-store'
const { Provider, useStore } = createContainer({
state: { addressList: [] },
reducers: {
testAction: (state, payload) => ({ ...state, ...payload }),
},
})
function Comp() {
const [{ addressList }, setAddressState] = useStore()
}
// 使用redux插件
const { Provider, useStore } = createContainer(
{
state: { addressList: [] },
reducers: {
testAction: (state, payload) => ({ ...state, ...payload }),
},
},
{
plugins: [reduxPlugin],
// 每个store会连接不同的 redux devtool instance, 我们可以定义不同的id加以区分
devtoolId: 'Address Store',
},
)
这样就可以像普通的 context、unstated-next 一样基于模块管理数据。
module
刚才我们已经注册了几个 module,可以看到,module 的具体结构如下
{
state: {}, // 初始化的state
reducers: {} // 除了setState之外的自定义reducer
}
hydrogen-store 定义为基于 module 进行全局状态管理。所以应当避免在全局 store 根节点直接存储基本类型的值,而应该使用 object 格式,将一系列相关状态进行合并管理。以下写法不会报错,但应避免。
// bad
const [cartCount, setCartCount] = useStore('cartCount')
const handleChange = () => setCartCount(1)
// good
const [{ cartCount } = {}, setCart] = useStore('cart')
const handleChange = () => setCart({ cartCount: 1 }, true)
Module 仅在 createStore 或者 registerModule 中进行初始化。初始化并非必须的, 因此可以直接将 React.useState 替换为 useStore 而立即享受安全的跨组件状态共享。未经初始化而直接使用的 module 相当于:
{
state: {
}
}
使用 useStore('moduleName')进行使用。
reducers
使用 reducer 可以极大方便对数据对更加集中的管理。同样推荐将状态的系列变更处理放在自定义 reducer 中。但是应该注意,虽然全局 state+reducer 有点 redux 的感觉,而且还能使用 redux-devtool。但是 hydrogen-store 的设计思路是一个不侵入 hooks 写法的状态库,更多类似的是 React.useState/React.useReducer 的全局化。 reducer 应当只是纯函数,只做状态的处理/格式化。getter、异步 action 这些事情,都推荐放到组件中使用 useMemo、useEffect 等去处理。虽然使用 hydrogen-store 可以很方便的存储全局状态,但是并不必要把所有数据都放在 store 中!
如果你真的喜欢/需要进行统一的状态管理,见下面的 effects 模块
effects
如果你非常喜欢将异步等等操作统一使用 store 进行管理,可以使用 effectPlugin,就可以像使用 dva/vuex 一样管理异步的操作
effect 类似于 reducer,不同在于:
- effect 提交的是 reducer action,而不是直接变更状态。
- effect 可以包含任意异步操作。
effects 概念基本等同于 dva 的 effects、vuex 的 actions
使用 dispatch 触发 reducer action,使用 dispatchEffect 触发 effect action
类型声明
type dispatchEffect = (effectName: string, payload: any, moduleName?: string) => any
import { createStore } from 'hydrogen-store'
import effectPlugin from 'hydrogen-store-effect-plugin'
const testModel = {
state: { dataList: [] },
reducers: {
testAction: (state, payload) => ({ ...state, ...payload }),
},
effects: {
// effect中进行异步操作,并触发reducer 更新数据
async loadData({ state, dispatch }, payload) {
const dataList = await fetchData()
dispatch('testAction', { dataList })
return dataList
},
// effect中可以触发其他effect
async testEffect({ state, dispatchEffect }, payload) {
const dataList = await dispatchEffect('loadData', payload)
// dispatchEffect的第三个参数可以触发其他模块effect
dispatchEffect('loadData', dataList, 'address')
// ...
},
},
}
export const singleStore = createStore({
modules: {
test: testModel,
},
plugins: [effectPlugin],
})
function Home() {
const [state = {}, setState, { dispatch, dispatchEffect }] = singleStore.useStore('test', true)
useEffect(() => {
dispatchEffect('loadData')
}, [])
}
immer
可以使用 immerPlugin,可以使用 immer 的语法简化编写 reducer
使用 immer 插件之后,你拥有了一个 react 版的 vuex
// 使用前
const reducers = {
testAction: (state, payload) => ({
...state,
data: {
...state.data,
a: {
...state.data.a,
b: 2,
},
},
}),
}
// 使用后
const reducers = {
testAction: (state, payload) => {
state.data.a.b = 2
},
}
import { createStore } from 'hydrogen-store'
import immerPlugin from 'hydrogen-store-immer-plugin'
const testModel = {
state: { data: { a: { b: 1 } } },
reducers: {
testAction: (state, payload) => {
state.data.a.b = 2
},
},
effects: {},
}
export const singleStore = createStore({
modules: {
test: testModel,
},
plugins: [effectPlugin, immerPlugin],
})