WHAT YOU’LL LEARN
  • The structure of a typical React + Redux app
  • How to view state changes in the Redux DevTools Extension

你将学到什么

  • 典型的React+Redux英语程序的结构
  • 如何在Redux开发者扩展中查看状态更改

Introduction

In Part 1: Redux Overview and Concepts, we looked at why Redux is useful, the terms and concepts used to describe different parts of Redux code, and how data flows through a Redux app.
Now, let’s look at a real working example to see how these pieces fit together.
在第一部分:Redux概述和概念,我们看到为什么Redux是有用的,语法和概念用来描述Redux代码的不同部分,
现在,让我们看一个真实案例,来看看这些片段如何组合在一起。

The Counter Example App

The sample project we’ll look at is a small counter application that lets us add or subtract from a number as we click buttons. It may not be very exciting, but it shows all the important pieces of a React+Redux application in action.
同样的项目,我们看是小型计数器应用,当我们点击按钮,数字增加或者减去。它可能不是令人兴奋,但它展示React+Redux应用的所有重要部分。

The project has been created using the official Redux template for Create-React-App. Out of the box, it has already been configured with a standard Redux application structure, using Redux Toolkit to create the Redux store and logic, and React-Redux to connect together the Redux store and the React components.
项目已经使用官方Create-React-app的Redux template被创建。此外,它已经通过标准Redux程序结构被配置,使用Redux Toolkit去创建Redux store和逻辑,React-Redux把Redux store和React 组件联系在一起。

Here’s the live version of the project. You can play around with it by clicking the buttons in the app preview on the right, and browse through the source files on the left.
这里是项目的线上版本,你可以在右侧应用的预览中点击按钮来玩一下,并浏览左侧的源文件。

If you’d like to try create this project on your own computer, you can start a new Create-React-App project using our Redux template:
如果你喜欢在你的电脑创建这个项目,你可以使用我们的Redex template新建一个Create-React-App项目开始。

  1. npx create-react-app redux-essentials-example --template redux

Using the Counter App

The counter app has already been set up to let us watch what happens inside as we use it.
计数器已经被设置,当我们用他的时候,让我们看看在它里面发生了什么。

Open up your browser’s DevTools. Then, choose the “Redux” tab in the DevTools, and click the “State” button in the upper-right toolbar. You should see something that looks like this:
打开浏览器开发者工具,然后在开发者工具选择“Redux”选项卡,然后在右上角工具条点击”State”按钮,你应该看到像这样:
devtools-initial.png
On the right, we can see that our Redux store is starting off with an app state value that looks like this:
在右侧,我们看到Redux store开始时,程序程序store值像这样:

  1. {
  2. counter: {
  3. value: 0
  4. }
  5. }

The DevTools will show us how the store state changes as we use the app.
卡发着工具展示给我们,当我们使用应用程序时,store状态如何改变。

Let’s play with the app first to see what it does. Click the “+” button in the app, then look at the “Diff” tab in the Redux DevTools:
我们首先玩一下程序看看它发生了什么。点击“+”按钮,看看Redux开发者工具的”Diff”选项卡:
devtools-first-action.png
中间暂时省略…
有空了更新…

Application Contents

Now that you know what the app does, let’s look at how it works.
现在你已经知道应用程序是做什么的,让我们看看它如何工作。

Here are the key files that make up this application:
这里是组成这个应用程序的关键文件:

  • /src
    • index.js:the starting point for the app //程序入口文件
    • App.js:the top-level React component //高层组件
    • app
      • store.js:creates the Redux store instance //创建Redux store实例
    • features
      • /counter
        • Counter.js:a React component that shows the UI for the counter feature//React组件展示功能UI
        • counterSlice.js:the Redux logic for the counter feature//计数功能的Redux逻辑

Let’s start by looking at how the Redux store is created.
让我看看Redux store如何被创建。

Creating the Redux Store

Open up app/store.js, which should look like this:
打开app/store.js,看起来应该像这样:

app/store.js

  1. import { configureStore } from '@reduxjs/toolkit'
  2. import counterReducer from '../features/counter/counterSlice'
  3. export default configureStore({
  4. reducer: {
  5. counter: counterReducer
  6. }
  7. })

The Redux store is created using the configureStore function from Redux Toolkit.
configureStore requires that we pass in a reducer argument.
使用Redux Toolkit提供的configureStore函数创建Redux store。
configureStore要求我们传入reducer参数。

Our application might be made up of many different features, and each of those features might have its own reducer function. When we call configureStore, we can pass in all of the different reducers in an object. The key names in the object will define the keys in our final state value.
我们的应用程序可以有许多不同的功能组成,并且每个功能可以有它自己的reducer函数。当我们调用configureStore的时候,我们可以在一个对象里传入所有不同的reducers。对象的键名将定义在我们最终的状态值的键。
We have a file named features/counter/counterSlice.js that exports a reducer function for the counter logic. We can import that counterReducer function here, and include it when we create the store.
我们有个文件 features/counter/counterSlice.js,导出一个计数器逻辑的ruducer函数。我们在这儿引入conuterReducer函数,当我们创建store时将它包括在内。

When we pass in an object like {counter: counterReducer}, that says that we want to have a state.counter section of our Redux state object, and that we want the counterReducer function to be in charge of deciding if and how to update the state.counter section whenever an action is dispatched.
当我们穿入一个对象,像{counter:counterReducer},那意味着我们想要Redux状态对象的state.counter部分,并且我们希望counterReducer函数负责决定,是否以及如何在每当派发action时更新state.counter部分。

Redux allows store setup to be customized with different kinds of plugins (“middleware” and “enhancers”). configureStore automatically adds several middleware to the store setup by default to provide a good developer experience, and also sets up the store so that the Redux DevTools Extension can inspect its contents.
Redux允许store和不同种类的插件(“中间件”和“增强剂”)自定义设置。configureStore默认提供一个好的开发者经验,自动添加服务器中间件给store,并设置store以便Redux开发者扩展工具可以检查它的内容。

Redux Slices

A “slice” is a collection of Redux reducer logic and actions for a single feature in your app, typically defined together in a single file. The name comes from splitting up the root Redux state object into multiple “slices” of state.
一个“slice”是应用程序中单个功能的Redux reducer逻辑和actions的集合,通常在单个文件中定义。名称来自将根Redux状态对象拆分为多个状态“切片”。

For example, in a blogging app, our store setup might look like:
比如,在博客程序中,我们的状态设置如下:

  1. import { configureStore } from '@reduxjs/toolkit'
  2. import usersReducer from '../features/users/usersSlice'
  3. import postsReducer from '../features/posts/postsSlice'
  4. import commentsReducer from '../features/comments/commentsSlice'
  5. export default configureStore({
  6. reducer: {
  7. users: usersReducer,
  8. posts: postsReducer,
  9. comments: commentsReducer
  10. }
  11. })

In that example, state.users, state.posts, and state.comments are each a separate “slice” of the Redux state. Since usersReducer is responsible for updating the state.users slice, we refer to it as a “slice reducer” function.
在例子中,state.user, state.posts,和state.comments是Redux状态的的单独切片。因为userReducer负责更新state.users切片,我们称其为“slice reducer“函数。

Detailed Explanation: Reducers and State Structure
省略…

Creating Slice Reducers and Actions

Since we know that the counterReducer function is coming from features/counter/counterSlice.js, let’s see what’s in that file, piece by piece.
因为我们知道counterReducer函数是来自features/counter/counterSlice.js,让我们看看有什么在文件中,一块一块的。

features/counter/counterSlice.js

  1. import { createSlice } from '@reduxjs/toolkit'
  2. export const counterSlice = createSlice({
  3. name: 'counter',
  4. initialState: {
  5. value: 0
  6. },
  7. reducers: {
  8. increment: state => {
  9. // Redux Toolkit allows us to write "mutating" logic in reducers. It
  10. // doesn't actually mutate the state because it uses the immer library,
  11. // which detects changes to a "draft state" and produces a brand new
  12. // immutable state based off those changes
  13. state.value += 1
  14. },
  15. decrement: state => {
  16. state.value -= 1
  17. },
  18. incrementByAmount: (state, action) => {
  19. state.value += action.payload
  20. }
  21. }
  22. })
  23. export const { increment, decrement, incrementByAmount } = counterSlice.actions
  24. export default counterSlice.reducer

Earlier, we saw that clicking the different buttons in the UI dispatched three different Redux action types:
早前,我们看到在UI中点击不同按钮派发三种Redux action类型:

  • {type: “counter/increment”}
  • {type: “counter/decrement”}
  • {type: “counter/incrementByAmount”}

We know that actions are plain objects with a type field, that the type field is always a string, and that we typically have “action creator” functions that create and return the action objects. So where are those action objects, type strings, and action creators defined?
我们知道actions是有一个类型字段的普通对象,类型字段通常是一个字符串,我们通常有“action creator”函数创建和返回一个action对象。那么这些action对象,类型字符串,和action creators在哪儿定义?

We could write those all by hand, every time. But, that would be tedious. Besides, what’s really important in Redux is the reducer functions, and the logic they have for calculating new state.
我们每次可以手写这些。但是,那太乏味。另外,在Redux中真正重要的是reducer函数,以及他们用于计算新状态的逻辑。

Redux Toolkit has a function called createSlice, which takes care of the work of generating action type strings, action creator functions, and action objects. All you have to do is define a name for this slice, write an object that has some reducer functions in it, and it generates the corresponding action code automatically. The string from the name option is used as the first part of each action type, and the key name of each reducer function is used as the second part. So, the “counter” name + the “increment” reducer function generated an action type of {type: “counter/increment”}. (After all, why write this by hand if the computer can do it for us!)
Redux Toolkit有一个叫createSlice的函数,它负责生成action类型字符串,action creator 函数,和action对象。所有要做的是为这个slice定义一个名字,写一个包含一些reducer函数的对象在它里面,它自动生成相应的action代码。名字选项的字符串用作每个action类型的第一部分,每个reducer函数的关键名称用作第二部分。所以,“counter”命名空间+“increment”reducer函数生成一个的action类型{type:”counter/increment”}。(毕竟,电脑能为我们做这些,为什么要用手写!)

In addition to the name field, createSlice needs us to pass in the initial state value for the reducers, so that there is a state the first time it gets called. In this case, we’re providing an object with a value field that starts off at 0.
除了命名空间外,createSlice需要我们给reducers传入一个初始状态值,以便第一次被调用时有一个状态。在这种清空下,我们提供一个从0开始的值的对象。

We can see here that there are three reducer functions, and that corresponds to the three different action types that were dispatched by clicking the different buttons.
我们能看到有三个reducer函数,并且点击不同按钮被派发的对应三个不同的action类型。

createSlice automatically generates action creators with the same names as the reducer functions we wrote. We can check that by calling one of them and seeing what it returns:
createSlice自动生成和我们编写的同名reducer函数action生成器。我们通过调用他们之一并看返回值来进项检查。

  1. console.log(counterSlice.actions.increment())
  2. // {type: "counter/increment"}

It also generates the slice reducer function that knows how to respond to all these action types:
它也能生成slice reducer函数,该函数知道如何响应所有这些action类型

  1. const newState = counterSlice.reducer(
  2. { value: 10 },
  3. counterSlice.actions.increment()
  4. )
  5. console.log(newState)
  6. // {value: 11}

Rules of Reducers

We said earlier that reducers must always follow some special rules:

  • They should only calculate the new state value based on the state and action arguments
  • They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.
  • They must not do any asynchronous logic or other “side effects”

我们之前说reducers必须始终遵守一些特殊规则:

  • 他们应该仅仅基于state和action的参数计算新的状态值
  • 他们是不允许修改已存在的state。相反,他们必须imuutable(不可变的)更新,通过复制已有state并修改副本的值。
  • 他们必须不做任何异步逻辑或者其他“副作用”

But why are these rules important? There’s a few different reasons:

  • One of the goals of Redux is to make your code predictable. When a function’s output is only calculated from the input arguments, it’s easier to understand how that code works, and to test it.
  • On the other hand, if a function depends on variables outside itself, or behaves randomly, you never know what will happen when you run it.
  • If a function modifies other values, including its arguments, that can change the way the application works unexpectedly. This can be a common source of bugs, such as “I updated my state, but now my UI isn’t updating when it should!”
  • Some of the Redux DevTools capabilities depend on having your reducers follow these rules correctly

但为什么这些规则很重要?有一些不同的原因:

  • Redux的目标之一是使你的代码可预测。当仅从输入参数计算函数的输出时,它是容易理解代码如何工作,并进行测试它。
  • 另一方面,如果一个函数依赖于自身外部的变量,或者行为随机,你从来不知道运行它会发生什么。
  • 如果函数修改了其他值(包括其参数),则可能会更改应用程序的异常工作方式。这可能是常见的错误来源,例如“我更新了状态,但是现在我的UI并没有在适当的时候更新!”
  • Redux DevTools的某些功能取决于让reducers正确遵循这些规则

The rule about “immutable updates” is particularly important, and worth talking about further.
关于“imutable updates”是格外重要的,值得进一步讨论。

Reducers and Immutable Updates

Earlier, we talked about “mutation” (modifying existing object/array values) and “immutability” (treating values as something that cannot be changed).
早些时候,我们讨论了“变异”(修改已经存在的对象/数组的值)和“不可变更”(将值视为无法更改的值)。

WARNING

In Redux, our reducers are never allowed to mutate the original / current state values!

**

警告 在Redux中,绝不允许我们的reducers改变原始/当前的状态值!

**
So if we can’t change the originals, how do we return an updated state?
所以如果我们不能改变原始值,我们如何返回一个更新后的状态?

TIP

Reducers can only make copies of the original values, and then they can mutate the copies.

**

提示 Reducers仅仅能复制原始值,然后才可以对副本变异。

We already saw that we can write immutable updates by hand, by using JavaScript’s array / object spread operators and other functions that return copies of the original values. However, if you’re thinking that “writing immutable updates by hand this way looks hard to remember and do correctly”… yeah, you’re right! :)
我们已经看到我们能手写immutable更新,通过用JavaScript的数组/对象的散布运算符和其他返回原始副本值的函数.然而,如果你想“用这种方式手写不可变的更新,看起来很记住和正确执行”…是的,你说的对:)

Writing immutable update logic by hand is hard, and accidentally mutating state in reducers is the single most common mistake Redux users make.
手写不可变的更新逻辑很困难,而在reducers中意外的改变状态是Redux用户犯的最常见错误。

That’s why Redux Toolkit’s createSlice function lets you write immutable updates an easier way!
这就是为什么使用Redux Toolkit的createSlice函数,让你更容易的方式编写不可变的更新!

createSlice uses a library called Immer inside. Immer uses a special JS tool called a Proxy to wrap the data you provide, and lets you write code that “mutates” that wrapped data. But, Immer tracks all the changes you’ve tried to make, and then uses that list of changes to return a safely immutably updated value, as if you’d written all the immutable update logic by hand.
在createSlice的里面使用一个叫做Immer的库。Immer使用一个叫Proxy包裹你提供的数据的特殊JS工具,让你写“变异”包裹后的数据代码。但是,Immer会跟踪你尝试所有的修改,并使用changes列表返回安全的不可变更新的值,就像你手写所有不可变更新逻辑一样。

So, instead of this:
所以,代替如下:

  1. function handwrittenReducer(state, action) {
  2. return {
  3. ...state,
  4. first: {
  5. ...state.first,
  6. second: {
  7. ...state.first.second,
  8. [action.someId]: {
  9. ...state.first.second[action.someId],
  10. fourth: action.someValue
  11. }
  12. }
  13. }
  14. }
  15. }

You can write code that looks like this:
你可以像这样写代码:

  1. function reducerWithImmer(state, action) {
  2. state.first.second[action.someId].fourth = action.someValue
  3. }

That’s a lot easier to read!
这更容易阅读!

But, here’s something very important to remember:
但是,这儿有非常重要的事情需要记住:

WARNING

You can only write “mutating” logic in Redux Toolkit’s createSlice and createReducer because they use Immer inside! If you write mutating logic in reducers without Immer, it will mutate the state and cause bugs!

**

警告 你只能在Redux Toolkit的createSlice和createReducer中编写“变异”逻辑,因为他们内部使用Immer库!如果你在没有Immer的reducers里写变异逻辑,它改变状态并导致错误!

**
With that in mind, let’s go back and look at the actual reducers from the counter slice.
考虑到这点,让我们返回并看看counter slice 的实际reducers。

features/counter/counterSlice.js

  1. export const counterSlice = createSlice({
  2. name: 'counter',
  3. initialState: {
  4. value: 0
  5. },
  6. reducers: {
  7. increment: state => {
  8. // Redux Toolkit allows us to write "mutating" logic in reducers. It
  9. // doesn't actually mutate the state because it uses the immer library,
  10. // which detects changes to a "draft state" and produces a brand new
  11. // immutable state based off those changes
  12. state.value += 1
  13. },
  14. decrement: state => {
  15. state.value -= 1
  16. },
  17. incrementByAmount: (state, action) => {
  18. state.value += action.payload
  19. }
  20. }
  21. })

We can see that the increment reducer will always add 1 to state.value. Because Immer knows we’ve made changes to the draft state object, we don’t have to actually return anything here. In the same way, the decrement reducer subtracts 1.
我们能看到增量reducer总是增加1到state.value。因为Immer知道我们已经改变草稿状态对象,我们不用在这儿实际返回任何东西。同样的,减量reducer减去1.

In both of those reducers, we don’t actually need to have our code look at the action object. It will be passed in anyway, but since we don’t need it, we can skip declaring action as a parameter for the reducers.
这两种reducers,我们实际不需要我们的代码关注action对象。它无论如何都会被传递,但因为我们不需要他,我们可以跳过将action声明作为reducers参数

On the other hand, the incrementByAmount reducer does need to know something: how much it should be adding to the counter value. So, we declare the reducer as having both state and action arguments. In this case, we know that the amount we typed into the textbox is being put into the action.payload field, so we can add that to state.value.
另一方面,在incrementByAmount reducer 需要知道一些事情:他应该增加到计数器的值是多少。因此,我们声明reducer作为两种状态和action参数。在这种情况下,我们知道输入库的值是被放进action.payload作用域中的,我们能增加这到state.value.

WANT TO KNOW MORE?

For more information on immutability and writing immutable updates, see the “Immutable Update Patterns” docs page and The Complete Guide to Immutability in React and Redux.

想知道更多? 更多关于不变性和编写不可变的更新的更多信息,看the “Immutable Update Patterns” docs page and The Complete Guide to Immutability in React and Redux.

Writing Async Logic with Thunks

So far, all the logic in our application has been synchronous. Actions are dispatched, the store runs the reducers and calculates the new state, and the dispatch function finishes. But, the JavaScript language has many ways to write code that is asynchronous, and our apps normally have async logic for things like fetching data from an API. We need a place to put that async logic in our Redux apps.
迄今,我们应用程序的所有逻辑是同步的。Actions被派发,store运行reducers并计算新的state,然后派发函数完成。但是,JavaScript语言有许多方式写异步代码,我们应用程序通常具有异步逻辑,可以处理从API获取数据之类的事情。我们需要一个地方在Redux应用程序中放置异步逻辑。

A thunk is a specific kind of Redux function that can contain asynchronous logic. Thunks are written using two functions:

  • An inside thunk function, which gets dispatch and getState as arguments
  • The outside creator function, which creates and returns the thunk function

thunk是一种特定类型的Redux函数,可以包含异步逻辑。Thunks用两种函数编写:

  • 一种在thunk函数内部,获取dispatch并getState作为参数
  • 在creator函数外部,创建并返回thunk函数

The next function that’s exported from counterSlice is an example of a thunk action creator:
从counterSlice导入下一个函数是thunk action creator的案例:

features/counter/counterSlice.js

  1. // The function below is called a thunk and allows us to perform async logic.
  2. // It can be dispatched like a regular action: `dispatch(incrementAsync(10))`.
  3. // This will call the thunk with the `dispatch` function as the first argument.
  4. // Async code can then be executed and other actions can be dispatched
  5. export const incrementAsync = amount => dispatch => {
  6. setTimeout(() => {
  7. dispatch(incrementByAmount(amount))
  8. }, 1000)
  9. }

We can use them the same way we use a typical Redux action creator:
我们可以像使用典型Redux action creator一样使用它们:

  1. store.dispatch(incrementAsync(5))

However, using thunks requires that the redux-thunk middleware (a type of plugin for Redux) be added to the Redux store when it’s created. Fortunately, Redux Toolkit’s configureStore function already sets that up for us automatically, so we can go ahead and use thunks here.
但是,使用thunks需要在创建的Redux store时,将redux-thunk中间件(Redux的一种插件)被增加到Redux store 中。幸好,Redux Toolkit的configureStore函数已经为我们自动设置,所以我们可以在这里使用Thunks。

When you need to make AJAX calls to fetch data from the server, you can put that call in a thunk. Here’s an example that’s written a bit longer, so you can see how it’s defined:
当你需要调用AJAX从服务器获取数据,你可以把这些调用放在thunk中。这是一个更长的例子,所以你可以看它如何被定义:
features/counter/counterSlice.js

  1. // the outside "thunk creator" function
  2. const fetchUserById = userId => {
  3. // the inside "thunk function"
  4. return async (dispatch, getState) => {
  5. try {
  6. // make an async call in the thunk
  7. const user = await userAPI.fetchById(userId)
  8. // dispatch an action when we get the response back
  9. dispatch(userLoaded(user))
  10. } catch (err) {
  11. // If something went wrong, handle it here
  12. }
  13. }
  14. }

未完…

The React Counter Component

Earlier, we saw what a standalone React component looks like. Our React+Redux app has a similar component, but it does a few things differently.
之前,我们看到一个独立的React组件外观,我们的React+Redux应用程序有一个类似组件,但它做一些不同的事情。

We’ll start by looking at the Counter.js component file:
我们从看Counter.js组件文件开始:

features/counter/Counter.js

  1. import React, { useState } from 'react'
  2. import { useSelector, useDispatch } from 'react-redux'
  3. import {
  4. decrement,
  5. increment,
  6. incrementByAmount,
  7. incrementAsync,
  8. selectCount
  9. } from './counterSlice'
  10. import styles from './Counter.module.css'
  11. export function Counter() {
  12. const count = useSelector(selectCount)
  13. const dispatch = useDispatch()
  14. const [incrementAmount, setIncrementAmount] = useState('2')
  15. return (
  16. <div>
  17. <div className={styles.row}>
  18. <button
  19. className={styles.button}
  20. aria-label="Increment value"
  21. onClick={() => dispatch(increment())}
  22. >
  23. +
  24. </button>
  25. <span className={styles.value}>{count}</span>
  26. <button
  27. className={styles.button}
  28. aria-label="Decrement value"
  29. onClick={() => dispatch(decrement())}
  30. >
  31. -
  32. </button>
  33. </div>
  34. {/* omit additional rendering output here */}
  35. </div>
  36. )
  37. }

Like with the earlier plain React example, we have a function component called Counter, that stores some data in a useState hook.
像之前普通React 案例,我们有个叫Counter的函数组件,在useState hook里面储存一些数据。

However, in our component, it doesn’t look like we’re storing the actual current counter value as state. There is a variable called count, but it’s not coming from a useState hook.
但是,在我们组件中,它不像我们存储实际当前计数器值作为状态。声明count,但它不是来自useState hook。

While React includes several built-in hooks like useState and useEffect, other libraries can create their own custom hooks that use React’s hooks to build custom logic.
React包含一些内置钩子像useState和useEffect,其他库可以创建他们自定义hooks,这些自定义hooks使用React的钩子创建自定义逻辑。

The React-Redux library has a set of custom hooks that allow your React component to interact with a Redux store.
React-Redux库有设置自定义钩子,这些钩子允许你的React组件和Redux store互动。

———————————————————————-Reading Data with useSelector———————————————————-

Reading Data with useSelector

用useSelector读取数据

First, the useSelector hook lets our component extract whatever pieces of data it needs from the Redux store state.
首先,useSelector钩子让我们组件从Redux存储状态提取所需的任何数据。

Earlier, we saw that we can write “selector” functions, which take state as an argument and return some part of the state value.
早前,我们能写”选择器”函数,携带state作为参数并返回一部分状态值。

Our counterSlice.js has this selector function at the bottom:
我们的counterSlice.js在底部有这个选择器函数:

features/counter/counterSlice.js

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state) => state.counter.value)`
export const selectCount = state => state.counter.value

If we had access to a Redux store, we could retrieve the current counter value as:
如果我们访问Redux状态,我们像这样检索当前计数器值:

const count = selectCount(store.getState())
console.log(count)
// 0

Our components can’t talk to the Redux store directly, because we’re not allowed to import it into component files. But, useSelector takes care of talking to the Redux store behind the scenes for us. If we pass in a selector function, it calls someSelector(store.getState()) for us, and returns the result.
我们的组件不能直接使用Redux store,因为我们不允许在组件文件中引入它。但是,useSelector会为我们与幕后的Redux store进行交谈。如果我们传入一个选择器函数,他为我们调用someSelector(store.getState()),然后返回结果。

So, we can get the current store counter value by doing:
所以,我们通过以下操作能获取当前store的计数器值:

const count = useSelector(selectCount)

We don’t have to only use selectors that have already been exported, either. For example, we could write a selector function as an inline argument to useSelector:
我们不必仅使用已经导出的选择器。例如,我们可以写一个选择器函数编写为useSelector的内联参数:

const countPlusTwo = useSelector(state => state.counter.value + 2)

Any time an action has been dispatched and the Redux store has been updated, useSelector will re-run our selector function. If the selector returns a different value than last time, useSelector will make sure our component re-renders with the new value.
每当发送action并更新Redux store时,useSelector将重新运行我们的selector函数。如果selector换回一个与上次不同的值,useSelector将使用新值重新渲染组件。

Dispatching Actions with useDispatch

使用useDispatch发送Actions

Similarly, we know that if we had access to a Redux store, we could dispatch actions using action creators, like store.dispatch(increment()). Since we don’t have access to the store itself, we need some way to have access to just the dispatch method.
相似的,如果我们访问Redux store,我们能够使用action creators 发送 actions,比如 store.dispatch(increment())。由于我们无权访问store本身,我们需要只能通过dispatch访问。

The useDispatchhook does that for us, and gives us the actual dispatchmethod from the Redux store:
useDispatch钩子帮我们做,并从Redux store 给我实际的dispatch方法:

const dispatch = useDispatch()

From there, we can dispatch actions when the user does something like clicking on a button:
从那儿,当用做一些事情比如户点击按钮,我们能dispatch actions :

features/counter/Counter.js

<button
  className={styles.button}
  aria-label="Increment value"
  onClick={() => dispatch(increment())}
>
  +
</button>

Component State and Forms

By now you might be wondering, “Do I always have to put all my app’s state into the Redux store?”
现在你可能想知道,“我总是将所有的应用状态放进Redux store?”

The answer is NO. Global state that is needed across the app should go in the Redux store. State that’s only needed in one place should be kept in component state.
答案是NO,应用状态所需的全局状态应该放进Redux store. State仅仅被需要在一个地方,则应该保存在组件state中 。

In this example, we have an input textbox where the user can type in the next number to be added to the counter:
这个例子中,在用户输入下一个被增加的数字的地方,有一个input框:

features/counter/Counter.js

const [incrementAmount, setIncrementAmount] = useState('2')

// later
return (
  <div className={styles.row}>
    <input
      className={styles.textbox}
      aria-label="Set increment amount"
      value={incrementAmount}
      onChange={e => setIncrementAmount(e.target.value)}
    />
    <button
      className={styles.button}
      onClick={() => dispatch(incrementByAmount(Number(incrementAmount) || 0))}
    >
      Add Amount
    </button>
    <button
      className={styles.asyncButton}
      onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
    >
      Add Async
    </button>
  </div>
)

We could keep the current number string in the Redux store, by dispatching an action in the input’s onChange handler and keeping it in our reducer. But, that doesn’t give us any benefit. The only place that text string is used is here, in the component. (Sure, there’s only one other component in this example: . But even if we had a larger application with many components, only cares about this input value.)
我们应该保持当前数字在Redux store,通过用输入框的onChange handler派发action并保存到我们的reducer。但是,这并没有给我带来任何好处。文本字符串被用在这儿,在Counter组件中。(的确,案例中仅仅有一个组件:。但即使我们有更大的拥有更多组件的应用程序,仅仅关心这个input的值。)

So, it makes sense to keep that value in a useStatehook here in the component.
所以,保存值到在组件中得useState hook中是有意义的。

Similarly, if we had a boolean flag called isDropdownOpen, no other components in the app would care about that - it should really stay local to this component.
类似的,如果我们有一个叫做isDropDownOpen布尔标志,应用程序中没有其他组件关心-它应该待在这个组件本地。

In a React + Redux app, your global state should go in the Redux store, and your local state should stay in React components.
在React+Redux应用程序中,你的全局state应该放到Redux store,你的本地state应该待在React组件中。

If you’re not sure where to put something, here are some common rules of thumb for determining what kind of data should be put into Redux:

  • Do other parts of the application care about this data?
  • Do you need to be able to create further derived data based on this original data?
  • Is the same data being used to drive multiple components?
  • Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
  • Do you want to cache the data (ie, use what’s in state if it’s already there instead of re-requesting it)?
  • Do you want to keep this data consistent while hot-reloading UI components (which may lose their internal state when swapped)?

如果你不确定方到哪儿,这是一些经验法则决定什么数据应该放到Redux

  • 程序其他部分是否关心这个数据?
  • 你需要基于这个原始数据创建进一步的派生数据?
  • 是否使用相同的数据驱动多个组件?
  • 能够将状态恢复到给定的时间点(例如,时间旅行调试)对您来说是否有价值?
  • 您是否要缓存数据(即,使用已经存在的状态而不是重新请求)?
  • 您是否想在热重载UI组件时保持这些数据的一致性(交换时可能会丢失其内部状态)?

This is also a good example of how to think about forms in Redux in general. Most form state probably shouldn’t be kept in Redux. Instead, keep the data in your form components as you’re editing it, and then dispatch Redux actions to update the store when the user is done.

未完待续…