100行代碼的Recoil

一、首先實現原子狀態管理的類——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

相關文章
相關標籤/搜索