npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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],
})