200 octets pour la gestion de l'état des composants React
- React hooks : c'est tout ce qu'il faut pour gérer l'état.
- ~ 200 octets , min + gz.
- API familière : utilisez simplement React comme d'habitude.
- API minimum : cinq minutes suffisent pour le comprendre.
- Écrit en TypeScript pour fournir une inférence de type automatique.
La question principale est: ce package est-il meilleur que Redux? Et bien ...
- Il est moins. Il est 40 fois plus petit.
- Il est plus rapide. Isolez les problèmes de performances au niveau des composants.
- C'est plus facile à apprendre. Dans tous les cas, vous devez pouvoir utiliser les hooks et le contexte React, ils sont sympas.
- Il est plus facile à intégrer. Connectez un composant à la fois sans rompre la compatibilité avec les autres bibliothèques React.
- C'est plus facile à tester. Tester séparément les réducteurs est une perte de temps; simplifiez les tests des composants React eux-mêmes.
- C'est plus simple en termes de frappe. Il est écrit de manière à maximiser l'utilisation de l'inférence de type.
- Il est minimaliste. C'est juste React.
Exemple de code
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"))
Attitude envers non déclaré
I (Jamie Kyle - environ Per.) Considérez cette bibliothèque comme le successeur d' Unstated . J'ai fait Unstated parce que j'étais convaincu que React lui-même faisait un excellent travail de gestion de l'État, et il ne manquait qu'un mécanisme simple pour séparer l'état général et la logique. Par conséquent, j'ai créé Unstated comme solution "minimale" pour ce problème.
Avec l'avènement des crochets, React est devenu beaucoup mieux en termes de mise en évidence de l'état général et de la logique. Tant mieux que, de mon point de vue, Unstated est devenu une abstraction inutile.
PAS MOINS , je crois que de nombreux développeurs ont peu d'idée sur la façon de séparer la logique et l'état général de l'application à l'aide de hooks React. Cela peut simplement être dû à une qualité insuffisante de la documentation et à l'inertie de la communauté, mais je pense qu'une API claire est capable de corriger cette lacune.
Unstated Next est cette API même. Au lieu d'être «l'API minimale pour partager l'état et la logique dans React», elle dispose désormais de «l'API minimale pour comprendre comment partager l'état et la logique dans React».
J'aime vraiment React, je veux que React s'épanouisse. Je préférerais que la communauté abandonne l'utilisation de bibliothèques externes pour gérer l'état comme Redux, et commence enfin à utiliser les outils intégrés à React en pleine force.
Si, au lieu d'utiliser Unstated, vous utilisez simplement React - je m'en réjouis. Écrivez à ce sujet sur vos blogs! Parlez-en lors de conférences! Partagez vos connaissances avec la communauté.
Guide non déclaré-suivant
Si vous n'êtes pas encore familier avec les crochets React, je vous recommande d'arrêter de lire et de lire
excellente documentation sur le site Web de React .
Ainsi, à l'aide de crochets, vous pouvez écrire quelque chose comme ce composant:
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> ) }
Si la logique des composants doit être utilisée à plusieurs endroits, elle peut être supprimée
dans un crochet personnalisé séparé:
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> ) }
Mais que faire quand on a besoin d'une condition générale, et pas seulement de logique?
Le contexte est utile ici:
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> ) }
C'est merveilleux et merveilleux; plus les gens écrivent dans ce style, mieux c'est.
Cependant, cela vaut la peine d'ajouter un peu plus de structure et de clarté pour que l'API exprime très clairement vos intentions.
Pour ce faire, nous avons ajouté la fonction createContainer()
afin que vous puissiez traiter vos hooks personnalisés comme des «conteneurs», afin que notre API claire et concise ne puisse pas être utilisée incorrectement.
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> ) }
Comparez le texte du composant avant et après nos modifications:
- 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> ) }
Si vous écrivez en TypeScript (et sinon, je vous recommande fortement de vous familiariser avec lui), vous obtiendrez également une meilleure inférence de type. Si votre hook personnalisé est fortement tapé, la sortie de tous les autres types fonctionnera automatiquement.
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>
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} /> }
Astuces
Conseil n ° 1: fusion des conteneurs
Puisque nous avons affaire à des crochets personnalisés, nous pouvons combiner des conteneurs à l'intérieur d'autres crochets.
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 } }
Astuce n ° 2: utilisez de petits conteneurs
Les conteneurs sont préférablement petits et clairement axés sur une tâche spécifique. Si vous avez besoin d'une logique métier supplémentaire dans des conteneurs - supprimez de nouvelles opérations dans des hooks séparés et laissez l'état être stocké dans des conteneurs.
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 } }
Conseil n ° 3: optimisation des composants
Il n'y a pas d '«optimisation» distincte pour unstated-next
, les méthodes habituelles d'optimisation des composants React sont suffisantes.
1) Optimisation des sous-arbres lourds en fractionnant les composants.
À:
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> ) }
Après:
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) Optimisation des opérations lourdes avec le crochet useMemo ()
À:
function CounterDisplay(props) { let counter = Counter.useContainer()
Après:
function CounterDisplay(props) { let counter = Counter.useContainer()
3) Réduisez le nombre de rendus à l'aide de React.memo () et useCallback ()
À:
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> ) }
Après:
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} /> }
Migration avec unstated
Je publie à dessein cette bibliothèque en tant que package séparé car toute l'API est complètement nouvelle. Par conséquent, vous pouvez installer les deux packages en parallèle et migrer progressivement.
Partagez vos impressions sur la transition vers unstated-next
, car au cours des prochains mois, je prévois de faire deux choses sur la base de ces informations:
- Assurez-vous que
unstated-next
répond à tous les besoins des utilisateurs unstated
. - Assurez-vous que pour les
unstated
il existe un processus clair et concis de migration vers unstated-next
.
J'ajouterai peut-être quelques API à l'ancienne ou à la nouvelle bibliothèque pour faciliter la vie des développeurs. En ce qui concerne unstated-next
, je promets que les API ajoutées seront aussi minimes que possible, et je ferai de mon mieux pour garder la bibliothèque petite.
À l'avenir, je unstated-next
probablement le code unstated-next
sur unstated
tant que nouvelle version majeure. unstated-next
sera toujours disponible afin que vous puissiez utiliser unstated@2
et unstated-next
dans le même projet en parallèle. Ensuite, lorsque vous avez terminé la migration, vous pouvez passer à unstated@3
et supprimer unstated-next
(bien sûr, mettre à jour toutes les importations ... il devrait y avoir suffisamment de recherche et de remplacement).
Malgré le changement spectaculaire de l'API, j'espère pouvoir vous fournir la migration la plus simple possible. Je serais heureux de tout commentaire sur ce qui pourrait être mieux fait.
Les références