如何简化React应用程序中的状态-轻松实现Redux

翟兴邦
2023-12-01

by Arnel Enero

通过Arnel Enero

如何简化React应用程序中的状态-轻松实现Redux (How to simplify state in your React app — Redux with a twist)

良好的旧Redux的新的,更容易的语法和语义 (New, much easier syntax and semantics for good old Redux)

The words “simple” and “Redux” rarely appear together in the same sentence. And yet, much of the React community has come to embrace Redux as one of the best solutions for implementing application state.

“简单”和“ Redux”这两个词很少在同一句子中同时出现。 然而,许多React社区已经开始将Redux视为实现应用程序状态的最佳解决方案之一。

Now there is a way to use Redux even if you don’t write a single line of Redux boilerplate code. You don’t even need to know or learn Redux. As long as you are convinced that Redux is the top choice for your app’s state requirements, you will want to read this.

现在,即使您没有编写一行Redux样板代码,也可以使用Redux。 您甚至不需要了解或学习Redux。 只要您确信Redux是满足应用程序状态要求的最佳选择,您就需要阅读此内容。

In this article we will cover these topics:

在本文中,我们将介绍以下主题:

  • Managing simple app state changes

    管理简单的应用程序状态更改
  • Working with async operations (e.g. data fetches)

    使用异步操作(例如,数据提取)
  • Code splitting and lazy-loaded app state

    代码拆分和延迟加载的应用程序状态

React堆库 (The Reactor Library)

I originally wrote the Reactor Library to minimize the boilerplate needed in my personal projects that use React. One of its features is the super simple app state management that I will share with you here.

我最初编写Reactor库是为了最大程度地减少使用React的个人项目所需的样板。 它的功能之一是超级简单的应用程序状态管理,我将在这里与您分享。

I have since decided to make the library available to everyone who may be looking to simplify their React/Redux code. Feel free to use it; it’s yours as much as mine.

从那以后,我决定让所有希望简化其React / Redux代码的人都可以使用该库。 随意使用它; 和我一样多。

To install:

安装:

npm install @reactorlib/core

三件事 (The 3 Key Things)

To write our application state management using Reactor Library, there are 3 key things we need to know about:

要使用Reactor Library编写我们的应用程序状态管理,我们需要了解3个关键事项:

  • Store: This is the single place where the entire state of our application is kept.

    商店:这是保存应用程序整个状态的唯一位置。

  • Entities: These are pieces of the app state, each representing a specific area of concern or functionality.

    实体:这些是应用状态的一部分,每个代表特定的关注领域或功能。

  • Actions: These are functions that our components can invoke to trigger some change in the app state. These also reside in the store.

    动作:这些是我们的组件可以调用以触发应用程序状态中某些更改的函数。 这些也驻留在商店中。

步骤1:建立实体 (Step 1: Creating Entities)

When we define an entity, we think about how the entity would react to certain actions. We refer to this as its reactions. Each reaction comprises state changes that occur within the entity (remember, each entity is just a portion of our app state).

当我们定义一个实体时,我们会考虑该实体如何对某些动作做出React。 我们称其为React 。 每个React都包含实体内发生的状态变化(请记住,每个实体只是我们应用状态的一部分)。

Reactor Library provides a function called createEntity that we will use to define our entities. It accepts two arguments, the entity’s reactions, as well as its initial state:

Reactor库提供了一个名为createEntity的函数,我们将使用该函数来定义我们的实体。 它接受两个参数,即实体的React及其初始状态:

createEntity(reactions: Object, initialState: any)

Let’s get the easier part out of the way first. The initialState should basically define the data structure of our entity by assigning a default value to it.

让我们先将较简单的部分排除在外。 initialState基本上应该通过为其分配默认值来定义我们实体的数据结构。

The reactions argument is a mapping of action names against corresponding reactions. Note that the mapping is not meant to define the actual action functions.

reactions参数是动作名称与相应React的映射。 请注意,映射并不意味着定义实际的动作功能。

In its simplest form, a reaction looks like this:

最简单的形式是:

action: (state, payload) => newState

where action corresponds to the name of an action, while payload(optional) is any single argument that the entity expects you to pass to the action. All this really means is, when action(payload) is invoked, the entity applies certain logic to change its state from state to newState.

其中action对应于一个动作的名称,而payload (可选)是实体希望您传递给该动作的任何单个参数。 这实际上意味着,当调用action(payload) ,实体将应用某些逻辑以将其状态从state更改为newState

Here is a simple example of entity definition:

这是一个简单的实体定义示例:

const initialState = { value: 0 };
const counter = createEntity(  {    increment: (state, by) => (      { ...state, value: state.value + by }    ),    reset: state => ({ ...state, value: 0 })  },  initialState);

IMPORTANT: In defining an entity’s reactions, keep in mind that the React golden rule of not mutating the component state also applies to the application state. So if your entity’s state is of object or array type, always make sure to return a fresh object or array.

重要信息:在定义实体的React时,请记住,不改变组件状态的React黄金法则也适用于应用程序状态。 因此,如果您实体的状态是对象或数组类型,请务必确保返回一个新的对象或数组。

Easy peasy so far, right? Let’s go on…

到目前为止很容易,对吗? 我们继续…

第2步:设置商店 (Step 2: Setting Up the Store)

I said ‘the store’ because there can only be one store throughout our entire application. To make this store available to all our components, we would need to inject this into a top-level component, typically <App>.

我说:“ 商店 ”,因为只能有我们整个申请店。 为了使我们的所有组件都可以使用此存储,我们需要将其注入顶级组件(通常为<A pp>)。

Reactor Library includes the withStore HOC that creates the store, puts entities into it, and designates its target component as the provider/owner of the store.

Reactor库包括withStore HOC,它创建商店,将实体放入商店,并将其目标组件指定为商店的提供者/所有者。

withStore(entities: Object) (Component)

Here the entities argument is a mapping of entity names against the actual entity objects created using createEntity(). This mapping is important because we access entities from the store using the names assigned here.

这里, entities参数是实体名称与使用createEntity()创建的实际实体对象的映射。 此映射非常重要,因为我们使用此处分配的名称从商店访问实体。

Let’s take the counter entity from our previous example, and create our store then place the entity in it:

让我们从前面的示例中获取counter实体,并创建我们的商店,然后将该实体放入其中:

import counter from './store/counter';
const _App = () => (  <Router>    <Shell />  </Router>);
const App = withStore({ counter })(_App);

As simple as that, really. Our store is now all set.

就这么简单,真的。 现在我们的商店已经准备好了。

步骤3:从商店导入道具 (Step 3: Importing Props from Store)

Now the last remaining step is to make the application state accessible to our components. There are 2 simple rules:

现在剩下的最后一步是使我们的组件可以访问应用程序状态。 有2条简单的规则:

  • Components are able to read the application state by importing entities from the store.

    组件能够通过从商店中导入实体读取应用程序状态。

  • They can also change the app state, by importing actions from the store.

    他们还可以通过从商店中导入操作更改应用程序状态。

We use Reactor Library’s getPropsFromStore HOC to do either or both, and inject them to our component as props.

我们使用Reactor Library的getPropsFromStore HOC来执行一项或两项操作,并将它们作为道具注入到我们的组件中。

getPropsFromStore(  entities?: Array<string>,   actions?: Array<string>) (Component)

Here, entities is a list of entity names, and actions is a list of action names.

在这里, entities是实体名称的列表,而actions是动作名称的列表。

Imported entities are injected as state props. This means that whenever any of these entities change, the component will re-render.

导入的实体作为状态道具被注入 这意味着只要这些实体中的任何一个发生更改,组件都将重新渲染。

Imported actions are injected as function props that we can directly invoke inside our component.

导入的动作作为功能道具注入,可以直接在组件内部调用。

You may be wondering, where do we define these action functions? Well, we don’t. The store creates these for us, based on all the action names we mapped to the reactions when creating our entities with createEntity.

您可能想知道,我们在哪里定义这些动作函数? 好吧,我们没有。 当使用createEntity创建实体时,商店会根据映射到React的所有动作名称为我们创建这些动作。

Continuing our previous examples, we import the counter entity from the store as follows:

继续前面的示例,我们从商店中导入counter实体,如下所示:

const _ClickCount = ({ counter, increment, reset }) => (  <>    You have clicked {counter.value} times.    <button onClick={() => increment(1)}>Click Me</button>    <button onClick={reset}>Reset Counter</button>  </>);
const ClickCount = getPropsFromStore(  ['counter'],   ['increment', 'reset'])(_ClickCount);

That’s it! In 3 easy steps, we have connected our component to the app state.

而已! 在3个简单的步骤中,我们已将组件连接到应用程序状态。

使用异步操作 (Working with Async Actions)

An async action is essentially one that requires some sort of non-blocking, asynchronous operation such as fetching data, timer, computation-intensive task, or anything else that is unable to immediately complete its execution.

异步操作本质上是一种需要某种非阻塞的异步操作,例如,获取数据,计时器,计算密集型任务或任何其他无法立即完成其执行的操作。

With the simple form of reaction, the calculation of new state is done immediately. But when dealing with async actions, the entity needs to perform an async operation, and wait for it to finish before it can calculate the state change. For this we need a different form of reaction, which is aptly called an async reaction.

通过简单的React形式,可以立即完成新状态的计算。 但是,在处理异步操作时,实体需要执行异步操作,并等待其完成才能计算状态变化。 为此,我们需要一种不同形式的React,恰当地称为异步React

定义异步React (Defining Async Reactions)

Reactor Library’s createEntity enables us to easily define async reactions, declaratively, in the following form:

Reactor库的createEntity使我们能够以声明的形式轻松地定义异步React,形式如下:

action: [  (state, payload) => newState,  async (payload, next) => {     const result = await doSomethingAsync();    next(result);   },  (state, result) => newState]

This is an array consisting of the 3 steps of our async reaction:

这是一个由异步React的3个步骤组成的数组:

  1. The startup step where any preparatory state change can be made, e.g. setting a ‘loading’ or ‘wait’ flag.

    可以进行任何准备状态更改的启动步骤 ,例如,设置“正在加载”或“等待”标志。

  2. The async step where the entity performs the async operation. It waits until the async operation completes, before calling the next step.

    实体执行异步操作的异步步骤 。 它会等到异步操作完成后再调用下一步。

  3. The completion step where the final state change is made, normally based on the result of the preceding async step.

    通常根据前一个异步步骤的结果进行最终状态更改的完成步骤。

This diagram illustrates how data flows throughout the 3 steps of the async reaction:

此图说明了异步响应的三个步骤中数据如何流动:

The first step (startup) is actually optional, as there are times when you don’t really need a preparatory state change.

第一步(启动)实际上是可选的 ,因为有时您并不需要真正的准备状态更改。

用法示例 (Example Usage)

Here is an example of a complete entity with both simple and async reactions. You can always go back to the illustration above if the flow of data and state changes still seem somewhat unclear.

这是具有简单和异步React的完整实体的示例。 如果数据流和状态更改似乎仍然不清楚,您可以随时返回上图。

const initialState = { auth: null, waiting: false };
const session = createEntity(  {    login: [      state => ({ ...state, waiting: true }),      async ({ username, password }, next) => {        const response = await login(username, password);        next(response);      },      (state, { auth }) => ({ ...state, auth, waiting: false }),    ],    logout: state => ({ ...state, auth: null }),  },  initialState);

Once you get used to this 3-step format, you will be able to create entities quickly because you would only need to focus on the state-change logic and data flow, and not worry about any complex boilerplate code to write.

一旦习惯了这种三步格式,就可以快速创建实体,因为您只需要关注状态更改逻辑和数据流,而不必担心要编写任何复杂的样板代码。

That’s it! Isn’t that way too easy?

而已! 那不是很容易吗?

延迟加载应用程序状态 (Lazy Loading the App State)

If you do code splitting, you will want to code-split your application state as well. A lazy-loaded module can have its own feature store containing feature-specific entities.

如果执行代码拆分,则还需要对应用程序状态进行代码拆分。 延迟加载的模块可以具有自己的功能存储,其中包含特定于功能的实体。

As there can only be a single store in the app, Reactor Library provides a simple way to dynamically merge lazy-loaded feature stores into the main store. This is using the withFeatureStore HOC, which has the following signature:

由于应用程序中只能有一个商店,因此Reactor库提供了一种将延迟加载的功能商店动态合并到主商店中的简单方法。 这是使用withFeatureStore HOC,它具有以下签名:

withFeatureStore(entities: Object) (Component)

As you might notice, this has exactly the same format as the withStore HOC that we discussed earlier. It specifies entities that are lazy-loaded together with your feature modules, to let Reactor Library know that these entities are to be dynamically merged into the store once the feature modules are loaded.

您可能会注意到,这与我们前面讨论的withStore HOC格式完全相同。 它指定了与您的功能模块一起延迟加载的entities ,以使Reactor库知道一旦加载了功能模块,这些实体将被动态合并到商店中。

用法示例 (Example Usage)

Let’s take, for example, a lazy-loaded timer feature that has a TimerPage component as its entry point, and a timer entity to manage its state.

让我们以一个延迟加载的计时器功能为例,该功能具有一个TimerPage组件作为其入口点,以及一个timer实体来管理其状态。

import timer from './store/timer';
const _TimerPage = () => (  <Countdown />);
const TimerPage = withFeatureStore({ timer })(_TimerPage);

That’s it! Again, quick and easy.

而已! 再次,快速简便。

更多信息 (Further Information)

To learn more about the Reactor Library that we used in this article, you can find its official documentation at https://github.com/arnelenero/reactorlib.

要了解有关本文中使用的Reactor库的更多信息,可以在https://github.com/arnelenero/reactorlib上找到其官方文档。

Thanks for reading.

谢谢阅读。

翻译自: https://www.freecodecamp.org/news/how-to-simplify-state-in-your-react-app-redux-with-a-twist-41b0e5b12dcb/

 类似资料: