preface

React state management is a colorful and complicated world, I know of no less than dozens of, among them the most famous immutable camp redux, mobx, React-easy-state, immutable camp is a mutable camp. After the birth of hooks, there were minimalist “unstated-next” and “Hox” and “Hoox” produced by ant Financial.

The fact that there are so many state management frameworks in the community also shows that there are some unsatisfying areas among the state management libraries.

RXV state management library

RXV is a React state management framework THAT I created based on these pain points and directly introduced Vue3’s package: @vue/ ReActivity. Here’s a simple example:

// store.ts
import { reactive, computed, effect } from '@vue/reactivity';

export const state = reactive({
  count: 0});const plusOne = computed((a)= > state.count + 1);

effect((a)= > {
  console.log('plusOne changed: ', plusOne);
});

const add = (a)= > (state.count += 1);

export const mutations = {
  // mutation
  add,
};

export const store = {
  state,
  computed: {
    plusOne,
  },
};

export type Store = typeof store;
Copy the code
// Index.tsx
import { Provider, useStore } from 'rxv'
import { mutations, store, Store } from './store.ts'
function Count() {
  const countState = useStore((store: Store) = > {
    const { state, computed } = store;
    const { count } = state;
    const { plusOne } = computed;

    return {
      count,
      plusOne,
    };
  });

  return (
    <Card hoverable style={{ marginBottom: 24}} >
      <h1>counter</h1>
      <div className="chunk">
        <div className="chunk">Count in store is now {countstate.count}</div>
        <div className="chunk">For computed, plusOne is now {countstate.plusone.value}</div>
         <Button onClick={mutations.add}>add</Button>
      </div>
    </Card>
  );
}

export default() = > {return (
    <Provider value={store}>
       <Count />
    </Provider>
  );
};
Copy the code

As you can see, the store definition only uses @vue/reactivity, whereas RXV only makes a bridge between Vue3 and React components, just as the name suggests: React x vue.

Some pain points

In my opinion, I will briefly summarize some of the shortcomings of the existing state management repository:

  1. In order toreduxFor example, the syntax is more redundant, more boilerplate.
  2. mobxGood, but you also need to learn a separate API, which is very intrusive for the React component, and the decorator syntax is unstable.
  3. unstated-nextIt is a minimalist framework with a shallow encapsulation of React Hook.
  4. react-easy-stateThe introduction of theobserve-utilThis library is very close to what Vue3 wants for reactive processing.

Let’s expand:

The options – -based pain points

The options-based models of Vuex and DVA now seem to have a lot of drawbacks. For details, see The vue-comaction-API documentation.

Simply put, a component has several function points, but these function points are scattered among Data, Methods, and computed, resulting in a chaotic structure.

When you want to maintain a feature, you have to look at the configuration object in its entirety.

Take a few lines out, change a few lines, leave behind some useless code, or maybe a related function hidden in the computed options secretly kills you…

The benefit of hooks is a more flexible way to organize your code.

redux

To cut to Dan’s own joke, there are so many concepts to learn that writing a simple feature can be a pain in the face as you have to jump between five files. The downsides of Redux have been discussed in the community for a long time, and I’m sure if you’ve written about redux, you’ll feel the same way.

unstated-next

Unstated -next is actually quite nice, about 40 lines of source code. It makes the best use of React Hook. To write a model is to write a custom Hook. But minimalism comes with some problems:

  1. Modules do not have the ability to access each other.
  2. The performance of the Context makes you need to pay attention to the division of modules. (See the performance section of my article for details.)
  3. The problem with module partition is that if it is all in one Provider, the update granularity is too large, and all components using useContext will be repeatedly rendered. If you put it in multiple providers, then you go back to the first pain point. These modules are independent of each other and are not accessible to each other.
  4. Hook brings some mental burden problem. Did you use the React Hooks correctly?

react-easy-state

Observe-util, introduced in this library, is actually very similar to the core implementation of the Vue3 ReActivity section. See my previous two articles for an explanation of the principles: To fully understand the Vue3 Proxy response principle! TypeScript implements proxy-based reactive libraries from scratch. With you thoroughly understand the Vue3 Proxy response principle! Map and Set response based on function hijacking.

Vue3 ReActivity is an enhanced version of Observe-Util, with more customizable capabilities. If we could plug it directly into the state management library, we would have all the responsive capabilities of Vue3.

The principle of analysis

Vue-next is the source repository of Vue3. Vue3 uses LERna to do the division of packages, while the responsive capability @vue/reactivity is divided into a separate package

Look at the core apis provided by this package:

effect

Effect is actually a common concept in the reactive libraries: observation functions, like Watcher in Vue2, Autorun in Mobx, and Observer, that collect dependencies.

It accepts a function that collects dependencies internally for access to responsive data, and then triggers a responsive update event when the responsive data is updated.

reactive

The core API for responsive data. This API returns a proxy, and access to all of these properties is hijacked so that dependencies (that is, effects that are running) are collected on get and updates are triggered on set.

ref

For simple data types like number, we can’t do something like this:

let data = reactive(2)
/ / 😭 oops
data = 5
Copy the code

This is against reactive interception rules. There is no way to intercept changes to the data itself, only to intercept changes to the properties of the data, so there is a ref.

const data = ref(2)
/ / 💕 ok
data.value= 5
Copy the code

computed

Calculate a property, and when the dependent value is updated, its value is automatically updated as well. But computed interior is also an effect.

Has advanced features such as observing another computed data within a computed, effect observing a computed change, etc.

implementation

From these core apis, as long as Effect is plugged into the React system, the rest of the apis are fine because they just collect Effect’s dependencies and notify Effect to trigger updates.

Effect accepts a function, and it also supports passing in the schedule parameter to define what function to trigger when a dependency is updated.

The core RXV API, useStore, also accepts a selector function that lets the user choose which data to access in the component.

The idea is obvious:

  1. theselectorWrapping is performed in effect to collect dependencies.
  2. Specifies the function to call when a dependency update occursUseStore is currently in useOf this componentforceUpdateForce the render function.

So the data changes, the component automatically updates?

Take a quick look at the core implementation

export const useStore = <T, S>(selector: Selector<T, S>): S => {
  const forceUpdate = useForceUpdate();
  const store = useStoreContext();

  const effection = useEffection(() => selector(store), {
    scheduler: forceUpdate,
    lazy: true,
  });

  const value = effection();
  return value;
};
Copy the code
  1. UseForceUpdate to register a function that forces updates in the current component.
  2. UseContext reads the store that the user passes from the Provider.
  3. We use the Vue effect to perform the selector(store) for us, and specify the scheduler to be forceUpdate to complete the dependency collection.

With just a few lines of code, you can use all of the @vue/ reActivity capabilities in React.

Advantages:

  1. Direct introduction of @vue/ Reacivity, full use of Vue3’s reactivity capabilities, computed, Effect, etc., and also provide responsive capabilities for Set and Map. It will become more complete and powerful as the library is updated.
  2. Vue-next Complete test case inside the repository.
  3. Full TypeScript type support.
  4. Fully reuse @vUE/Reacivity to achieve super global state management ability.
  5. Precise updates at the component level in state management.
  6. Vue3 always has to learn, learn ahead of time to prevent unemployment!

Disadvantages:

  1. Due to the need for accurate collection relies entirely onuseStore, soselectorThe function must access exactly the data you care about. Even if you need to trigger an update of a value inside the array, you can’t just return the array itself in useStore.

Here’s an example:

function Logger() {
  const logs = useStore((store: Store) = > {
    return store.state.logs.map((log, idx) = > (
      <p className="log" key={idx}>
        {log}
      </p>
    ));
  });

  return (
    <Card hoverable>
      <h1>The console</h1>
      <div className="logs">{logs}</div>
    </Card>
  );
}
Copy the code

This code returns the entire section of JSX directly in useStore, because the map goes back and accesses each item of the array to collect dependencies, which is the only way to be responsive.

The source address

Github.com/sl1673495/r…

If you like this library, welcome to give your star✨, your support is my biggest motivation ~