Unstated-next: gerenciamento de estado minimalista de um aplicativo React

200 bytes para gerenciamento de estado dos componentes do React

  • Ganchos de reação : basta para gerenciar o estado.
  • ~ 200 bytes , min + gz.
  • API familiar : basta usar o React como de costume.
  • API mínima : cinco minutos são suficientes para descobrir.
  • Escrito em TypeScript para fornecer inferência automática de tipo.

A principal questão é: este pacote é melhor que o Redux? Bem ...


  • Ele é menos É 40 vezes menor.
  • Ele é mais rápido. Isolar problemas de desempenho no nível do componente.
  • É mais fácil aprender. De qualquer forma, você precisa poder usar os ganchos e o contexto do React, pois são legais.
  • É mais fácil de integrar. Conecte um componente de cada vez sem quebrar a compatibilidade com outras bibliotecas do React.
  • É mais fácil testar. Testar redutores separadamente é uma perda de tempo; simplifique o teste dos componentes do React.
  • É mais simples em termos de digitação. Está escrito para maximizar o uso da inferência de tipo.
  • Ele é minimalista. Isso é apenas reagir.

Exemplo de código


import React, { useState } from "react" import { createContainer } from "unstated-next" import { render } from "react-dom" function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <span>{counter.count}</span> <button onClick={counter.increment}>+</button> </div> ) } function App() { return ( <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } render(<App />, document.getElementById("root")) 

Atitude em relação ao não declarado


I (Jamie Kyle - aprox. Por.) Considere esta biblioteca como sucessora de Não declarado . Fiz Unstate porque estava convencido de que o próprio React fazia um ótimo trabalho de gerenciamento do estado, e faltava apenas um mecanismo simples para separar o estado geral e a lógica. Portanto, criei Unstated como a solução "mínima" para esse problema.


Com o advento dos ganchos, o React se tornou muito melhor em termos de destaque do estado geral e da lógica. Tanto melhor que, do meu ponto de vista, o Unstated se tornou uma abstração desnecessária.


NÃO MENOS , acredito que muitos desenvolvedores têm pouca idéia de como separar a lógica e o estado geral do aplicativo usando ganchos React. Isso pode ser simplesmente devido à qualidade insuficiente da documentação e à inércia da comunidade, mas acredito que uma API clara possa corrigir essa falha.


Não declarado A seguir é essa mesma API. Em vez de ser a "API mínima para compartilhar estado e lógica no React", agora ela tem a "API mínima para entender como compartilhar estado e lógica no React".


Eu realmente gosto de React, quero que React floresça. Prefiro que a comunidade abandone o uso de bibliotecas externas para gerenciar estados como o Redux e, finalmente, comece a usar as ferramentas incorporadas ao React com força total.


Se, em vez de usar Unstated, você apenas usar React - eu aceitarei isso. Escreva sobre isso em seus blogs! Fale sobre isso em conferências! Compartilhe seu conhecimento com a comunidade.


Unstated-next Guide


Se você ainda não está familiarizado com os ganchos do React, recomendo que pare de ler e ler
excelente documentação no site do React .


Portanto, com a ajuda de hooks, você pode escrever algo como este componente:


 function CounterDisplay() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return ( <div> <button onClick={decrement}>-</button> <p>You clicked {count} times</p> <button onClick={increment}>+</button> </div> ) } 

Se a lógica de componentes precisar ser usada em vários locais, ela poderá ser retirada
em um gancho personalizado separado:


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } function CounterDisplay() { let counter = useCounter() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

Mas o que fazer quando você precisa de uma condição geral, e não apenas lógica?
O contexto é útil aqui:


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContext(null) function CounterDisplay() { let counter = useContext(Counter) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } function App() { let counter = useCounter() return ( <Counter.Provider value={counter}> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 

É maravilhoso e maravilhoso; quanto mais as pessoas escrevem nesse estilo, melhor.


No entanto, vale a pena adicionar um pouco mais de estrutura e clareza para que a API expresse suas intenções com muita clareza.


Para fazer isso, adicionamos a função createContainer() , para que você possa tratar seus ganchos personalizados como "contêineres", para que nossa API clara e nítida seja simplesmente impossível de usar incorretamente.


 import { createContainer } from "unstated-next" function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } function App() { return ( <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 

Compare o texto do componente antes e depois das nossas alterações:


 - import { createContext, useContext } from "react" + import { createContainer } from "unstated-next" function useCounter() { ... } - let Counter = createContext(null) + let Counter = createContainer(useCounter) function CounterDisplay() { - let counter = useContext(Counter) + let counter = Counter.useContainer() return ( <div> ... </div> ) } function App() { - let counter = useCounter() return ( - <Counter.Provider value={counter}> + <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 

Se você escrever no TypeScript (e se não, eu recomendo fortemente que você se familiarize com ele), também obterá uma melhor inferência de tipo. Se seu gancho personalizado for fortemente digitado, a saída de todos os outros tipos funcionará automaticamente.


API


createContainer(useHook)


 import { createContainer } from "unstated-next" function useCustomHook() { let [value, setValue] = useState() let onChange = e => setValue(e.currentTarget.value) return { value, onChange } } let Container = createContainer(useCustomHook) // Container === { Provider, useContainer } 

<Container.Provider>


 function ParentComponent() { return ( <Container.Provider> <ChildComponent /> </Container.Provider> ) } 

Container.useContainer()


 function ChildComponent() { let input = Container.useContainer() return <input value={input.value} onChange={input.onChange} /> } 

useContainer(Container)


 import { useContainer } from "unstated-next" function ChildComponent() { let input = useContainer(Container) return <input value={input.value} onChange={input.onChange} /> } 

Dicas


Dica 1: mesclando contêineres


Como lidamos com ganchos personalizados, podemos combinar contêineres dentro de outros ganchos.


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment, setCount } } let Counter = createContainer(useCounter) function useResettableCounter() { let counter = Counter.useContainer() let reset = () => counter.setCount(0) return { ...counter, reset } } 

Dica 2: use recipientes pequenos


Os recipientes devem ser pequenos e focados claramente em uma tarefa específica. Se você precisar de lógica comercial adicional em contêineres - realize novas operações em ganchos separados e deixe o estado ser armazenado em contêineres.


 function useCount() { return useState(0) } let Count = createContainer(useCount) function useCounter() { let [count, setCount] = Count.useContainer() let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) let reset = () => setCount(0) return { count, decrement, increment, reset } } 

Dica 3: Otimização de componentes


Não existe uma “otimização” separada para o unstated-next ; os métodos usuais para otimizar os componentes do React são suficientes.


1) Otimização de subárvores pesadas dividindo componentes.


Para:


 function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <div> <div> <div> <div>   </div> </div> </div> </div> </div> ) } 

Depois:


 function ExpensiveComponent() { return ( <div> <div> <div> <div>   </div> </div> </div> </div> ) } function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <ExpensiveComponent /> </div> ) } 

2) Otimização de operações pesadas com o gancho useMemo ()


Para:


 function CounterDisplay(props) { let counter = Counter.useContainer() //    ,   `counter` —   let expensiveValue = expensiveComputation(props.input) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

Depois:


 function CounterDisplay(props) { let counter = Counter.useContainer() //    ,     let expensiveValue = useMemo(() => { return expensiveComputation(props.input) }, [props.input]) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

3) Reduza o número de novas renderizações usando React.memo () e useCallback ()


Para:


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay(props) { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

Depois:


 function useCounter() { let [count, setCount] = useState(0) let decrement = useCallback(() => setCount(count - 1), [count]) let increment = useCallback(() => setCount(count + 1), [count]) return { count, decrement, increment } } let Counter = createContainer(useCounter) let CounterDisplayInner = React.memo(props => { return ( <div> <button onClick={props.decrement}>-</button> <p>You clicked {props.count} times</p> <button onClick={props.increment}>+</button> </div> ) }) function CounterDisplay(props) { let counter = Counter.useContainer() return <CounterDisplayInner {...counter} /> } 

Migração com unstated


Publico propositadamente esta biblioteca como um pacote separado, porque toda a API é completamente nova. Portanto, você pode instalar os dois pacotes em paralelo e migrar gradualmente.


Compartilhe suas impressões da transição para unstated-next , porque nos próximos meses planejo fazer duas coisas com base nessas informações:


  • Certifique-se de que unstated-next atenda a todas as necessidades de usuários unstated .
  • Certifique-se de que para unstated exista um processo claro e conciso de migração para unstated-next .

Talvez eu adicione algumas APIs à biblioteca antiga ou nova para facilitar a vida dos desenvolvedores. Quanto ao unstated-next , prometo que as APIs adicionadas serão o mínimo possível e farei o possível para manter a biblioteca pequena.


No futuro, provavelmente unstated-next o código unstated-next volta para unstated como uma nova versão principal. unstated-next ainda estará disponível para que você possa usar unstated@2 e unstated-next no mesmo projeto em paralelo. Em seguida, ao concluir a migração, você pode atualizar para unstated@3 e excluir unstated-next (é claro, atualizando todas as importações ... deve haver pesquisa e substituição suficientes).


Apesar da mudança drástica na API, espero poder fornecer a migração mais fácil possível. Eu ficaria feliz em quaisquer comentários sobre o que poderia ser feito melhor.


Referências


Source: https://habr.com/ru/post/pt451410/


All Articles