本文转载自:众成翻译
译者:iOSDevLog
链接:http://www.zcfy.cc/article/3811
原文:https://www.fullstackreact.com/30-days-of-react/day-19/
随着我们了解了flux和Redux的知识,让我们将Redux整合到我们的应用中,并通过连接的应用。
昨天, 我们讨论了流量模式的原因, 它是什么, 我们有不同的选择, 以及介绍了Redux。
今天, 我们将回到代码和添加Redux在我们的应用。现在我们正在用它构建的应用是简单的, 这只会显示我们最后一次获取当前时间的页面。为了简单起见, 我们不会调用远程服务器, 只需使用 JavaScript 的Date
对象。
第一件事, 我们使用redux将不得不安装redux库。我们可以使用npm
包管理器来安装redux
。在我们以前构建的应用的根目录中, 让我们运行npm install
命令来安装redux:
npm install --save redux
想使用redux, 我们还需要安装另一个包, react-redux
, 帮助我们把react
和 redux
绑在一起。
npm install --save react-redux
配置和设置
接下来的工作, 我们需要做的是在我们的应用内建立redux。我们需要执行以下操作才能设置它:
定义归约
创建Store
创建动作创造者
将Store与我们的React意见联系起来
得益
第5步没有promises, 但它会更好, 嗯?
Precursor先驱
我们少量地讨论术语,(让我们的手指移动是更重要的)。我们将只是稍微调整我们的应用(烦人, 我知道, 但这是最后一次), 所以我们为了提供数据通过我们的应用可以创建一个包装组件。
完成后, 我们的应用树将具有以下形状:
[Root] -> [App] -> [Router/Routes] -> [Component]
废话少说, 让我们将我们的 src/App.js
移动到src/containers
目录中, 我们需要同时更新来自我们的导入的一些路径。我们将使用几天前讨论的React路由材料。
我们将在 <Switch />
语句中包含几条路由, 以确保一次只显示一个。
import React from 'react';
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom'
// We'll load our views from the `src/views`
// directory
import Home from './views/Home/Home';
import About from './views/About/About';
const App = props => {
return (
<Router>
<Switch>
<Route
path="/about"
component={About} />
<Route
path="*"
component={Home} />
</Switch>
</Router>
)
}
export default App;
此外, 我们将需要创建一个新的容器, 我们将调用Root
, 这将包装我们的整个 <App />
组件, 并使可用的其余应用。让我们创建src/containers/Root.js
文件:
`touch src/containers/Root.js`
目前, 我们将在这里使用一个占位符组件, 但我们将在讨论存储时替换此内容。现在, 让我们导出 _一些东西_:
import React from 'react';
import App from './App';
const Root = (props) => {
return (
<App />
);
}
export default Root;
最后, 让我们更新的路由, 我们渲染我们的应用在src/index.js
文件使用我们的新的Root
容器, 而不是它以前使用的App
。
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './containers/Root';
import './index.css';
ReactDOM.render(
<Root />,
document.getElementById('root')
);
添加Redux
现在有了一个坚实的应用结构就位, 我们可以开始添加Redux。我们将采取的步骤以配合一些我们将建立的大多数应用的Redux结构通常都是相同的。我们需要:
写一个根归约器
写 actionCreators
配置存储、rootReducer和应用
将视图连接到 actionCreators
我们故意保持这个高层次的介绍短一些, 所以坚持一下, 这一切都将在短期内变得更有意义。
让我们设置允许我们添加Redux的结构。我们将在src/redux
目录中完成几乎所有的工作。让我们创建该目录。
mkdir -p src/redux
touch src/redux/configureStore.js
touch src/redux/reducers.js
首先我们先创建归约器。虽然它听起来很复杂, 但是归约器实际上是相当直接的, 有一定的经验。归约器仅是 字面 函数。它的唯一责任是返回一个 next 状态的表示。
在Redux模式, 不像flux, 我们只在 整个 应用处理 一个 全局存储的。这使得事情变得更容易处理, 因为有一个地方的数据, 我们的应用生活。
root 归约器函数负责返回应用当前全局状态的表示形式。当我们在存储上发送操作时, 将使用应用的当前状态和导致状态更新的操作来调用此归约器函数。
让我们在一个文件中建立我们的根归约器在src/redux/reducers.js
.。
// Initial (starting) state
const initialState = {
currentTime: new Date().toString()
}
// Our root reducer starts with the initial state
// and must return a representation of the next state
const rootReducer = (state = initialState, action) => {
return state;
}
export default rootReducer
I在函数中, 我们将第一个参数定义为初始状态 (第一次运行时, 不带参数调用rootReducer
, 因此它总是在第一次运行时返回 initialState
)。
这就是现在的 rootReducer。就像现在这样, state的价值总是和 initialState 一样。在我们的例子中, 这意味着我们的数据树有一个单一的currentTime
键。
什么是动作?
这里的第二个参数是从存储区发送的操作。我们很快就会回来。现在,让我们来看看动作。
最起码, 一个动作 必须 包括一个type
键。type
键可以是我们想要的任何值, 但它必须存在。例如, 在我们的应用中, 我们将偶尔发送一项操作, 我们想告诉存储获取 最新 当前时间。我们可以将此操作称为FETCH_NEW_TIME
的字符串值。
我们可能从我们的存储发送的处理此更新的操作如下:
{
type: 'FETCH_NEW_TIME'
}
正如我们将通过键入这个字符串很多, 我们希望避免可能的拼写错误的地方, 它是常见的创建一个types.js
文件, 将操作类型导出为常量。让我们遵循这个约定, 创建一个src/redux/types.js
文件:
`export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';`
我们将从types.js
文件中引用它, 而不是使用'FETCH_NEW_TIME' 的硬编码字符串调用该操作:
import * as types from './types';
{
type: types.FETCH_NEW_TIME,
}
当我们想发送数据与我们的动作, 我们可以添加任何键, 我们想我们的动作。我们通常会看到这个所谓的payload 有效载荷
, 但它可以被称为任何东西。这是一个公约, 调用附加信息的payload
。
我们的FETCH_NEW_TIME
动作将发送一个有效载荷与新的当前时间。因为我们想发送一个 序列化 值与我们的动作, 我们将发送新的当前时间的字符串值。
{
type: types.FETCH_NEW_TIME,
payload: new Date().toString() // Any serializable value
}
回到我们的归约器, 我们可以检查的动作类型, 并采取适当的步骤, 创建下一个状态。在我们的情况下, 我们只储存payload
。如果操作的type
是FETCH_NEW_TIME
, 我们将返回新的 currentTime (从我们的操作有效负载) 和其他状态 (使用 ES6 扩展语法):
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case types.FETCH_NEW_TIME:
return { ...state, currentTime: action.payload}
default:
return state;
}
}
请记住, 归约 必须 返回一个状态, 所以在默认情况下, _最起码_确保返回当前状态。
保持轻型化
由于归约器的功能每次调度一个动作, 我们要确保这些功能是尽可能简单和快速。我们不希望他们造成任何副作用或有太多的延迟。
我们将处理动作创造者中归约器的副作用。
在我们看动作创造者 (以及为什么我们称他们为动作创造者) 之前, 让我们把我们的存储与我们的应用挂钩。
我们将使用 react-redux
包将我们的视图连接到我们的redux存储。让我们确保使用npm
安装此包:
npm install --save react-redux
将存储与视图挂钩
react-redux
包输出一个名为 Provider
的组件。Provider
组件使存储可以使用我们的应用中的所有容器组件, 而无需我们每次都需要手动传递它。
Provider
组件期望一个store
的属性, 它期望是一个有效的redux存储, 所以我们的应用没有错误运行之前,我们需要完成一个configureStore
功能,。现在, 让我们在应用中连接Provider
组件。我们将通过更新我们以前创建的包装Root
组件来使用Provider
组件来完成此项。
import { Provider } from 'react-redux';
// ...
const Root = (props) => {
// ...
return (
<Provider store={store}>
<App />
</Provider>
);
}
请注意, 我们正在将store
值发送到Provider
组件, 但我们还没有创建该存储。现在我们来解决这个问题。
配置存储
为了创建一个存储, 我们将使用新的src/redux/configureStore.js
来导出将负责创建存储的函数。
我们如何创建一个存储?
redux包输出一个叫做
createStore 的函数, 它将为我们创建实际的存储区, 因此, 让我们打开
src/redux/configureStore.js 文件并导出一个称为
configureStore()的函数 (我们将很快定义), 并导入
createStore` 帮助器:
import {createStore} from 'redux';
// ...
export const configureStore = () => {
// ...
}
// ...
export default configureStore;
实际上我们在我们的存储还没有返回任何东西, 所以让我们实际创建的redux
存储使用 createStore
的功能, 我们从redux导入:
import {createStore} from 'redux';
export const configureStore = () => {
const store = createStore();
return store;
}
export default configureStore;
如果我们在浏览器中加载我们的页面, 我们将看到我们有一个巨大的错误: 没有页面得到渲染。
redux给我们的错误告诉我们, 我们存储里没有归约器。如果没有归约器, 它将不知道如何处理动作或如何创建状态等。为了越过这个错误, 我们需要参考我们创建的 rootReducer。
createStore
函数要求我们将 rootReducer 作为第一个参数来传递。它还希望将初始状态作为第二个参数传递。我们将从我们创建的reducers.js
文件中导入这两个值。
import { rootReducer, initialState } from './reducers'
// ...
export const configureStore = () => {
const store = createStore(
rootReducer, // root reducer
initialState, // our initialState
);
return store;
}
现在, 让我们通过调用 configureStore()
函数创建store
的实例来更新我们的Root.js
文件。
const Root = (props) => {
const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
连接视图 (续)
我们的应用中的一切都设置为使用redux, 而无需 太 多开销。redux
提供的一个更方便的方法是使用react-redux
包导出的connect()
函数, 将状态树的片断 绑定 到不同的组件。
connect()
函数返回一个函数, 它期望第一参数是一个组件。这通常叫做高阶组件。
connect()
函数要求我们在函数中至少传递一个参数 (但通常我们会传递两个)。它所期望的第一个参数是一个函数, 它将被称为state
并期望一个返回的对象将数据连接到视图。让我们看看我们是否能在代码中揭开这种行为的神秘面纱。
我们将这个函数称为mapStateToProps
函数。因为它的责任是将状态映射到与组件的原始props
合并的对象。
让我们在src/views/Home.js
中创建 home 视图, 并使用此connect()
函数来绑定currentTime
在我们的状态树中的值。
import { connect } from 'react-redux';
// ...
const mapStateToProps = state => {
return {
currentTime: state.currentTime
}
}
export default connect(
mapStateToProps
)(Home);
此connect()
函数 自动 将函数的第一个参数中的任何键传递为Home
组件的props
。
在我们的演示案例中, Home
组件中的currentTime
属性将映射到currentTime
中的状态树键。让我们更新 Home
组件, 以显示currentTime
中的值:
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
虽然这个演示不是很有趣, 它表明我们有我们的Redux
应用建立了我们的 data
致力于全局状态和我们的视图组件映射数据。
明天, 我们将开始通过操作创建者来触发我们的全局状态的更新, 并通过将多个redux模块组合在一起来工作。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。