HooX: Hook-based React State Management Tool

Keywords: Javascript github React Vue

Why Make Wheels

hook self-contained wheel halo

I won't go into react hook much. Hook provides the ability to abstract state, which naturally leads to the idea that global state can be extracted based on hook. Because of its inherent wheeled aura, many hook-based state management tools have emerged in the community, such as those developed by the Flying Ice Team a few years ago. icestore Or this stamen But I prefer this relatively. unstated-next.

Now that others have built so many wheels, why do they build them themselves? Naturally, it is because:

Other people's wheels are not enough.

For instance unstated-next It essentially globalizes a custom hook. The idea is very good, but the particle size is too big. state, actions, effects must be maintained in a custom hook. A series of internal actions, effects need to add useCallback, useMemo is also more troublesome, if pulled out to the outside, but also to pass many parameters, write TS, and write a lot of generics. In short, if the project is relatively complex, it will be tiring to write.

stamen It's not bad. Declare a store containing state, reducer, effects. And there is no need to wrap the Provider around the component, and plug it in anywhere to respond to updates. It's dispatch, which I don't like very much. It's not easy to locate directly to the action or effect declaration, and it's missing the type of input and output parameters.

icestore The problem is similar. Said to support TS, is actually incomplete, look at the source code, the type is completely lost. In addition, I don't like the namespace set very much.

Of course, these problems can also be optimized. But why? I didn't have many lines of code. When I mentioned PR to people, I wrote wheels myself. So in a word, we should make it ourselves.

My Ideal Type

So what kind of state management tools do I want? Before hoox, I actually implemented a version that basically copied a version of dva's api (replacing yield with async/await). It's a bit like icestore, but there's no namespace. The most lethal and unsolvable problem is the loss of types and function references.

Later I summed up what I really wanted:

  1. Global state management, and not a single store;
  2. actions and effects are normal functions, independently declared and directly referenced.
  3. Perfect TS support.

So the goal is simple, so to speak. unstated-next De hook package edition. So I implemented the first edition, the final effect is as follows:

HooX

Create a global state

// store.js
import createHoox from 'hooxjs'

// Declare global initial state
const state = {
  count: 1
}

// Create store
export const { setHoox, getHoox, useHoox } = createHoox(state)

// Create an action
export const up = () => setHoox(({ count }) => ({ count: count + 1 }))

// Create an effect 
export const effectUp = () => {
  const [{ count }, setHoox] = getHoox();
  const newState = { count: count + 1 }
  return fetch('/api/up', newState).then(() => setHoox(newState))
  // Or refer directly to action
  // return fetch('/api/up', newState).then(up)
}

Consumption state

import { useHoox, up, effectUp } from './store';

function Counter() {
  const [state] = useHoox()
  return (
    <div>
      <div>{state.count}</div>
      <button onClick={up}>up</button>
      <button onClick={effectUp}>effectUp</button>
    </div>
  )
}

Direct modification of status

import { useHoox } from './store';

function Counter() {
  const [state, setHoox] = useHoox()
  return (
    <div>
      <div>{state.count}</div>
      <input
        value={state.count}
        onChange={value => setHoox({ count: value })}
      /
    </div>
  )
}

reset state

We know that in the class component, merging state updates are done through this.setState. But in the function component, the second parameter setState returned by useState is a replacement update. In practice, we all have demands. Especially for non-TS projects, the state model may be dynamic and may need to be reset. In order to meet the needs of everyone, I also added an api for you to use.

import { useHoox } from './store';

function Counter() {
  const [state, setHoox, resetHoox] = useHoox()
  return (
    <div>
      {state ? <div>{state.count}</div> : null}
      <button onClick={() => resetHoox(null)>reset</button>
    </div>
  )
}

Global computed

With the above api, in fact, we can also achieve the same effect as computed in vue.

import { useHoox } from './store';

export function useDoubleCount () {
  const [{ count }] = useHoox();
  return count * 2
}

For some very complex operations, we can also use react's useMemo to optimize.

import { useHoox } from './store';

export function usePowCount (number = 2) {
  const [{ count }] = useHoox();
  return useMemo(() => Math.pow(count, number), [count, number])
}

In addition, some global effect s can be achieved.

A place that is not good enough

Need Provider

The bottom layer of hoox is based on context and useState. Because state exists in context, it is similar to Redux. Components of consumption state must be descendants of corresponding Context.Provider. Such as:

import { Provider } from './store';
import Counter from './counter';

function App() {
  return <Provider>
    <Counter />
  </Provider>
}

This in turn leads to the fact that if a component needs to consume two store s, it needs to be a descendant of two providers.

hoox provides a grammatical sugar, createContainer, to simplify the grammar a little.

import { createContainer } from './store';
import Counter from './counter';

function App () {
  return <Counter />
}
  
export default createContainer(App)

Other bad points

Leave it to the comment area

Github

Specific source code and api introduction can be seen in github: https://github.com/wuomzfx/hoox

I won't elaborate on the source code, nor do I have a few lines of code, so you can see.

Posted by anto91 on Thu, 26 Sep 2019 23:06:11 -0700