Reescrevendo a biblioteca 반충 파라 React em 100 linhas

Créditos da Imagem
Recoiléuma nova biblioteca do React,escrita por algumas pessoas do Facebook que trabalham em uma ferramenta chamada Ela surgiu devido a problemas de ergonomia edesempenho com context euseState._mabiblioteca muito inteligente e quase todo mundo encontraáuma utilidade para ela-confira este se quiser saber mais.
come ço eu fiquei realmente suprreso com a conversa sobre teoria dos grá ficos e a má gica maravillhosa que o Recoil executa,mas deposis de um tempo comecei a ver que talvez n ão seja t ão special assim이 없습니다.Aqui estáminha chance de implementar algo semelhante!
Antes de começar,observe que a maneira como implementei meu clone do Recoilécompletamente diferente de como o Recoil realéimplementado.Não presuma nada sobre backil disso.

Átomos


O backiléconstruído em torno do conceito de“átomos”.Osátomos são pequenas partes atômicas de estado que vocêpode assinar e atualizar em seus components.
Para começar,vou criar uma classe chamadaAtomque vai envolver algum valorT.유럽연합adicionei métodos Auxiliarsupdateesnapshotparapermitirque vocêobtenhae defina o valor.
class Atom<T> {
  constructor(private value: T) {}

  update(value: T) {
    this.value = value;
  }

  snapshot(): T {
    return this.value;
  }
}
Para ouvir as mudanças no estado,vocêprecisa usaro padrão do observador.Issoécomumente visto em bibliotecas comoRxJS,mas,neste caso,vou escrev uma versão síncrona simples do zero.
Parasaberquemestáouvindooestado,usoumSetcom 리셋.UmSet(ou concento de hash)uma estrutura de dados que contém apenas itensúnicos.Em JavaScript,ele pode ser facilmente transformado Em um array e possui métodosúteis para adicionar e remover itens rapidamente.
A adição de um ouvinteéfeita por meio do métodosubscribe.OmétodosubscribereturnaDisconnecter-uma 인터페이스 내용umétodoqueimpediráumouvintedeescutar.Issoéchamado quando um componente Reactédesmontado e vocènão deseja mais ouvir as alteraçes.
Em seguida,um método chamadoemitéadicionado.Este método percorre cada um dos ouvintes e fornece a eles o valor atual do estado.
Por fim,atualizo o métodoupdatepara emitir os novos valores sempre que o estado for definido.
type Disconnecter = { disconnect: () => void };

class Atom<T> {
  private listeners = new Set<(value: T) => void>();

  constructor(private value: T) {}

  update(value: T) {
    this.value = value;
    this.emit();
  }

  snapshot(): T {
    return this.value;
  }

  emit() {
    for (const listener of this.listeners) {
      listener(this.snapshot());
    }
  }

  subscribe(callback: (value: T) => void): Disconnecter {
    this.listeners.add(callback);
    return {
      disconnect: () => {
        this.listeners.delete(callback);
      },
    };
  }
}
오파!
Éhora de escrever oá tomo em nossos 구성 요소 반응.Para fazer isso,Crei um gancho chamadouseCoiledValue.( soa familiar? )
Este gancho returna o estado atual de umátomo,e escuta e renderiza novatement e sempre que o valor mudar.Sempre que o ganchoédesmontado,ele desconecta o ouvinte.
Uma coisa um pouco estranha aquiéo ganchoupdateState.Ao executar um estado definido com uma nova referência de objeto({}),o React irárenderizar novamente o component.Issoéum pouco um hack,maséuma maneira fácil de garantir que o componente seja renderizado Novatemente.
export function useCoiledValue<T>(value: Atom<T>): T {
  const [, updateState] = useState({});

  useEffect(() => {
    const { disconnect } = value.subscribe(() => updateState({}));
    return () => disconnect();
  }, [value]);

  return value.snapshot();
}
Em seguida,adicionei um métodouseCoiledState.Tem uma API muito semelhante auseState-fornece o valor atual do estado e permite que vocêdefina um novo.
export function useCoiledState<T>(atom: Atom<T>): [T, (value: T) => void] {
  const value = useCoiledValue(atom);
  return [value, useCallback((value) => atom.update(value), [atom])];
}
Agora que implementamos esses ganchos,éhora de passar para os seletores.Antes disso,vamos refatorar um pouco que temos.
Um seletoréUm valor com estado,assim como Umátomo.Para tornar a implementaão deles um pouco mais fácil,moverei a maior parte da lógica deAtomPara uma classe base chamadaStateful.
class Stateful<T> {
  private listeners = new Set<(value: T) => void>();

  constructor(private value: T) {}

  protected _update(value: T) {
    this.value = value;
    this.emit();
  }

  snapshot(): T {
    return this.value;
  }

  subscribe(callback: (value: T) => void): Disconnecter {
    this.listeners.add(callback);
    return {
      disconnect: () => {
        this.listeners.delete(callback);
      },
    };
  }
}

class Atom<T> extends Stateful<T> {
  update(value: T) {
    super._update(value);
  }
}
Seguindo em frente!

Seletores 회사


Um seletoréa versão de backil de“valores computados”ou“redutores”.Em suaspróprias palavras:

Um seletor representa uma parte derivado do estado. Você pode pensar no estado derivado como a saída da passagem do estado para uma função pura que modifica o estado fornecido de alguma forma.


API para seletores no backilébastante simples,vocêcria um objeto com um método chamadogete tudo que esse método returnaéo valor do seu estado.Dentro do métodoget,vocêpode assinar outras partes do estado e,sempre que elas forem atualizadas,o seu seletor também será.
Em nosso caso,vou renomear o métodogeta ser chamado degenerator.Estou chamando-o assim porqueéessencialmente uma funão de fábrica que deve gerar o próximo valor do estado,com base em tudo que canalizado para ele.

código가 없으면podemoscapturaressemétodogenerate는tipo의 조수입니다.
type SelectorGenerator<T> = (context: GeneratorContext) => T;
Para aqueles que não estão familizados com TypeScript,essaéuma funão que recebe um objeto de contexto(GeneratorContext)como par–metro e returna algum valorT.Esse valor de returnoéo que se torna o estado interno do seletor.
O que O objetoGeneratorContextfaz?
Bem,éassim que os seletores usam outras partes do estado ao gerar seu próprio estado interno.De agora em diante,essas partes do estado como'dependências'를 인용할 수 있도록 허락해 주십시오.
interface GeneratorContext {
  get: <V>(dependency: Stateful<V>) => V
}
Sempre que alguém chama o métodogetnoGeneratorContext,ele adiciona um pedaço de estado como uma dependencia.atualizada, Isso signific que sempre que uma dependência, o seletor também será.
우리는 선거에서 승리할 것이다.
function generate(context) {
  // Registra "NameAtom" como dependência
  // e retorna seu valor
  const name = context.get(NameAtom);
  // Faz o mesmo para "AgeAtom"
  const age = context.get(AgeAtom);

  // Retorna um novo valor usando os átomos anteriores
  // Ex: "Bob is 20 years old"
  return `${name} is ${age} years old.`;
};
Com a funão de geraão de estado fora do caminho,vamos criar a classeSelector.Essa classe deve aceitar a fun227;o de geraão como um par–metro do construtor e usar um métodogetDepna classe para returnar o valor dasAtomde dedededependencias.
Vocêpode notar no construtor que escrevisuper(undefined as any).Isso ocorre porquesuperdeve ser a primeira linha no construtor de uma classe derivida.Se ajudar,neste caso vocêpode pensar emundefinedcomo uma memória não inicializada.
export class Selector<T> extends Stateful<T> {
  private getDep<V>(dep: Stateful<V>): V {
    return dep.snapshot();
  }

  constructor(
    private readonly generate: SelectorGenerator<T>
  ) {
    super(undefined as any);
    const context = {
      get: dep => this.getDep(dep) 
    };
    this.value = generate(context);
  }
}
Este seletor sóbom para gerar estado uma vez.Para reagirás mudanças nas Dependencias,precisamos assinálas.
Para fazer isso,vamos atualizar o métodogetDepPara assinar as Dependencias e chamar o métodoupdateSelector.Para garantir que o seletor seja atualizado apenas uma vez a cada alteraão,vamos acompanhar as depenências usando umSet.
O métodoupdateSelectorémuito semelhante ao construtor do exemplo front.Ele criaGeneratorContext,executa o métodogeneratee então usa o métodoupdateda classe baseStateful.
export class Selector<T> extends Stateful<T> {
  private registeredDeps = new Set<Stateful>();

  private getDep<V>(dep: Stateful<V>): V {
    if (!this.registeredDeps.has(dep)) {
      dep.subscribe(() => this.updateSelector());
      this.registeredDeps.add(dep);
    }

    return dep.snapshot();
  }

  private updateSelector() {
    const context = {
      get: dep => this.getDep(dep)
    };
    this.update(this.generate(context));
  }

  constructor(
    private readonly generate: SelectorGenerator<T>
  ) {
    super(undefined as any);
    const context = {
      get: dep => this.getDep(dep) 
    };
    this.value = generate(context);
  }
}
당장 취소!반충tem algumas fun ões AUXILIARS para criar á tomos e seletores.Como a maioria dos desenvolvedores de JavaScript는 이를 Como má prática,eles ajudar ão a mascarar nossas cratades 클래스로 간주합니다.
어, 여덟 번째, 우마토모...
export function atom<V>(
  value: { key: string; default: V }
): Atom<V> {
  return new Atom(value.default);
}
E um para criar um seletor...
export function selector<V>(value: {
  key: string;
  get: SelectorGenerator<V>;
}): Selector<V> {
  return new Selector(value.get);
}
오, lembra daquele ganchouseCoiledValue de antes?Vamos atualizar isso para aceitar seletores também:
export function useCoiledValue<T>(value: Stateful<T>): T {
  const [, updateState] = useState({});

  useEffect(() => {
    const { disconnect } = value.subscribe(() => updateState({}));
    return () => disconnect();
  }, [value]);

  return value.snapshot();
}
Éisso aí!콘세쿠모스!🎉
Dèum tapinha nas suas costas!
아카바도?
Por uma questão de brevidade(e para usar aquele título de“100 linhas”para ganhar uns claiques),decidi omitir comentários,testes e exemplos.Se vocèquiser uma explicaèo mais completa(ou quiser brincar com examplos),tudo isso estáno meurepositório “recoil-clone” do Github.
Também háumexemplo de siteao vivo para que vocêpossa testálo.

결론


Umavez lique todo bom 소프트웨어 개발자는 o suficiente para qualquer pessoa possa reescrevêlo, se necess ário를 간소화했다.O 반충tem muitos recursos que n ã O implementei aqui,mas é empolgante verum design t ã O simples e inquitivo que pode ser razoavelmente implementado manualmente.
Antes de decidir lançar meu Bootlog Backil em produço,certifique se de verificar o seguinte:
  • Os seletores nunca cancelam a inscriço dosátomos.Isso signific que eles vazarão memória quando vocèparar de usálos.
  • 반응introduziuumganchochamadouseMutableSource.Se vocêestiver usando uma versão recente do React,vocêdeve usá-lo ao invés desetStateemuseCoiledValue.
  • Seletores eÁtomos fazem apenas uma comparaço Surface entre estados antes de renderizar Novatemente.Em alguns casos,pode fazer sentido alterar isso para uma comparaço profunda.
  • O backil usa um campokeypara cadaátomo e seletor queéusado como metadados para um recurso chamado“observaãO em todo O aplicativo”.Eu o inclu í apesar de n ão us á-lo para manter a API 익숙합니다.
  • O Receive Supporte a seletores assíncronos,isso seria uma tarefa gigantesca,entãO fiz questãO de excluílo.
  • Além disso,espero ter mostrado a vocèque nem sempre vocèprecisa olhar para uma biblioteca ao decidir sobre as soluèes de gerenciamento de estado.Na maioria das vezes,vocêpode projetar algo que se encaixa perfeitante Na sua soluão-afinal,foi assim que o Recoil nasceu.
    Deposis de escrever este post,vi a bibliotecajotai._mconcento de recursos muito semelhante ao meu 클론 e of erece suporte ass íncrono!

    Créditos 회사


  • Rewriting Facebook's "Recoil" React library from scratch in 100 lines,escrito originalmente por.
  • 좋은 웹페이지 즐겨찾기