React 状态管理的演变:从本地到异步

WBOY
发布: 2024-08-21 06:49:02
原创
989 人浏览过

目录

  • 介绍
  • 当地州
    • 类组件
    • 功能组件
    • 使用Reducer Hook
  • 全局状态
    • 什么是全局状态?
    • 如何使用?
    • 主要途径
    • 简单的方法
    • 错误的方式
  • 异步状态
  • 结论

介绍

嗨!

本文概述了数千年前,当类组件统治世界时,状态是如何在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) return 
Loading...
; if (error) return
Error: {error}
; return (

Commit List

    {commits.map(commit => (
  • {commit.commit.message}
  • ))}
); } } class TotalCommitsCount extends Component { render() { return
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) return 
Loading...
; if (error) return
Error: {error}
; return (

Commit List

    {commits.map(commit => (
  • {commit.commit.message}
  • ))}
); }; const TotalCommitsCount = ({ count }) => { return
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 状态管理工具是......

The Evolution of React State Management: From Local to Async

Redux

The Evolution of React State Management: From Local to Async

如果你想使用 React,你应该学习Redux
如果您目前使用React,您可能已经知道了。

好的,这就是我们通常使用Redux获取数据的方式。

免责声明
“啥?这有道理吗?Redux 是存储数据的,不是取数据的,你F怎么用 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 方面,我们使用useDispatchuseSelector:
与操作集成

// 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) return 
Loading...
; if (isError) return
Error while trying to fetch commits.
; return (
    {commits.map(commit => (
  • {commit.commit.message}
  • ))}
); };
登录后复制

如果 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); return 
Total commits: {commitCount}
; }
登录后复制

Disclaimer
In theory, this piece of code would make more sense living inside Commits.tsx, but let's assume we want to display this component in multiple places of the app and it makes sense to put the commits list on the Global State and to have this TotalCommitsCount component.

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.

The simple 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
The Evolution of React State Management: From Local to Async

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) return 
Loading...
; if (isError) return
Error occurred
; return (
    {commits.map(commit => (
  • {commit.commit.message}
  • ))}
); }
登录后复制

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.
The Evolution of React State Management: From Local to Async

The wrong way

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:
The Evolution of React State Management: From Local to Async

What happens if I need to fetch data from 20 endpoints inside 20 components?

  • 20x isLoading + 20x isError + 20x actions to mutate this properties.

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.

Async State

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.

The Evolution of React State Management: From Local to Async

在本文中,我将使用 React-Query。

我建议您访问每个工具的文档,以更好地了解它们解决的问题。

代码如下:

不再需要执行任何操作,不再需要调度,不再需要全局状态

来获取数据。

这是您必须在 App.tsx 文件中执行的操作,才能正确配置 React-Query:

你看,异步状态

很特别。

这就像薛定谔的猫 - 在观察(或运行它)之前你不知道它的状态。

但是等等,如果两个组件都在调用 useCommits 并且 useCommits 正在调用 API 端点,这是否意味着将会有两个相同的请求来加载相同的数据?

简短回答:不!

长答案:React Query 非常棒。它会自动为您处理这种情况,它带有预配置的缓存,足够智能,可以知道何时重新获取

您的数据或简单地使用缓存。

它的可配置性也非常好,因此您可以进行调整以满足 100% 的应用程序需求。

现在我们的组件始终为 isLoading 或 isError 做好准备,并且我们可以减少全局状态的污染,并且拥有一些非常简洁的开箱即用功能。

结论

现在您知道本地全局异步状态

之间的区别。


本地->仅组件。
全球-> Single-Json-NoSQL-DB-For-The-FE。

异步->外部数据,就像薛定谔的猫一样,存在于 FE 应用程序之外,需要加载并且可能返回错误。

希望您喜欢这篇文章,如果您有不同意见或任何建设性反馈,请告诉我,干杯!

以上是React 状态管理的演变:从本地到异步的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!