1, first implement the class Stateful that managed atomic states

// An interface with the disconnect method. This could just be a function // but I think having it as an object is more readable. type Disconnect = { disconnect: () => void; } // `Stateful` is the base class that manages states and subscriptions. // Both Atom and Selector are derived from it. export class Stateful<T> { // This is a set of unique callbacks. The callbacks are listeners // that have subscribed private listeners = new Set<(value: T) => void>(); private queue = 0; // The value property is protected because it needs to be manually // assigned in the constructor (because of inheritance quirks) constructor(protected value: T) {} // Simple method for returning the state. This could return a deep // copy if you wanted to be extra cautious. snapshot(): T { return this.value; } // The emit method is what updates all the listeners with the new state // After updating the value, let all the listeners know there's a // new state. private emit() { // eslint-disable-next-line no-restricted-syntax for  (const listener of Array.from(this.listeners)) { listener(this.snapshot()); } } private flush() { const cb = () => { this.queue = 0; this.emit(); }; if (typeof MessageChannel ! == undefined) { const { port1, port2 } = new MessageChannel(); port1.onmessage = cb; port2.postMessage(null); } else { setTimeout(cb); } } // The update method is the canonical way to set state. It uses object // equality to prevent unnecessary renders. A  deep comparison could be // performed for complex objects that are often re-created but are the // same. protected update(value: T) { if (this.value ! == value) { this.value = value; this.queue += 1; // batch update the value, prevent multiple updates at the same time this.queue === 1 && this.flush(); } } // The subscribe method lets consumers listen for state updates. Calling // the `disconnect` method will stop the callback from being called in // the future. subscribe(callback: (value: T) => void): Disconnect { this.listeners.add(callback); return { disconnect: () => { this.listeners.delete(callback); }}; }}Copy the code

2. Implement the atom node based on the above classes


// The atom is a thin wrapper around the `Stateful` base class. It has a
// single method for updating the state.
//
// Note: `useState` allows you to pass a reducer function, you could add support
// for this if you wanted.
export class Atom<T> extends Stateful<T> {
  public setState(value: T) {
    super.update(value);
  }
}

// A helper function for creating a new Atom
// The `key` member is currently unused. I just kept it around to maintain a similar
// API to Recoil.
export function atom<V>(value: { key: string; default: V }): Atom<V> {
  return new Atom(value.default);
}
Copy the code

3. Realize the combination of partial attributes of multiple atomic nodes into one atomic node

// The Recoil selector function is a bit gnarley. Essentially the "get" function // is the way that selectors can subscribe to other selectors and atoms. type SelectorGenerator<T> = (context: { get: <V>(dep: Stateful<V>) => V }) => T; // The selector class. It extends `Stateful` so that it can be used as a value like // atoms. export class Selector<T> extends Stateful<T> { // Keep track of all the registered dependencies. We want to make sure we only // re-render once when they change. private registeredDeps = new Set<Stateful<any>>(); // When the get function is called, it allows consumers to subscribe to state // changes. This method subscribes to the dependency if it hasn't been already, // then returns it's value. private addDep<V>(dep: Stateful<V>): V { if (! this.registeredDeps.has(dep)) { dep.subscribe(() => this.updateSelector()); this.registeredDeps.add(dep); } return dep.snapshot(); } // A helper method for running the internal generator method, updating dependencies, // returning the computed state and updating all listeners. private updateSelector() { this.update(this.generate({ get: dep => this.addDep(dep) })); } // eslint-disable-next-line @typescript-eslint/no-parameter-properties constructor(private readonly generate: SelectorGenerator<T>) { // This needs to be undefined initially because of Typescript's inheritance rules // It's effectively "initialised memory" super(undefined as any); this.value = generate({ get: dep => this.addDep(dep) }); } } // A helper method for creating a new Selector // Likewise the `key` method is just for looking like Recoil. export function selector<V>(value: { key: string; get: SelectorGenerator<V>; }): Selector<V> { return new Selector(value.get); }Copy the code

4. Implement our hooks based on React hooks

import { useState, useEffect, useCallback } from 'react'; function isObject(value: any): boolean { return Object.prototype.toString.call(value) === "[object Object]" } // This hook will re-render whenever the supplied `Stateful` value changes. // It can be used with `Selector`s or `Atom`s. export function useCoiledValue<T>(value: Stateful<T>): T { const [, updateState] = useState({}); useEffect(() => { const { disconnect } = value.subscribe(() => updateState({})); return () => disconnect(); }, [value]); return value.snapshot(); } // Similar to the above method, but it also lets you set state. // eslint-disable-next-line @typescript-eslint/no-shadow export function useCoiledState<T>(atom: Atom<T>): [T, (value: T) => void] { const value = useCoiledValue(atom); // eslint-disable-next-line @typescript-eslint/no-shadow return [value, useCallback(value => atom.setState(value), [atom])]; } // Similar to the above method, but it only lets you set state. // eslint-disable-next-line @typescript-eslint/no-shadow export function useSetCoiledState<T>(atom: Atom<T>): (value: T) => void { return useCallback(value => atom.setState(isObject(value) ? {... atom.snapshot(), ... value} : value), [atom]) }Copy the code

We now have highly customizable state management. I’m sure there will be more new state management under React hooks. Are you following the new trends instead of sticking to the old redux, DVA,rematch