by Matthew Choi
由Matthew Choi
使用React构建Tesla的电池范围计算器(第2部分:Redux版本) (Building Tesla’s Battery Range Calculator with React (Part 2: Redux version))
This tutorial is the second part of building Tesla’s battery range calculator with React.
本教程是使用React构建特斯拉电池范围计算器的第二部分。
In part 1, after constructing the project through create-react-app, we implemented each component by subdividing the UI. We managed the state and event using local state and props, and completed the entire app.
在第1部分中,通过create-react-app构建项目后,我们通过细分UI来实现每个组件。 我们使用本地状态和道具管理状态和事件,并完成了整个应用程序。
If you haven’t read it yet, be sure to check out Part 1, which focuses on React, here:
如果您还没有阅读它,请确保在这里阅读有关React的第1部分:
Building Tesla’s Battery Range Calculator with React (Part 1)In this series of articles, I will walk you through the process of building Tesla’s battery range calculator with React…medium.freecodecamp.com
使用React构建Tesla的电池范围计算器(第1部分) 在本系列文章中,我将引导您完成使用React构建Tesla的电池范围计算器的过程 。medium.freecodecamp.com
In this installment, we’ll introduce Redux, a state management solution, to see how we can transform our application into an application that manages the state of the app with Redux.
在本期中,我们将介绍状态管理解决方案Redux,以了解如何将应用程序转换为使用Redux管理应用程序状态的应用程序。
This is the final image of our application in part 2:
这是第2部分中应用程序的最终图像:
? Check out the part 2 live demo.
? 请查看第2部分的在线演示。
Before we taking a look into what Redux is, let’s see why we need to use Redux to solve problems.
在研究什么是Redux之前,让我们看看为什么需要使用Redux来解决问题。
1.我们要解决什么问题? (1. What problem do we solve?)
Redux is getting becoming the de facto way to build React apps. But should Redux be used in all React apps? At the very least, not all apps will need an ambitious state management solution from the beginning.
Redux正在成为构建React应用程序的实际方法。 但是Redux是否应该在所有React应用程序中使用? 至少从一开始,并不是所有的应用程序都需要雄心勃勃的状态管理解决方案。
Today’s front-end development trends are component-based. Components can have data state and UI state, and the state that they need to manage becomes more and more complicated as your app grows.
当今的前端开发趋势是基于组件的 。 组件可以具有数据状态和UI状态,并且随着应用程序的增长,它们需要管理的状态变得越来越复杂。
State management solutions have emerged to solve the following problems, and Redux is becoming popular as a standard among other solutions.
为了解决以下问题,出现了状态管理解决方案,并且Redux在其他解决方案中已成为标准。
- components share state 组件共享状态
- state should be accessible from anywhere 状态应该可以从任何地方访问
- components need to mutate the state 组件需要改变状态
- components need to mutate the state of other components 组件需要改变其他组件的状态
Redux is a state management library, which is a tool that allows you to store the state of our app somewhere, mutate the state, and receive the updated state.
Redux是一个状态管理库 ,它是一个工具,可让您在某个地方存储我们的应用程序状态,更改状态并接收更新后的状态。
In other words, with Redux, we have one place where we can refer the state, change the state, and get the updated state.
换句话说,在Redux中,我们有一个地方可以引用状态,更改状态并获取更新的状态。
Redux was written with React in mind, but it is also framework-agnostic and even can be used with Angular or jQuery applications.
Redux是在考虑到React的情况下编写的,但它与框架无关 ,甚至可以与Angular或jQuery应用程序一起使用。
I recommend you read Dan Abramov’s You Might Not Need Redux before choosing Redux.
建议您在选择Redux之前先阅读Dan Abramov的“ 您可能不需要 Redux”。
2. Redux中的数据流 (2. Data flow in Redux)
As you saw in the part 1, in React, the data is passed through the component using props. This is called unidirectional data flow that flows from parent to child.
如您在第1部分中所见,在React中,数据通过props通过组件传递。 这称为从父级流到子级的单向数据流 。
Due to these characteristics, communication between components other than parent-child relationship is not clear.
由于这些特性,除父子关系之外的组件之间的通信尚不清楚。
React does not recommend direct component-to-component communication as shown above. There is a suggested way for this in React, but you have to implement it yourself.
如上所述,React不建议直接进行组件间通信。 在React中有一种建议的方法,但是您必须自己实现。
According to React docs:
根据React docs :
For communication between two components that don’t have a parent-child relationship, you can set up your own global event system. … Flux pattern is one of the possible ways to arrange this.
要在没有父子关系的两个组件之间进行通信,可以设置自己的全局事件系统。 …助焊剂模式是解决此问题的一种可能方法。
This is where Redux comes in handy.
这是Redux派上用场的地方。
Redux provides a solution for managing all application state in a single place called a store
.
Redux提供了一种解决方案,用于在称为store
的单个位置管理所有应用程序状态。
The component then dispatches
the state change to the store instead of passing it directly to the other components
然后,该组件将状态更改dispatches
到存储,而不是将其直接传递给其他组件
The components that need to be aware of state changes can subscribe
to the store.
需要了解状态变化的组件可以subscribe
商店。
Redux is, in a word, a state container that represents and manages the state of an app as a single object from a JavaScript-based application.
总之,Redux是一个状态容器 ,它作为基于JavaScript的应用程序中的单个对象来表示和管理应用程序的状态。
3. Redux核心概念 (3. Redux Core Concept)
Redux itself is very simple. The state of the app we created in the last article can be represented as a generic object like this:
Redux本身非常简单。 我们在上一篇文章中创建的应用程序的状态可以表示为通用对象,如下所示:
This object is the same as the model without setters.
该对象与不带setter的模型相同。
To change this state in Redux, you must dispatch an action
.
要在Redux中更改此状态,您必须调度action
。
Actions are plain objects describing what happened in the app, and serve as the sole way to describe an intention to mutate the data. It’s one of the fundamental design choices of Redux.
动作是描述应用程序中发生的事情的简单对象,并且是描述意图改变数据的唯一方法。 这是Redux的基本设计选择之一。
Here are some examples to be implemented in our app soon.
以下是一些即将在我们的应用中实现的示例。
Forcing all of these state changes into action will give us a clear understanding of what’s going on in your app. When something happens, we can see why it happened.
强制执行所有这些状态更改将使我们对您的应用程序中发生的事情有清晰的了解。 当某些事情发生时,我们可以看到为什么发生。
Now we need a function called reducer
to bind these states and actions together. Reducer is nothing more than a function that takes a state and an action as arguments and returns a new state.
现在我们需要一个称为reducer
的函数来将这些状态和动作绑定在一起。 Reducer只是一个将状态和操作作为参数并返回新状态的函数。
In a word:
一句话:
(state, action) => state
(状态,动作)=>状态
Actions only describe that something happened and don’t specify how the application’s state changes in response. This is the job of reducers.
动作仅描述发生了某些事情,没有指定应用程序的状态如何响应 。 这是减速器的工作。
Here is one example of a reducer to implement in our app:
这是在我们的应用程序中实现的reducer的一个示例:
4. Redux的三项原则 (4. Redux Three Principles)
I’ve mentioned Flux
a few times. Flux is a pattern of state management, not a downloadable tool like Redux. Redux, on the other hand, is a practical implementation of the Flux pattern and has three main principles.
我已经提到过几次Flux
。 Flux是状态管理的一种模式 ,不是Redux这样的可下载工具。 另一方面,Redux是Flux模式的实际实现,它具有三个主要原理。
4.1真理的单一来源 (4.1 Single source of truth)
The state of the entire application is stored in an object tree within a single store.
整个应用程序的状态存储在单个存储中的对象树中。
Since all states exist in one place, this is called a single source of truth
.
由于所有状态都存在于一个地方,因此这被称为single source of truth
。
This one-store
approach of Redux is one of the primary differences between it and Flux’s multiple store approach.
Redux的这种one-store
方法是它与Flux的多商店方法之间的主要区别之一。
What are the advantages of a single state tree? This makes it easier to debug applications or perform internal inspections and to easily implement some features that were previously difficult to implement (for example, undo / redo).
单一状态树的优点是什么? 这使得调试应用程序或执行内部检查以及轻松实现以前难以实现的某些功能(例如,撤消/重做)变得更加容易。
4.2状态为只读 (4.2 State is read-only)
The only way to change the state is to emit an action that describes what happened.
更改状态的唯一方法是发出描述所发生情况的操作。
In other words, the application does not directly change the state, but instead expresses the intention to transform the state by passing the action.
换句话说,应用程序不直接更改状态,而是表达通过传递动作来转换状态的意图。
In fact, if you look at the Redux API, you can see that it consists of just four methods:
实际上,如果您查看Redux API,则可以看到它仅包含四种方法:
store.dispatch(action)store.subscribe(listener)store.getState()replaceReducer(nextReducer)
As you can see, there is no setState() method. Therefore, passing an action is the only channel that can mutate the state of the application.
如您所见,没有setState()方法。 因此, 传递动作是唯一可以改变应用程序状态的通道。
4.3使用纯函数进行更改 (4.3 Changes are made with pure functions)
You write reducers as pure functions to specify the concrete way the state tree is transformed by action.
您将化简器编写为纯函数,以指定通过动作转换状态树的具体方式。
Reducers are pure functions that take a previous state and action and return a new state. Keep in mind that you must return a new state object instead of changing the old state.
约简器是纯函数,它们具有先前的状态和操作并返回新的状态。 请记住,您必须返回一个新的状态对象,而不是更改旧的状态。
“Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.” — Redux Docs
“给出相同的参数,它应该计算下一个状态并返回它。 没什么好奇怪的 没有副作用。 没有API调用。 没有突变。 只是计算而已。” — Redux文档
The pure function has the following characteristics:
纯函数具有以下特征:
- It does not make outside network or database calls. 它不会进行外部网络或数据库调用。
- Its return value depends solely on the values of its parameters. 它的返回值仅取决于其参数的值。
- Its arguments should be considered “immutable”, meaning they should not be changed. 其参数应被认为是“不变的”,这意味着它们不应被更改。
- Calling a pure function with the same set of arguments will always return the same value. 用相同的参数集调用纯函数将始终返回相同的值。
5.将应用程序分为容器和组件 (5. Divide The App Into Containers and Components)
Now, let’s re-build our Tesla calculator app that we made in Part 1 as a Redux version.
现在,让我们重新构建在第1部分中作为Redux版本构建的Tesla计算器应用程序。
First, let’s look at the overall component UI layout of the app that will be implemented in this article.
首先,让我们看一下将在本文中实现的应用程序的整体组件UI布局 。
Placing React and Redux logic inside a single component can be messy, so it is recommended that you create a Presentational
component for presentation purposes only, and a Container
component, an upper wrapper component that handles Redux and dispatches actions.
将React和Redux逻辑放在单个组件中可能会很混乱,因此建议您创建一个Presentational
组件,仅用于演示目的,而Container
组件是一个处理Redux并分派动作的上层包装器组件。
The role of the parent Container component is to provide state values to presentational components, to manage events, and to communicate with Redux on behalf of presentational components.
父容器组件的作用是为表示组件提供状态值,以管理事件,并代表表示组件与Redux通信。
6.列出每个组件的状态和操作 (6. List State and Actions For Each Component)
Refer to the entire component layout to create a list of states and actions for each component:
请参考整个组件布局,以为每个组件创建状态和动作的列表:
TeslaCar Container : state : wheels action : N/A
TeslaStats Container : state : carstats(array) action : N/A TeslaSpeedCounter Container : state : config.speed action : SPEED_UP, SPEED_DOWN
TeslaTempCounter Container : state : config.temperature action : TEMPERATURE_UP, TEMPERATURE_DOWN TeslaClimate Container : state : config.climate action : CHANGE_CLIMATE
TeslaWheel Container : state : config.wheel action : CHANGE_WHEEL
7.设置第1部分项目代码库 (7. Set up part 1 project code base)
If you want to go directly to part 2 without looking at part 1, you need to build the codebase first by cloning the part 1 code.
如果您想不看第1部分而直接进入第2部分,则需要首先通过克隆第1部分代码来构建代码库。
After the npm start, let’s make sure the application works.
在npm start之后 ,让我们确保该应用程序正常运行。
git clone https://github.com/gyver98/part1-react-tesla-battery-range-calculator-tutorial
git clone https://github.com/gyver98/part1-react-tesla-battery-range-calculator-tutorial
npm install
npm安装
npm start
npm开始
8.为每个动作创建动作创建者 (8. Create Action Creators For Each Action)
Now that you’ve created an action list, it’s time to create action creators
.
现在,您已经创建了动作列表,是时候创建action creators
。
An action creator is a function that literally creates an action object. In Redux, action creators simply return an action object and pass the argument value if necessary.
动作创建者是从字面上创建动作对象的函数。 在Redux中,动作创建者只需返回一个动作对象,并在必要时传递参数值。
changeWheel action creator sample :
changeWheel动作创建者示例:
const changeWheel = (value) => { return { type: 'CHANGE_WHEEL', value }}
These action creators are passed to the dispatch function as the result value, and the dispatch is executed.
这些动作创建者作为结果值传递到调度函数,并执行调度。
dispatch(changeWheel(size))
The dispatch function can be accessed directly from the store as store.dispatch(), but in most cases it will be accessed using a helper like react-redux’s connect()
. We’ll look at connect later.
可以从商店以store.dispatch()的形式直接访问调度功能,但是在大多数情况下,将使用诸如react-redux的connect()
类的助手来访问该调度功能。 我们稍后再看连接 。
8.1创建Action.js (8.1 Create Action.js)
Create an index file in the src/actions directory and define action creators as follows:
在src / actions目录中创建一个索引文件,并按如下所示定义动作创建者:
src/actions/index.js
src / actions / index.js
import { counterDefaultVal } from '../constants/counterDefaultVal';
export const speedUp = (value) => { return { type: 'SPEED_UP', value, step: counterDefaultVal.speed.step, maxValue: counterDefaultVal.speed.max }}
export const speedDown = (value) => { return { type: 'SPEED_DOWN', value, step: counterDefaultVal.speed.step, minValue: counterDefaultVal.speed.min }}
export const temperatureUp = (value) => { return { type: 'TEMPERATURE_UP', value, step: counterDefaultVal.temperature.step, maxValue: counterDefaultVal.temperature.max }}
export const temperatureDown = (value) => { return { type: 'TEMPERATURE_DOWN', value, step: counterDefaultVal.temperature.step, minValue: counterDefaultVal.temperature.min }}
export const changeClimate = () => { return { type: 'CHANGE_CLIMATE' }}
export const changeWheel = (value) => { return { type: 'CHANGE_WHEEL', value }}
export const updateStats = () => { return { type: 'UPDATE_STATS' }}
Because we need default values based on the action creator, we define this constant value in constants/counterDefaultVal under src directory and import it.
因为我们需要基于动作创建者的默认值 ,所以我们在src目录下的constants / counterDefaultVal中定义此常量值并将其导入。
src/constants/counterDefaultVal.js
src /常量/counterDefaultVal.js
export const counterDefaultVal = { speed: { title: "Speed", unit: "mph", step: 5, min: 45, max: 70 }, temperature: { title: "Outside Temperature", unit: "°", step: 10, min: -10, max: 40 }}
Check out counterDefaultVal.js
9.为每个动作创建减速器 (9. Create Reducers For Each Action)
Reducers are functions that receive state and action objects from a Redux store and return a new state to be stored back into Redux.
减速器是从Redux存储接收状态和动作对象并返回新状态以存储回Redux的函数。
It’s important not to directly modify the given state here. Reducers must be pure functions and must return a new state.
重要的是不要在此处直接修改给定状态。 减速器必须是纯函数,并且必须返回新状态 。
Reducer functions are called from the Container that will be created when a user action occurs.
从发生用户操作时将创建的容器调用Reducer函数。
When the Reducer returns a state, Redux passes the new state to each component, and React renders each component again.
当Reducer返回状态时, Redux将新状态传递给每个组件,然后React再次渲染每个组件 。
9.1不可变的数据结构 (9.1 Immutable Data Structures)
JavaScript primitive data type(number, string, boolean, undefined, and null) => immutable
JavaScript基本数据类型(数字,字符串,布尔值,未定义和null)=&g t; 不可变的
Object, array and function => mutable
对象,数组和函数=&g t; 可变
Changes to the data structure are known to be buggy. Since our store consists of state objects and arrays, we need to implement a strategy to keep the state immutable.
已知对数据结构的更改存在错误。 由于我们的商店由状态对象和数组组成,因此我们需要实施一种策略来保持状态不变 。
There are three ways to change the state here:
这里有三种更改状态的方法:
ES5
ES5
// Example Onestate.foo = '123';
// Example TwoObject.assign(state, { foo: 123 });
// Example Threevar newState = Object.assign({}, state, { foo: 123 });
In the example above, the first and second mutate the state object. The second example mutates because Object.assign() merges all its arguments into the first argument.
在上面的示例中,第一个和第二个突变状态对象。 第二个示例发生了变化,因为Object.assign()将其所有参数合并到第一个参数中。
The third example doesn’t mutate the state. It merges the contents of state and { foo: 123 } into a new empty object which is the first argument.
第三个例子没有改变状态 。 它将state和{foo:123}的内容合并到一个新的空对象中 ,该对象是第一个参数。
The spread operator introduced in ES6 provides a simpler way to keep the state immutable.
ES6中引入的散布运算符提供了一种更简单的方法来保持状态不变。
ES6 (ES2015)
ES6(ES2015)
const newState = { ...state, foo: 123 };
For more information about the spread operator, see here.
有关点差运算符的更多信息,请参见此处 。
9.2为ChangeClimate创建减速器 (9.2 Create a Reducer for ChangeClimate)
First, we will create ChangeClimate through test-driven development method.
首先,我们将通过测试驱动的开发方法来创建ChangeClimate。
In Part1, our app was generated through create-react-app, so we basically use jest
as test runner.
在第1部分中,我们的应用程序是通过create-react-app生成的 ,因此我们基本上使用jest
作为测试运行器。
The jest looks for a test file using one of the following naming conventions:
开玩笑地使用以下命名约定之一查找测试文件:
Files with .js suffix in __tests__ foldersFiles with .test.js suffixFiles with .spec.js suffix
Create teslaRangeApp.spec.js in src/reducers and create a test case.
在src / reducers中创建teslaRangeApp.spec.js并创建一个测试案例。
describe('test reducer', () => { it('should handle initial stat', () => { expect( appReducer(undefined, {}) ).toEqual(initialState) })})
After create the test, run the npm test
command. You should be able to see the following test failure message. This is because we have not written the appReducer yet.
创建测试后,运行npm test
命令。 您应该能够看到以下测试失败消息。 这是因为我们尚未编写appReducer 。
To make the first test successful, we need to create teslaRangeApp.js in the same reducers directory and write initial state and reducer functions.
为了使第一个测试成功,我们需要在相同的reducers目录中创建teslaRangeApp.js并编写初始状态和reducer函数。
src/reducers/teslaRangeApp.js
src / reducers / teslaRangeApp.js
const initialState = { carstats:[ {miles:246, model:"60"}, {miles:250, model:"60D"}, {miles:297, model:"75"}, {miles:306, model:"75D"}, {miles:336, model:"90D"}, {miles:376, model:"P100D"} ], config: { speed: 55, temperature: 20, climate: true, wheels: 19 }}
function appReducer(state = initialState, action) { switch (action.type) { default: return state }}
export default appReducer;
Next, import teslaRangeApp.js from teslaRangeApp.spec.js and set initialState.
接下来,从teslaRangeApp.spec.js导入teslaRangeApp.js并设置initialState。
src/reducers/teslaRangeApp.spec.js
src / reducers / teslaRangeApp.spec.js
import appReducer from './teslaRangeApp';
const initialState = { carstats:[ {miles:246, model:"60"}, {miles:250, model:"60D"}, {miles:297, model:"75"}, {miles:306, model:"75D"}, {miles:336, model:"90D"}, {miles:376, model:"P100D"} ], config: { speed: 55, temperature: 20, climate: true, wheels: 19 }}
describe('test reducer', () => { it('should handle initial stat', () => { expect( appReducer(undefined, {}) ).toEqual(initialState) })})
Run npm test again and the test will succeed.
再次运行npm test,测试将成功。
In the current test case, the action type is {}, so the initialState is returned.
在当前测试用例中,操作类型为{},因此返回initialState 。
Now let’s test the CHANGE_CLIMATE action.
现在让我们测试CHANGE_CLIMATE动作。
Add the following climateChangeState and CHANGE_CLIMATE test cases to teslaRangeApp.spec.js.
将以下ClimateChangeState和CHANGE_CLIMATE测试用例添加到teslaRangeApp.spec.js。
const climateChangeState = { carstats:[ {miles:267, model:"60"}, {miles:273, model:"60D"}, {miles:323, model:"75"}, {miles:334, model:"75D"}, {miles:366, model:"90D"}, {miles:409, model:"P100D"} ], config: { speed: 55, temperature: 20, climate: false, wheels: 19 }}
it('should handle CHANGE_CLIMATE', () => { expect( appReducer(initialState,{ type: 'CHANGE_CLIMATE' }) ).toEqual(climateChangeState) })
Then add the CHANGE_CLIMATE case, updateStats, and calculateStats functions to teslaRangeApp.js. Then import the BatteryService.js that was used in part 1.
然后将CHANGE_CLIMATE情况, updateStats和calculateStats函数添加到teslaRangeApp.js。 然后导入第1部分中使用的BatteryService.js 。
import { getModelData } from '../services/BatteryService';
function updateStats(state, newState) { return { ...state, config:newState.config, carstats:calculateStats(newState) } }
function calculateStats(state) { const models = ['60', '60D', '75', '75D', '90D', 'P100D']; const dataModels = getModelData(); return models.map(model => { const { speed, temperature, climate, wheels } = state.config; const miles = dataModels[model][wheels][climate ? 'on' : 'off'].speed[speed][temperature]; return { model, miles }; });}
function appReducer(state = initialState, action) { switch (action.type) { case 'CHANGE_CLIMATE': { const newState = { ...state, config: { climate: !state.config.climate, speed: state.config.speed, temperature: state.config.temperature, wheels: state.config.wheels } }; return updateStats(state, newState); } default: return state }}
If you check the test results, you can see that the two test cases are successful.
如果检查测试结果,则可以看到两个测试用例均成功。
What we have implemented so far is that the changes in the state that occur when the user turns the air conditioner on and off in the application through the test runner only from the viewpoint of Action and Reducer without Redux Store or View.
到目前为止,我们已经实现的是,仅从没有Redux Store或View 的Action and Reducer的角度来看,当用户通过测试运行程序在应用程序中打开和关闭空调时,状态发生了变化。
Check out teslaRangeApp.js as we’ve written it so far
到目前为止我们已经写了teslaRangeApp.js
Check out teslaRangeApp.spec.js
9.3为他人创建Reducer (9.3 Create Reducer for others)
If you create the rest of the test cases by referring to the above method, you finally define the teslaRangeApp.js file in which the reducers of all the apps are defined and the teslaRangeApp.spec.js to test them.
如果您通过参考上述方法创建其余测试用例,则最终定义teslaRangeApp.js文件(其中定义了所有应用程序的简化程序)以及teslaRangeApp.spec.js进行测试。
The final code can be found at:
最终代码可以在以下位置找到:
After completing the code and testing, a total of seven test cases must succeed.
完成代码和测试后,总共必须成功完成七个测试用例。
10.观点:聪明而笨拙的组成部分 (10. The views: smart and dumb components)
As mentioned in 5. Divide The App Into in Containers and Components, we will create Presentational components (dumb components) for presentation purposes and a Container components (smart components) which are wrapper component responsible for Actions while communicating with Redux.
如5.在容器和组件中划分应用程序所述 ,我们将创建用于表示目的的Presentational组件 (哑组件)和一个Container组件 (智能组件),这些组件是与Redux通信时负责Action的包装器组件。
Smart components are responsible for the actions. If a dumb component underneath them needs to trigger an action, the smart component will pass a function through props, and the dumb component can then treat that as a callback.
智能组件负责这些动作 。 如果它们下面的一个哑组件需要触发一个动作,则智能组件将通过prop传递一个函数,然后哑组件可以将其视为回调 。
We already have dumb components for presentation purposes in part 1, and will reuse them.
在第1部分中,我们已经有用于演示目的的哑组件,并将重复使用它们。
Here we create container components as upper wrapper around each dumb components.
在这里,我们创建容器组件作为每个哑组件的上部包装 。
10.1视图层绑定 (10.1 The view layer binding)
Redux needs some help to connect the store to the view. It needs something to bind the two together. This is called the view layer binding. In an app that uses react, this is react-redux
.
Redux需要一些帮助来将商店连接到视图。 它需要一些东西将两者绑定在一起。 这称为视图层绑定 。 在使用react的应用程序中,这是react-redux
。
Technically, a container component is just a React component that uses store.subscribe() to read a part of the Redux state tree and supply props to a presentational component it renders.
从技术上讲,容器组件只是一个React组件,它使用store.subscribe()来读取Redux状态树的一部分并将道具提供给它呈现的呈现组件。
Hence, we can manually create container components, but this is not recommended for Redux official docs. This is because react-redux performs many performance optimizations that are difficult to perform manually.
因此,我们可以手动创建容器组件,但是Redux官方文档不建议这样做。 这是因为react-redux会执行许多难以手动执行的性能优化 。
For this reason, instead of writing the container component by hand, we write it using the connect()
function provided by react-redux.
因此,我们不用手工编写容器组件,而是使用react-redux提供的connect()
函数编写它。
Let’s install the necessary packages first.
让我们先安装必要的软件包。
npm install –save redux
npm install –保存redux
npm install –save react-redux
npm install –保存react-redux
10.2 TeslarCar集装箱 (10.2 TeslarCar Container)
To use connect(), you need to define a special function called mapStateToProps
. This function tells you how to convert the current Redux store state to props to be passed to the presentation component.
要使用connect() ,您需要定义一个名为mapStateToProps
的特殊函数。 此功能告诉您如何将当前Redux商店状态转换为要传递给演示组件的道具 。
The TeslarCar container takes the wheelsize stored in the current store and passes it to props so that it can be rendered by the TeslarCar component. This props will be updated every time the state is updated.
TeslarCar容器将存储在当前商店中的车轮尺寸传递给道具,以便可以由TeslarCar组件进行渲染。 每次更新状态时,此道具都会更新。
After defining the mapStateToProps function, we defined the connect() function as shown below.
定义mapStateToProps函数之后,我们定义了connect()函数,如下所示。
const TeslaCarContainer = connect(mapStateToProps, null)(TeslaCar)
connect() accepts mapDispatchToProps
as the second argument, which takes the dispatch method of the store as its first argument. In the TeslaCar component, we do not need an action, so we have to pass null.
connect()接受mapDispatchToProps
作为第二个参数,它将商店的调度方法作为第一个参数。 在TeslaCar组件中,我们不需要操作,因此我们必须传递null。
Another parenthesis in connect()() may look weird. This form actually means two function calls, the first connect() returns another function, and the second function needs you to pass a React component. In this case it’s our TeslaCar component. This pattern is called currying or partial application and is a form of functional programming.
connect()()中的另一个括号可能看起来很奇怪。 这种形式实际上意味着两个函数调用,第一个connect()返回另一个函数 , 第二个函数需要您传递React组件 。 在这种情况下,这是我们的TeslaCar组件。 这种模式称为currying或部分应用程序 ,是功能编程的一种形式。
Create src/containers/TeslaCarContainer.js and write the code.
创建src / containers / TeslaCarContainer.js并编写代码。
Check out TeslaCarContainer.js
10.3 TeslaStats容器 (10.3 TeslaStats Container)
As with the TeslaCar container, define only the mapStatToProps function and pass it to connect() in TeslaStats container.
与TeslaCar容器一样,仅定义mapStatToProps函数并将其传递给TeslaStats容器中的connect()。
Create src/containers/TeslaStatsContainer.js and write the code.
创建src / containers / TeslaStatsContainer.js并编写代码。
Check out TeslaStatsContainer.js
10.4 TeslaSpeedCounter容器 (10.4 TeslaSpeedCounter Container)
The TeslaSpeedCounter container defines an additional mapDispatchToProps
function to handle the user actions that occur in the TeslaSpeedCounter component.
TeslaSpeedCounter容器定义了一个附加的mapDispatchToProps
函数,以处理TeslaSpeedCounter组件中发生的用户操作。
Create src/containers/TeslaSpeedCounterContainer.js and write the code.
创建src / containers / TeslaSpeedCounterContainer.js并编写代码。
Check out TeslaSpeedCounterContainer.js
10.5 TeslaTempCounter容器 (10.5 TeslaTempCounter Container)
The TeslaTempCounter container is almost identical to the TeslaSpeedCounter except for the state and action creators being passed.
除了传递状态和动作创建者之外, TeslaTempCounter容器与TeslaSpeedCounter几乎相同。
Create src/containers/TeslaTempCounterContainer.js and write the code.
创建src / containers / TeslaTempCounterContainer.js并编写代码。
Check out TeslaTempCounterContainer.js
10.6 TeslaClimateContainer (10.6 TeslaClimateContainer)
Create src/containers/TeslaClimateContainer.js and write the code.
创建src / containers / TeslaClimateContainer.js并编写代码。
Check out TeslaClimateContainer
10.7 TeslaWheelsContainer (10.7 TeslaWheelsContainer)
Create src/containers/TeslaWheelsContainer.js and write the code.
创建src / containers / TeslaWheelsContainer.js并编写代码。
Check out TeslaWheelsContainer.js
We have created the container components corresponding to the presentation components generated in part 1 through connect() of react-redux.
我们已经通过react-redux的connect()创建了与第1部分中生成的表示组件相对应的容器组件。
11.提供者 (11. Provider)
Let’s put together all the things we’ve done so far and make our apps work. So far we have defined action objects and created action creators that create action objects. And when an action occurs, we have created reducers that actually treat and return a new state. We then created a container component that connects each of the presentation components to the Redux store.
让我们将到目前为止所做的所有事情汇总起来,使我们的应用程序正常运行。 到目前为止,我们已经定义了动作对象并创建了创建动作对象的动作创建者 。 当动作发生时,我们创建了实际上可以处理并返回新状态的减速器 。 然后,我们创建了一个容器组件 ,用于将每个演示组件连接到Redux存储。
Now every container component needs a way to access the store, which is what the Provider
does. The Provider component wraps the entire application and allows subcomponents to communicate with the store via connect().
现在,每个容器组件都需要一种访问商店的方法,这就是Provider
所做的。 Provider组件包装了整个应用程序,并允许子组件通过connect()与商店进行通信 。
The top-level component of our app, App.js, looks like this:
我们应用程序的顶级组件App.js如下所示:
Check out App.js
查看App.js
12.他们如何一起工作 (12. How they all work together)
Finally, all the puzzle pieces were completed. Now let’s look at the following animation as an example of when all the puzzle pieces are tied together and the user triggers the speed up event.
最终,所有拼图块都完成了。 现在,让我们看下面的动画,作为所有拼图块捆绑在一起并且用户触发加速事件的示例。
Now run npm start and it will be compiled normally and the application will be started.
现在运行npm start ,它将正常编译并启动应用程序。
But there are still a few things to do.
但是仍然有一些事情要做。
First, copy all the contents of /containers/TeslaBattery.css that you created in part 1 and add them to App.css.
首先,复制您在第1部分中创建的/containers/TeslaBattery.css的所有内容,并将它们添加到App.css中 。
Check out App.css
Next, open /components/TeslaCounter/TeslaCounter.js and modify the onClick event handler as follows: This is because part 2 no longer handles event handling in TeslaBattery.js.
接下来,打开/components/TeslaCounter/TeslaCounter.js并按如下所示修改onClick事件处理程序:这是因为第2部分不再处理TeslaBattery.js中的事件处理。
onClick={(e) => props.increment(e, props.initValues.title)}-->onClick={(e) => { e.preventDefault(); props.increment(props.currentValue)}}
onClick={(e) => props.decrement(e, props.initValues.title)}-->onClick={(e) => { e.preventDefault(); props.decrement(props.currentValue)}}
- Next, let’s not use props repeatedly by using ES6 Object destructuring. 接下来,我们不要通过使用ES6 Object Destructuring重复使用props。
const TeslaCounter = (props) => ( <p className="tesla-counter__title">{props.initValues.title}</p> ...
-->const TeslaCounter = ({ initValues, currentValue, increment, decrement }) => ( <p className="tesla-counter__title">{initValues.title}</p> ...
Check out TeslaCounter.js
Finally, our Redux version of Tesla Battery Range Calculator app is complete!
终于,我们的Redux版本的Tesla Battery Range Calculator应用程序完成了!
13.另外一件事:Redux Dev Tools (13. One more thing: Redux Dev Tools)
The Redux Dev Tool
makes it much easier to view Redux state tracking and take advantage of cool features like time travel debugging.
Redux Dev Tool
使查看Redux状态跟踪变得更加容易,并利用了诸如时程调试之类的出色功能。
We’ll install it on Chrome here.
我们将在此处将其安装在Chrome上。
Open the App.js file and modify the createStore part as follows:
打开App.js文件,并按如下所示修改createStore部分:
const store = createStore(appReducer);-->const store = createStore(appReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
- Check on browser 在浏览器上检查
在继续下一部分之前: (Before you go on to the next part:)
Check out the final project code
查看最终的项目代码
Check out the live demo
查看现场演示