React is a view layer framework. Its core idea is UI = f(state), that is, "UI is a projection of state", state flows from top to bottom, and the entire React component tree is driven by state. When a React application is complex enough and the component nesting is deep enough, the state flow in the component tree becomes difficult to control (for example, how do you track the changes made when the state of the parent node flows to the leaf nodes). At this time, we need to manage the state. While performing state management, we also need to distinguish which state types are in the React application to facilitate the development of the most suitable state management solution.
State type
React application state can be divided into two categories in a macro sense:
- Client State: Mostly used to control the UI display of the client. The Local state, Feature state, and Application state described below are all client states.
- Server State: The data obtained by the client through asynchronous requests.
Local state-Local state
only exists in a single component state, we can call it "local" or "UI" state.
Usually it can help us manage user interface interactions, such as showing and hiding content or enabling and disabling buttons. It also often controls what is rendered while we are waiting. Consider the following example:
function Text() {
const [viewMore, setViewMore] = useState(false);
return (
<Fragment>
<p>
React makes it painless to create interactive UIs.
{
viewMore && <span>
Design simple views for each state in your application.
</span>
}
</p>
<button onClick={() => setViewMore(true)}>read more</button>
</Fragment>
)
}
viewMore
is a state that is only meaningful for this particular component, and its function is to control the visibility of the text here.
In this example, viewMore
is useless for the other components of the application, so you don't have to leak this status outside the Text
Feature state-Feature state
combines two or more components, these components need to know the same information, we define this state as a "feature" state.
It can be said that every non-local state belongs to this category. However, not every feature state is the same, and we will learn more about this in this article.
A good example of a feature state is the form state, which looks a bit like the UI state described above, but it combines multiple inputs and manages multiple components.
import { useState } from "react";
const Skill = ({ onChange }) => (
<label>
技能:
<input type="text" onChange={(e) => onChange(e.target.value)} />
</label>
);
const Years = ({ onChange }) => (
<label>
工龄:
<input type="text" onChange={(e) => onChange(e.target.value)} />
</label>
);
export default function Form() {
const [skill, setSkill] = useState("");
const [years, setYears] = useState("");
const isFormReady = skill !== "" && years !== "";
return (
<form onSubmit={() => alert("提及成功")}>
<Skill onChange={setSkill} /> <br />
<Years onChange={setYears} />
<button disabled={!isFormReady}>submit</button>
</form>
);
}
Here we have a Form form, which contains two fields skill
and years
. By default, the submit button of the Form form is disabled. The button becomes enabled only when both inputs have values. Please pay attention to skill
and years
are all required so that we can calculate the value of isFormReady
Form is the best place to implement this kind of logic, because it contains all the associated elements.
This example reflects a boundary between the feature state and the application state. Redux can also be used for state management here, but this is not a good practice. before it is promoted and become the application state. Identify the characteristic state .
Application state-Application state
application status is the status that guides the user's overall experience, which may be authorization status, profile data, or global style themes. may be needed anywhere in the application. Here is a simple example:
import React, { useContext, useState } from "react";
const ThemeContext = React.createContext();
const Theme = ({ onChange }) => {
const { theme } = useContext(ThemeContext);
return `Theme: ${theme}`;
}
const ThemeSelector = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<select value={theme} onChange={toggleTheme}>
<option value="light">light</option>
<option value="dark">dark</option>
</select>
);
}
export default function App() {
const [theme, setTheme] = useState("light");
const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light");
const themeStyle = {
background: theme === "light" ? "#fff" : "#b9b9b9"
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className="App">
<header style={themeStyle}>
<Theme />
</header>
<footer style={themeStyle}>
<ThemeSelector />
</footer>
</div>
</ThemeContext.Provider>
);
}
In this example, there is a header and a footer, and they both need to know the theme of the current application. theme
to the application state by using the Context API. The purpose of this is to make Theme
and ThemeSelector
(they also need to access theme
, but they may be nested in other components).
If certain properties are required by many components and may need to be updated from remote components, then we may have to set it to the application state.
Server state-Server state
The server status can be understood as the interface status. The server status has the following characteristics: it is stored remotely, cannot be directly controlled locally, requires asynchronous API to query and update, and may be changed by another requester without knowing it. Cause data to be out of sync, etc.
Existing state management libraries (such as Mobx, Redux, etc.) are suitable for managing client state, but they don't care how the client requests remote data asynchronously, so they are not suitable for handling asynchronous state from the server.
To identify server status, you must consider the frequency of data changes and the source of these data. If it is more or less static, then we should avoid getting it from the server, just store it to the client and pass it as a global variable.
State management
As the React ecosystem continues to grow, the community provides a variety of ways to solve state management, such as Redux based on Flux ideas, Zustad and React's own useReducer + Context , 9ba8bfd7bfcodil, and 161ba8bfd7b4b7 based on And so on; there is also the representative of the responsive program Mobx , here is not too much for the introduction, let's understand some of the most commonly used.
Hooks are the main mechanism for state management
useState
or useReducer
is the way most people use to manage local state. useState
actually the main re-rendering mechanism in React, which is why most state management libraries use it at the bottom. If you go deeper, you will find that the Custom Hooks provided by these different third-party libraries all rely on the default useState
, useReducer
and useffect
. Below is a simple example of useReducer
provided on the official website:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
Lifting State Up-Lifting State Up
In React, the shared state can be realized by moving the state that needs to be shared among multiple components to their nearest common parent component, which is called "state promotion".
view-more
example at the beginning of this article (local state controls the visibility of the text). However, if there is a new request, we have a global "Expand Text" button, and then we must upgrade this state, and then pass it props
// viewMore 是一个 local state
function Component() {
const [viewMore, setViewMore] = useState(false);
return (
<Fragment>
<p>Text... { viewMore && <span>More text ...</span>}</p>
<button onClick={() => setViewMore(true)}>read more</button>
</Fragment>
);
}
// 将 viewMore 提升为组件的 feature state
function Component({ viewMore }) {
return (
<p>Text... { viewMore && <span>More text ...</span>}</p>
);
}
When you see a local state variable becomes a props
, we can regard it as a state promotion. requires attention prop drilling
. You don’t want to have many props
to their sub-components.
Use Context API
Context API provides a way to pass data through the component tree without manually passing down props at each level.
In a typical React application, data is passed from top to bottom (parent to child) through props, but this approach is extremely cumbersome for certain types of props (for example: regional preference, UI theme), These attributes are required by many components in the application. Context provides a way to share such values between components without having to explicitly pass props through the component tree layer by layer.
Context is designed to share data that is "global" to a component tree, such as the currently authenticated user, theme, or preferred language. Therefore, Context is mainly used for application state management (see application state example).
If you just want to avoid transferring some properties layer by layer, component composition is sometimes a better solution than context.
Composition mode-Composition mode
Context API is suitable for managing application state. If you want to avoid problems such as Props Drilling, you can use the combined mode. Composition is a way to create larger components by uniting components together. Composition not only has the flexibility and reusability, but also has the characteristics of single responsibility. Composition is also the core of React. Look at an example of the official website:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
In the example, children
is not used, but the two components Contacts
and Chat
props.left/props.right
. The passed components and the parent component form a more complex component. Although they are combined, each member component has a single responsibility. .
client-server method
This method is embodied in Apollo and ReactQuery , basically adding a layer/client
application and is responsible for requesting/caching data from external sources.
They come with some nice hooks. For the client, this approach looks a lot like using local state, which eliminates the complexity of data storage and retrieval. Here is a simple example of Apollo and ReactQuery:
// Apollo
import { useQuery, gql } from '@apollo/client';
// 在这个例子中,我们使用了 GraphQL
const EXCHANGE_RATES = gql`
query GetExchangeRates {
rates(currency: "USD") {
currency
rate
}
}
`;
function ExchangeRates() {
const { loading, error, data } = useQuery(EXCHANGE_RATES);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.rates.map(({ currency, rate }) => (
<div key={currency}>
<p>
{currency}: {rate}
</p>
</div>
));
}
// ReactQuery
import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
);
}
function Example() {
const { isLoading, error, data, isFetching } = useQuery("repoData", () =>
fetch(
"https://api.github.com/repos/tannerlinsley/react-query"
).then((res) => res.json())
);
if (isLoading) return "Loading...";
if (error) return "An error has occurred: " + error.message;
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{" "}
<strong>✨ {data.stargazers_count}</strong>{" "}
<strong>🍴 {data.forks_count}</strong>
<div>{isFetching ? "Updating..." : ""}</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
From the example, we found that the front end is responsible for specifying how to obtain data, and the rest depends on the Apollo/ReactQuery client, including the loading
and a error
required by the front end. Both states are provided and managed by the back end. This allows the front end to have a state, but actually allows the state to be managed on the back end, which is an interesting combination.
in conclusion
State management is very complicated. State management does not have the best solution, only the most suitable solution.
For third-party state management libraries, it is recommended to read the 56 NPM packages to solve 16 React problems .
refer to:
https://krasimirtsonev.com/blog/article/react-50-shades-of-state
https://www.sytone.me/b-system-state-management#5aa0daa44b404b168bf2cbe0939b711d
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。