嗨!
本文概述了数千年前,当类组件统治世界时,状态是如何在React应用程序中管理的,直到最近,功能组件还只是一个大胆的想法,当状态的新范例出现时:异步状态。
好吧,所有使用过 React 的人都知道什么是本地状态。
每次更新状态时,组件都会重新渲染。
您可能曾经使用过这座古老的建筑:
class CommitList extends React.Component { constructor(props) { super(props); this.state = { isLoading: false, commits: [], error: null }; } componentDidMount() { this.fetchCommits(); } fetchCommits = async () => { this.setState({ isLoading: true }); try { const response = await fetch('https://api.github.com/repos/facebook/react/commits'); const data = await response.json(); this.setState({ commits: data, isLoading: false }); } catch (error) { this.setState({ error: error.message, isLoading: false }); } }; render() { const { isLoading, commits, error } = this.state; if (isLoading) returnLoading...; if (error) returnError: {error}; return (); } } class TotalCommitsCount extends Component { render() { returnCommit List
{commits.map(commit => (
- {commit.commit.message}
))} Total commits: {this.props.count}; } } }
也许是现代功能性之一:
const CommitList = () => { const [isLoading, setIsLoading] = useState(false); const [commits, setCommits] = useState([]); const [error, setError] = useState(null); // To update state you can use setIsLoading, setCommits or setUsername. // As each function will overwrite only the state bound to it. // NOTE: It will still cause a full-component re-render useEffect(() => { const fetchCommits = async () => { setIsLoading(true); try { const response = await fetch('https://api.github.com/repos/facebook/react/commits'); const data = await response.json(); setCommits(data); setIsLoading(false); } catch (error) { setError(error.message); setIsLoading(false); } }; fetchCommits(); }, []); if (isLoading) returnLoading...; if (error) returnError: {error}; return (); }; const TotalCommitsCount = ({ count }) => { returnCommit List
{commits.map(commit => (
- {commit.commit.message}
))} Total commits: {count}; };
或者甚至是一个“更容易被接受”的? (不过肯定更罕见)
const initialState = { isLoading: false, commits: [], userName: '' }; const reducer = (state, action) => { switch (action.type) { case 'SET_LOADING': return { ...state, isLoading: action.payload }; case 'SET_COMMITS': return { ...state, commits: action.payload }; case 'SET_USERNAME': return { ...state, userName: action.payload }; default: return state; } }; const CommitList = () => { const [state, dispatch] = useReducer(reducer, initialState); const { isLoading, commits, userName } = state; // To update state, use dispatch. For example: // dispatch({ type: 'SET_LOADING', payload: true }); // dispatch({ type: 'SET_COMMITS', payload: [...] }); // dispatch({ type: 'SET_USERNAME', payload: 'newUsername' }); };
这会让你想知道......
为什么黑客我要为单个组件编写这个复杂的减速器?
嗯,React 从一个名为Redux的非常重要的工具继承了这个丑陋名为 useReducer 的钩子。
如果您曾经在 React 中处理过全局状态管理,您一定听说过Redux。
这将我们带入下一个主题:全局状态管理。
全局状态管理是学习 React 时最先复杂的科目之一。
它可以是多种东西,以多种方式构建,具有不同的库。
我喜欢将其定义为:
单个 JSON 对象,由应用程序的任何组件访问和维护。
const globalState = { isUnique: true, isAccessible: true, isModifiable: true, isFEOnly: true }
我喜欢将其视为:
前端No-SQL数据库。
没错,就是数据库。这是您存储应用程序数据的地方,您的组件可以读取/写入/更新/删除。
我知道,默认情况下,每当用户重新加载页面时都会重新创建状态,但这可能不是您想要的,如果您在某个地方(例如 localStorage)保存数据,您可能想要了解迁移以避免每次新部署都会破坏应用程序。
我喜欢用它作为:
一个多维门户,组件可以发送他们的感受并选择他们的属性。一切、无处不在、同时发生。
Redux
这是行业标准。
我使用 React、TypeScript 和Redux已经有 7 年了。我专业参与过的每个项目都使用Redux。
我见过的绝大多数使用 React 的人都使用Redux。
Trampar de Casa 的 React 空缺职位中提到最多的工具是Redux。
最流行的 React 状态管理工具是......
Redux
如果你想使用 React,你应该学习Redux。
如果您目前使用React,您可能已经知道了。
好的,这就是我们通常使用Redux获取数据的方式。
如果你想过这个,我必须告诉你:
我实际上并没有使用 Redux 获取数据。
Redux 将成为应用程序的柜子,它将存储与获取直接相关的 ~shoes~ 状态,这就是为什么我使用了这个错误的短语:“使用 Redux 获取数据”。
// actions export const SET_LOADING = 'SET_LOADING'; export const setLoading = (isLoading) => ({ type: SET_LOADING, payload: isLoading, }); export const SET_ERROR = 'SET_ERROR'; export const setError = (isError) => ({ type: SET_ERROR, payload: isError, }); export const SET_COMMITS = 'SET_COMMITS'; export const setCommits = (commits) => ({ type: SET_COMMITS, payload: commits, }); // To be able to use ASYNC action, it's required to use redux-thunk as a middleware export const fetchCommits = () => async (dispatch) => { dispatch(setLoading(true)); try { const response = await fetch('https://api.github.com/repos/facebook/react/commits'); const data = await response.json(); dispatch(setCommits(data)); dispatch(setError(false)); } catch (error) { dispatch(setError(true)); } finally { dispatch(setLoading(false)); } }; // the state shared between 2-to-many components const initialState = { isLoading: false, isError: false, commits: [], }; // reducer export const rootReducer = (state = initialState, action) => { // This could also be actions[action.type]. switch (action.type) { case SET_LOADING: return { ...state, isLoading: action.payload }; case SET_ERROR: return { ...state, isError: action.payload }; case SET_COMMITS: return { ...state, commits: action.payload }; default: return state; } };
现在在 UI 方面,我们使用useDispatch和useSelector:
与操作集成
// Commits.tsx import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchCommits } from './action'; export const Commits = () => { const dispatch = useDispatch(); const { isLoading, isError, commits } = useSelector(state => state); useEffect(() => { dispatch(fetchCommits()); }, [dispatch]); if (isLoading) returnLoading...; if (isError) returnError while trying to fetch commits.; return (
如果 Commits.tsx 是唯一需要访问提交列表的组件,则不应将此数据存储在全局状态上。它可以使用本地状态来代替。
But let's suppose you have other components that need to interact with this list, one of them may be as simple as this one:
// TotalCommitsCount.tsx import React from 'react'; import { useSelector } from 'react-redux'; export const TotalCommitsCount = () => { const commitCount = useSelector(state => state.commits.length); returnTotal commits: {commitCount}; }
With the index.js component being something like this:
import React from 'react'; import ReactDOM from 'react-dom'; import thunk from 'redux-thunk'; import { createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import { Commits } from "./Commits" import { TotalCommitsCount } from "./TotalCommitsCount" export const App = () => () const store = createStore(rootReducer, applyMiddleware(thunk)); ReactDOM.render( , document.getElementById('root') );
This works, but man, that looks overly complicated for something as simple as fetching data right?
Redux feels a little too bloated to me.
You're forced to create actions and reducers, often also need to create a string name for the action to be used inside the reducer, and depending on the folder structure of the project, each layer could be in a different file.
Which is not productive.
But wait, there is a simpler way.
Zustand
At the time I'm writing this article, Zustand has 3,495,826 million weekly downloads, more than 45,000 stars on GitHub, and 2, that's right,TWOopen Pull Requests.
ONE OF THEM IS ABOUT UPDATING IT'S DOC
If this is not a piece of Software Programming art, I don't know what it is.
Here's how to replicate the previous code using Zustand.
// store.js import create from 'zustand'; const useStore = create((set) => ({ isLoading: false, isError: false, commits: [], fetchCommits: async () => { set({ isLoading: true }); try { const response = await fetch('https://api.github.com/repos/facebook/react/commits'); const data = await response.json(); set({ commits: data, isError: false }); } catch (error) { set({ isError: true }); } finally { set({ isLoading: false }); } }, }));
This was our Store, now the UI.
// Commits.tsx import React, { useEffect } from 'react'; import useStore from './store'; export const Commits = () => { const { isLoading, isError, commits, fetchCommits } = useStore(); useEffect(() => { fetchCommits(); }, [fetchCommits]); if (isLoading) returnLoading...; if (isError) returnError occurred; return (
And last but not least.
// TotalCommitsCount.tsx import React from 'react'; import useStore from './store'; const TotalCommitsCount = () => { const totalCommits = useStore(state => state.commits.length); return (); };Total Commits:
{totalCommits}
There are no actions and reducers, there is a Store.
And it's advisable to have slices of Store, so everything is near to thefeaturerelated to the data.
It works perfect with a folder-by-feature folder structure.
I need to confess something, both of my previous examples are wrong.
And let me do a quick disclaimer: They're notwrong, they're outdated, and therefore,wrong.
This wasn't always wrong though. That's how we used to develop data fetching in React applications a while ago, and you may still find code similar to this one out there in the world.
But there is another way.
An easier one, and more aligned with an essential feature for web development:Caching. But I'll get back to this subject later.
Currently, to fetch data in a single component, the following flow is required:
What happens if I need to fetch data from 20 endpoints inside 20 components?
What will they look like?
With 20 endpoints, this will become averyrepetitive process and will cause a good amount of duplicated code.
What if you need to implement a caching feature to prevent recalling the same endpoint in a short period? (or any other condition)
Well, that will translate intoa lot of workforbasicfeatures (like caching) and well-written components that are prepared for loading/error states.
This is whyAsync Statewas born.
Before talking about Async State I want to mention something. We knowhowto use Local and Global state but at this time I didn't mentionwhatshould be stored andwhy.
TheGlobal Stateexample has a flaw and an important one.
The TotalCommitsCount component will always display the Commits Count, even if it's loading or has an error.
If the request failed, there's no way to know that theTotal Commits Countis 0, so presenting this value is presentinga lie.
In fact, until the request finishes, there is no way to know for sure what's theTotal Commits Countvalue.
This is because theTotal Commits Countis not a value we haveinsidethe application. It'sexternalinformation,asyncstuff, you know.
We shouldn't be telling lies if we don't know the truth.
That's why we must identifyAsync Statein our application and create components prepared for it.
We can do this with React-Query, SWR, Redux Toolkit Query and many others.
在本文中,我将使用 React-Query。
我建议您访问每个工具的文档,以更好地了解它们解决的问题。
代码如下:
不再需要执行任何操作,不再需要调度,不再需要全局状态
来获取数据。 这是您必须在 App.tsx 文件中执行的操作,才能正确配置 React-Query:你看,异步状态
很特别。 这就像薛定谔的猫 - 在观察(或运行它)之前你不知道它的状态。 但是等等,如果两个组件都在调用 useCommits 并且 useCommits 正在调用 API 端点,这是否意味着将会有两个相同的请求来加载相同的数据?简短回答:不!
长答案:React Query 非常棒。它会自动为您处理这种情况,它带有预配置的缓存,足够智能,可以知道何时重新获取
您的数据或简单地使用缓存。 它的可配置性也非常好,因此您可以进行调整以满足 100% 的应用程序需求。 现在我们的组件始终为 isLoading 或 isError 做好准备,并且我们可以减少全局状态的污染,并且拥有一些非常简洁的开箱即用功能。 结论现在您知道本地、全局和异步状态
之间的区别。
本地->仅组件。
全球-> Single-Json-NoSQL-DB-For-The-FE。
以上是React 状态管理的演变:从本地到异步的详细内容。更多信息请关注PHP中文网其他相关文章!