react状态管理
If you like videos better and articles there’s the corresponding video for this article. It’s my first video so do give feedback even if it’s shit :)
如果您更喜欢视频和文章,那么这里有相应的视频。 这是我的第一个视频,所以即使它很烂也要提供反馈:)
什么是国家? (What is State?)
I’ll like to talk about how to understand state in a way that helps you make more informed decisions about managing it.
我想谈谈如何以一种可以帮助您做出更明智的管理决定的方式来理解状态。
State in reactive programming is data that dictates the configuration of the application in any moment in time.
React式编程中的状态是指示应用程序在任何时刻的配置的数据。
In simpler words, any part of the application that is subject to change has some associated data that changes it; that data is called a state. Now, this is where people stop, learn about state, and then after learning redux put everything inside redux global state. In order to understand how to better manage state, we need to know how many types of state there can be. I like to classify state in two ways and then choose the technologies that are best suited for managing those kinds of state.
简而言之,应用程序中任何可能更改的部分都有一些相关联的数据可以对其进行更改。 该数据称为状态。 现在,这是人们停下来了解状态的地方,然后在学习redux之后将所有内容放入redux全局状态。 为了了解如何更好地管理状态,我们需要知道可以有多少种状态。 我喜欢用两种方式对状态进行分类,然后选择最适合管理此类状态的技术。
根据产地分类 (Classification based on origin)
Wherefrom the state originates is an important thing to consider and can be classified into:
国家起源是一个重要的考虑因素,可以分为以下几类:
Client-side state: Any data that is generated and consumed on the client-side like UI state can be put into this category. The general rule of thumb while managing this sort of state is to see how far the components consuming it are in the component tree. We will talk about this sort of classification a bit later. A good practice to follow if you are unsure of managing this is start with local state and you if other components need it too, you can start lifting the state up the tree. Note: Never put UI state in the cache.
客户端状态:在客户端生成和使用的任何数据(如UI状态)都可以归入此类别。 管理此类状态的一般经验法则是查看消耗该状态的组件在组件树中的距离。 稍后我们将讨论这种分类。 如果不确定如何管理此问题,可以遵循的一个好习惯是从本地状态开始,如果其他组件也需要它,则可以开始将状态提升到树上。 注意:切勿将UI状态放入缓存中。
Server-side state: This is not be confused by the state that is managed between the server and the database. This state is essentially any data that is requested by the client from the server via REST/GraphQL APIs. This kind of data is not originated in the client and hence requires special treatment. We would not like to re-fetch this data from the server continuously and would like to cache it. Now if you are an expert you can certainly do it yourself with Redux/Mobx/Recoil and your own caching mechanism. But there are libraries out there that are better suited for this job, like ReactQuery/SWR if you are using REST, or Apollo if you are using GraphQL. These libraries are specialized to handle these kinds of state and optimally caches it.
服务器端状态:这不会与服务器和数据库之间管理的状态混淆。 此状态实质上是客户端通过REST / GraphQL API从服务器请求的任何数据。 此类数据不是源于客户端,因此需要特殊处理。 我们不希望连续地从服务器重新获取此数据,而是要对其进行缓存。 现在,如果您是专家,您当然可以使用Redux / Mobx / Recoil和您自己的缓存机制来自己做。 但是有一些库更适合此工作,例如,如果您使用的是REST,则为ReactQuery / SWR;如果您使用的是GraphQL,则为Apollo。 这些库专门处理这些状态并对其进行最佳缓存。
基于距离的分类 (Classification based on distance)
Now, this is something every developer at some point and another makes a mistake in. I too was guilty in putting everything in the global state. It’ll create unnecessary files, folder and boilerplate for simple things like updating a counter that is used in a single component. You’ll generally want to keep the data close to where you are consuming it. Now that we all agree redux is bad (JK :P) let’s move on to classifying it.
现在,这是每个开发人员在某个时候都犯下的错误,另一个错误。我对将所有内容置于全球状态也感到内gui。 它将创建不必要的文件,文件夹和样板,以完成诸如更新单个组件中使用的计数器之类的简单操作。 通常,您通常希望将数据保持在使用位置附近。 现在我们都同意redux不好(JK:P),让我们继续对其进行分类。
地方政府 (Local State)
This will be the state that you’ll need and use the most. It’s generally the state that is required by one component and is very easy to handle.
这将是您最需要和最常使用的状态。 通常,这是一个组件所要求的状态,并且非常容易处理。
Here we have the Counter component using a count state variable whose value is 5. In order to update/change the value, we’ll use two methods; one using the useState hook and another using useReducer.
在这里,我们有一个Counter组件,它使用一个值为5的count状态变量。为了更新/更改该值,我们将使用两种方法: 一个使用useState挂钩,另一个使用useReducer。
N
ñ
Example using useState:
使用useState的示例:
Link to code in the playground: click here
链接到操场上的代码: 单击此处
import React, { useState } from "react";const Counter: React.FC = () => {
const [count, setCount] = useState(0);
return (
Count: {count}
);
};export default Counter;
The useState hook provides us with a state variable and a callback to update the state. We can use the count
variable like a normal variable and the setCount()
callback when called with a new value of the count, reflects the update in all the places the variable count
is used.
useState挂钩为我们提供了状态变量和用于更新状态的回调。 我们可以像普通变量一样使用count
变量,并且在使用新的count值调用setCount()
回调时,可以在使用变量count
所有位置反映更新。
Example using useReducer:
使用useReducer的示例:
Link to code in the playground: click here
链接到操场上的代码: 单击此处
import React, { useReducer } from "react";type State = {
count: number;
};
type Action = { type: "increment" } | { type: "decrement" };
type Reducer = (state: State, action: Action) => State;const initialState: State = { count: 0 };const reducer: Reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
};const Counter: React.FC = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
Count: {state.count}
);
};
export default Counter;
I’ll not go into the details of the Flux architecture and Redux concepts (that’s a topic for another time) but as you can see useReducer follows the redux pattern and exposes a more fine-grain control of how the state is updated. You can easily interchange useState and useReducer and in most cases, my local state doesn’t get so complicated and I end up using useState.
我将不讨论Flux架构和Redux概念的细节(这是另一个话题),但是如您所见,useReducer遵循redux模式并提供了更精细的状态更新控制。 您可以轻松地将useState和useReducer互换,并且在大多数情况下,我的本地状态没有那么复杂,我最终使用了useState。
The basic difference here is you call the useReducer hook with a reducer function, that accepts the state and different actions, and also an initial state. You could have optionally passed in an initializer function for lazy initialization of the initial state. The hook returns you with the state variable and a dispatch method, unlike useState which return a setState callback. You can then dispatch the type of action depending on your need and the reducer function will execute the corresponding portion of the switch case to update your state.
这里的基本区别是,您调用带有reducer函数的useReducer钩子,该钩子接受状态和不同的动作以及初始状态。 您可以选择传入一个初始化函数,以对初始状态进行延迟初始化。 钩子返回状态变量和调度方法,与useState不同,后者返回setState回调。 然后,您可以根据需要调度操作的类型,reduce函数将执行switch case的相应部分以更新状态。
Cat trying to understand State management 猫试图了解国家管理附近组件使用的状态 (State used by nearby components)
Sometimes two or more nearby components requires the same state variable and the action you should take to manage that kind of state is to look at how far apart are they in the component tree.
有时,两个或更多附近的组件需要相同的状态变量,而您要采取的处理这种状态的措施是查看它们在组件树中的距离。
The state variable is in the yellow component and is passed down to the red ones 状态变量在黄色部分中,并向下传递到红色部分If the components using the state is nearby in the component tree like the above image, the simplest thing to do is to lift the state up to the component that is the parent to both of them. In this case, C2 is the first common parent of C5 and Counter. I generally do this only if the parent is one level above the children. But if the state is passed multiple levels deep, a lot of components will we just used for passing the state down the tree and without consuming it. This is called a prop drilling problem. An example would be:
如果使用状态的组件位于组件树附近(如上图所示),则最简单的操作是将状态提升到作为两个组件的父级的组件。 在这种情况下,C2是C5和Counter的第一个公共父对象。 通常,只有当父母比孩子高一级时,我才这样做。 但是,如果将状态深层传递给多个级别,那么我们将使用很多组件将状态沿树向下传递,而不会消耗它。 这称为支撑钻探问题。 一个例子是:
Example of prop passing:
道具传递示例:
Link to code in the playground: click here
链接到操场上的代码: 单击此处
import React, { useState } from "react";const CounterContainer: React.FC = () => {
const [count, setCount] = useState(0);
return ;
};interface ICounter {
count: number;
setCount: React.Dispatch>;
}const CounterMiddle: React.FC = ({ count, setCount }) => {
return (
I am a middle layer
);
};const Counter: React.FC = ({ count, setCount }) => {
return (
Count: {count}
);
};export default CounterContainer;
Here we are using useState to keep the state in the parent CounterContainer
component and passing the state and the callback down the tree to the Counter
. The problem here is that the CounterMiddle
doesn’t use the state and is used for just passing down the props to Counter.
在这里,我们使用useState将状态保留在父级CounterContainer
组件中,并将状态和回调沿树向下传递到Counter
。 这里的问题是, CounterMiddle
不使用状态,仅用于将道具传递给Counter.
To solve this issue people start using global state and with the growth of the project, you have 30–40 redux files just managing state that goes only 2–3 levels down the tree. A better solution would be to use a technique called composition. Let’s look at what it is.
为了解决此问题,人们开始使用全局状态,并且随着项目的发展,您只需要管理状态的30–40个redux文件,状态仅下降了2-3级。 更好的解决方案是使用一种称为合成的技术。 让我们看看它是什么。
Example using composition:
使用合成的示例:
Link to code in the playground: click here
链接到操场上的代码: 单击此处
import React, { useState } from "react";const CounterContainer: React.FC = () => {
const [count, setCount] = useState(0);
return (
);
};interface ICounterMiddle {
children: React.ReactNode;
}const CounterMiddle: React.FC = (props) => {
return (
I am a middle layer
{props.children}
);
};interface ICounter {
count: number;
setCount: React.Dispatch>;
}const Counter: React.FC = ({ count, setCount }) => {
return (
Count: {count}
);
};export default CounterContainer;
Here we apply a very simple yet neat trick and that is using the children props of a component. Observe that the CounterMiddle
has nothing to do with the Counter
and all its actual state are passed from the CounterContainer.
We can now make the CounterMiddle
take the Counter
as children
from the CounterContainer
itself and this will allow us to pass the props directly to the Counter
component. This sort of composition can help you avoid two to three levels deep prop drilling problem while also providing a better architectured React application and cleaner code.
在这里,我们应用了一个非常简单但巧妙的技巧,即使用组件的子项道具。 请注意, CounterMiddle
与Counter
无关,并且其所有实际状态都是从CounterContainer.
传递的CounterContainer.
现在,我们可以使CounterMiddle
采取Counter
为children
从CounterContainer
本身,这将使我们能够直接通过道具的Counter
组件。 这种组合可以帮助您避免两到三个级别的深层钻探问题,同时还可以提供更好的体系结构React应用程序和更简洁的代码。
全球状态 (Global State)
Now I believe, truly global state that is used by almost every component is rare and most use cases consist of components using the same piece of the state and are far apart in the component tree. An example of such state might be a button in the header toggling a sidebar/menu and an example of truly global state might be theme change in the entire website from dark to light.
现在我相信,几乎每个组件都使用的真正的全局状态是很少见的,大多数用例都是由使用同一状态块的组件组成,并且在组件树中相距甚远。 这种状态的一个例子可能是标题中的按钮切换了侧边栏/菜单,而真正的全局状态的一个例子可能是整个网站中主题的变化(从暗到亮)。
C3 and Counter situated far apart uses the same state C3和相距很远的Counter使用相同的状态In the above case, the count state is used by both C3 and Counter and they are situated far apart in the component tree. To manage these sort of state you can use various state management libraries like Redux/MobX/Recoil but if you notice through this article we are classifying the states and using the appropriate tools for managing them, so at the end when we reach to the global state, this might be only 10–15% of the entire state of the application.
在上述情况下,C3和Counter都使用了计数状态,并且它们在组件树中相距很远。 要管理这些状态,您可以使用各种状态管理库,例如Redux / MobX / Recoil,但是如果您通过本文注意到,我们正在对状态进行分类并使用适当的工具来管理它们,因此最后到达全局状态,可能只占应用程序整个状态的10%到15%。
So if your application is not going to generate huge amounts of global state you can manage this using React Context API. Using Context is very simple, you’ll need to declare a context with an initial state value and use a Provider to provide the state in whichever part of the tree you see fit (yes it doesn’t need to always be truly global). Now all you need to do is consume the state in the components that need them.
因此,如果您的应用程序不会生成大量的全局状态,则可以使用React Context API进行管理。 使用Context非常简单,您需要使用初始状态值声明一个上下文,并使用Provider在您认为合适的树的任何部分中提供状态(是的,它不一定总是真正的全局)。 现在,您需要做的就是消耗需要它们的组件中的状态。
Example using Context API:
使用上下文API的示例:
Link to code in the playground: click here
链接到操场上的代码: 单击此处
import React, { useState, createContext, useContext } from "react";interface ICounter {
count: number;
setCount: React.Dispatch>;
}
const CountContext = createContext({ count: 0, setCount: () => {} });const CounterContainer: React.FC = () => {
const [count, setCount] = useState(0);
const initValue = { count: count, setCount: setCount };
return (
);
};const CounterMiddle: React.FC = () => {
return (
I am a middle layer
);
};const Counter: React.FC = () => {
const { count, setCount } = useContext(CountContext);
return (
Count: {count}
);
};export default CounterContainer;
Here we declare a context called CounterContext
that takes a count
and a setCount
and we’ll be using useState to manage the actual state changing.
在这里,我们声明了一个称为CounterContext
的上下文,该上下文接受一个count
和一个setCount
,我们将使用useState来管理实际的状态更改。
Note: Default value is not the same as initial value. The default is used as a fallback.
注意:默认值与初始值不同。 默认值用作后备。
We pass an initial value to the CounterContext.Provider
and wrap it around CounterContainer
so that all children of that component can access the state. The state will not be accessible outside the scope of the provider, which is exactly what we want.
我们将初始值传递给CounterContext.Provider
并将其包装在CounterContainer
周围,以便该组件的所有子代都可以访问该状态。 在提供者的范围之外将无法访问该状态,这正是我们想要的。
Now, all we have to do is get the state and the callback using an useContext hook from React and use and change the state the same way we used useState.
现在,我们要做的就是使用React的useContext钩子获取状态和回调,并使用和使用useState相同的方式使用和更改状态。
Now that we have learned a lot about managing state, here’s a bonus section for you.
现在我们已经学到了很多有关状态管理的知识,这是为您准备的奖金部分。
GraphQL奖励 (Bonus with GraphQL)
The landscape of state management changes when we enter graphQL. If you are using libraries like Apollo to manage your GraphQL state you can replace everything with Apollo equivalents.The server cache is maintained by Apollo InMemory cache,the local state can be maintained by Apollo’s reactive vars,and finally, the global state too can be maintained in a lot of ways, one such being attaching a client directive to your Queries and Mutations.
当输入graphQL时,状态管理的格局将发生变化。 如果使用诸如Apollo之类的库来管理GraphQL状态,则可以用Apollo等效项替换所有内容。服务器缓存由Apollo InMemory缓存维护,本地状态可以由Apollo的React变量维护,最后,全局状态也可以可以通过多种方式进行维护,例如将客户指令附加到您的查询和突变中。
But that being said I still prefer using React’s own solution for state management most of the time, at least for local state.
话虽这么说,但我还是更喜欢在大多数时间使用React自己的解决方案进行状态管理,至少是针对本地状态。
结论 (Conclusion)
State Management in React can be a sticky affair, but I hope that I could explain the essence of classifying state in your React application. To summarise our discussion:
React中的状态管理可能是一件棘手的事情,但是我希望我能解释一下您的React应用程序中对状态进行分类的本质。 总结一下我们的讨论:
This is how it looks when you use state management tools that fit the purpose 这是使用适合目的的状态管理工具时的外观If you can understand which kind of state is it, you can apply the right tool for the job.If you can understand who uses the state, you can put it in the appropriate position in the tree and architecture your components in a better way.
如果您了解状态是哪种状态,则可以为该作业应用正确的工具;如果您了解谁使用了状态,则可以将其放置在树中的适当位置,并以更好的方式构建组件。
Examples from real projects:
实际项目中的示例:
Now if you have come this far and is interested to see these applied in a real project that is under development, check this out, called Litmus:https://github.com/litmuschaos/litmus/tree/litmus-portal/litmus-portal/frontend/src
现在,如果您走了这么远并且有兴趣看到将它们应用到正在开发的真实项目中,请查看名为Litmus的项目: https : //github.com/litmuschaos/litmus/tree/litmus-portal/litmus-门户/前端/ src
The tech stack consists of Typescript, Apollo and Redux. Here we use Redux because we do have a lot of global states and the amazing developer tooling that Redux provides.You can study the code, suggest improvements or open a discussion in Slack (We have a #litmus channel under Kubernetes slack). I’m a developer contributing in Litmus and I’ll try my best to answer your queries and explain why these technologies are being used and if you like the project, you can leave us a star.
技术堆栈由Typescript,Apollo和Redux组成。 这里我们使用Redux是因为我们确实有很多全局状态以及Redux提供的了不起的开发人员工具。您可以在Slack中学习代码,提出改进建议或进行讨论(我们在Kubernetes slack下拥有#litmus频道)。 我是Litmus的一名开发人员,我将尽力回答您的问题,并解释为什么使用这些技术,如果您喜欢该项目,可以给我们留下一颗星 。
Catato 卡塔托You can find me on Twitter here.If you are more of a LinkedIn person, catch me here.Or if Github is your thing, I’m there too.
你可以找到我的Twitter 这里 。如果你是一个LinkedIn的人,赶上我在这里 。或者如果Github的是你的事,我有过。
Feel free to contact me if you have a problem with React, Web development in general or just hit me up with a cat gif if that’s your thing. I’m no expert but I’ll try my best to help you. Thanks for sticking for so long, here’s a catato.
如果您在React,Web开发方面遇到问题,请随时与我联系,如果那是您的事,请给我加个cat gif。 我不是专家,但我会尽力帮助您。 感谢您坚持这么长时间,这是catato。
Adios ❤
阿迪奥斯❤
翻译自: https://medium.com/@arkajyoti31/react-state-and-how-to-manage-it-b5c710b1d54c
react状态管理