一、首先實現原子狀態管理的類——Stateful
// 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);
},
};
}
}
複製代碼
二、基於上述類實現原子節點——atom
// 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);
}
複製代碼
三、實現拾取多個原子節點部分屬性合併成一個原子節點
// 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);
}
複製代碼
四、基於react的hooks實現咱們的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])
}
複製代碼
至此,咱們就實現了高度可定製的狀態管理,相信在React的hooks下新的狀態管理會愈來愈多,你是否已經跟隨新的潮流,而不在拘泥於老的redux,dva,rematch