这篇文章是30天React系列的一部分 。
在本系列中,我们将从非常基础开始,逐步了解您需要了解的所有内容,以便开始使用React。如果您曾经想学习React,那么这里就是您的最佳选择!
使用Redux进行数据管理
完整代码演示
在线演示 :我的示例
GitHub: https://github.com/lenvo222/Redux_demo.git
凭借flux和Redux的知识,让我们将Redux集成到我们的应用程序中,并浏览连接的应用程序。
昨天,我们(详细地)讨论了Flux模式的原因,它是什么,我们可以使用的不同选项,以及Redux。
今天,我们将回到代码并在我们的应用程序中添加Redux。我们现在用它构建的应用程序很简单,只会向我们显示页面最后一次获取当前时间。为简单起见,我们不会使用JavaScript Date
对象调用远程服务器。
我们要使用Redux的第一件事就是安装库。我们可以使用npm
包管理器进行安装redux
。在我们之前构建的应用程序的根目录中,让我们运行npm install
命令来安装redux:
npm install --save redux
我们还需要安装的软件包,我们将与Redux使用时,react-redux
这将帮助我们react
和redux
绑在一起:
npm install --save react-redux
配置和设置
我们需要做的下一步工作是在我们的应用程序内部设置Redux。我们需要执行以下操作来设置它:
- Define reducers 定义减速器
- Create a store 创建一个商店
- Create action creators 创建动作创建者
- Tie the store to our React views 将商店与我们的React视图联系起来
- Profit 利润
第5步没有promises,但它会很好,是吗?
Precursor( 先导)
我们将继续谈论术语,所以请轻轻地进行这种设置讨论(实现更重要的是让我们的手指移动)。我们稍微重构我们的应用程序(烦人,我知道......但这是最后一次)所以我们可以创建一个 包装器组件( can create a wrapper component )来通过我们的应用程序提供数据。
完成后,我们的应用树将具有以下形状:
[Root] -> [App] -> [Router/Routes] -> [Component]
不再拖延,让我们src/App.js
进入src/containers
目录,我们需要同时更新我们的导入中的一些路径。我们将使用几天前讨论的反应路由器材料。
要安装路由的库
npm install --save react-router-dom
我们将在<Switch />
声明中包含一些路线,以确保一次只显示一个。
App.js
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;
先将Home.js 和About.js写为如下,看下效果 下方代码是Redux案例中使用的
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
const About = (props) => (
<div className="about">
<h2>About route</h2>
</div>
)
在views/Home/Home.js && view/About/About
import React from 'react';
import { connect } from 'react-redux';
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
const mapStateToProps = state => {
return {
currentTime: state.currentTime
}
}
export default connect(
mapStateToProps
)(Home);
import React from 'react';
const About = (props) => (
<div className="about">
<h2>About route</h2>
</div>
)
export default About
此外,我们需要创建一个我们将调用的新容器Root
,它将包装整个<App />
组件并使 store 商店可用于应用程序的其余部分。让我们创建src/containers/Root.js
文件:
touch src/containers/Root.js
目前,我们将在此处使用占位符组件,但在讨论store 商店时我们将替换此内容。现在,让我们输出一些东西:
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结构中采取的步骤通常对于我们将构建的大多数应用程序都是相同的。我们需要:
- Write a root reducer
- Write actionCreators
- Configure the store with the rootReducer, the store, and the app
- Connect the views to the actionCreators
- 写一个根减速器
- 写actionCreators
- 使用rootReducer,商店和应用程序配置商店
- 将视图连接到actionCreators
我们会有目的地保持这个高级别的介绍有点短,所以如果这是一个满口的话,请紧紧抓住它,这一切都会很快变得更有意义。
让我们设置结构以允许我们添加redux。我们几乎可以在一个src/redux
目录中完成所有工作。让我们创建该目录。
mkdir -p src/redux
touch src/redux/configureStore.js
touch src/redux/reducers.js
让我们首先创建我们的Reducer减速器。虽然听起来很复杂,但是Reducer减速器实际上非常简单,具有一定的经验。减速器实际上只是一个功能。它的唯一责任是返回下一个state状态的代表。
在Redux模式中,与flux不同,我们只为整个应用程序处理一个全局存储。这使事情变得更容易处理,因为我们的应用程序的数据只有一个地方可以存在。
该root reducer(根减速器)功能是负责返回应用程序的当前全局状态的表示。当我们在store商店上dispatch调度一个action动作时,将使用应用程序调用此reducer函数的action动作导致 current state当前状态和the state to update状态更新。
让我们在一个文件中构建我们的根reducer src/redux/reducers.js
。
// Initial (starting) state
export 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
这个初始值是要导出的以后设置值会用到
// Initial (starting) state
export const initialState = {
currentTime: new Date().toString()
}
在函数中,我们将第一个参数定义为初始状态(第一次运行时,rootReducer
调用时不带参数,因此它将始终返回initialState
第一次运行)。
那是现在的rootReducer。就目前而言,State(状态)始终与initialState具有相同的值。在我们的例子中,这意味着我们的 data tree(DOM数据树)只有一个single key (单独的键)currentTime
。
什么是action(动作)?
这里的第二个参数是从商店调度的操作。我们很快就会回到这意味着什么。现在,让我们来看看这个action动作。
至少,一个action动作必须包括一个type
键。该type
键可以是任何我们想要的value(值),但是它必须存在。例如,在我们的应用程序中,我们偶尔会发送一个动作,我们想告诉store商店获取新的当前时间。我们可能将此操作称为字符串值FETCH_NEW_TIME
。
我们可能从我们的store(商店)dispatch(发送)处理此更新的操作如下所示:
{
type: 'FETCH_NEW_TIME'
}
因为我们通过输入这个字符串很多并且我们希望避免在某处可能出现错误拼写,所以创建一个types.js
将操作类型导出为常量的文件是很常见的。让我们遵循这个约定并创建一个src/redux/types.js
文件:
export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';
我们将从types.js
文件中引用它,而不是使用硬编码的'FETCH_NEW_TIME'字符串调用操作:
创建一个src/redux/actionCreators.js
文件
import * as types from './types';
export const fetchNewTime = () => ({
type: types.FETCH_NEW_TIME,
})
当我们想要将数据与我们的操作一起发送时,我们可以向操作添加我们想要的any keys任何键。我们通常会看到这个被称为payload
,但它可以被称为任何东西。将有效的信息称为负载payload
是一种惯例。
我们的FETCH_NEW_TIME
操作将使用新的当前时间发送有效负载。由于我们希望通过我们的操作发送可序列化值( serializable value),因此我们将发送新当前时间的字符串值。
{
type: types.FETCH_NEW_TIME,
payload: new Date().toString() // Any serializable value
}
回到我们的reducer中,我们可以检查操作类型并采取适当的步骤来创建下一个状态。在我们的例子中,我们只会存储payload
。如果type
动作是FETCH_NEW_TIME
,我们将返回新的currentTime(from our action payload 来自我们的动作有效负载)和状态的其余部分(using the ES6 spread syntax 使用ES6扩展语法):
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case types.FETCH_NEW_TIME:
return { ...state, currentTime: action.payload}
default:
return state;
}
}
完整的reducers.js代码
import * as types from './types';
export const initialState = {
currentTime: new Date().toString(),
}
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case types.FETCH_NEW_TIME:
return { ...state, currentTime: action.payload}
default:
return state;
}
}
请记住,reducers 必须返回一个状态,因此在默认情况下,请确保至少返回当前状态。
保持清醒
由于每次调度操作时都会运行reducer函数,因此我们希望确保这些函数尽可能简单快速。我们不希望它们引起任何副作用或者有很多延迟。
我们将在动作创建者中处理reducer 之外的副作用。
在我们查看动作创建者(以及为什么我们称之为动作创建者)之前,让我们将我们的商店连接到我们的应用程序。
我们将使用该react-redux
包将我们的视图连接到我们的redux商店。让我们确保使用npm
以下命令安装此软件包:
npm install --save react-redux
连接商店到视图
该react-redux
包导出一个名为的组件Provider
。该Provider
组件使我们的应用程序中的所有容器组件都可以使用该存储,而无需我们每次都需要手动传递它。
该Provider
组件需要一个store
它期望成为有效的redux存储的prop,因此我们需要configureStore
在应用程序运行之前完成一个函数而不会出错。现在,让Provider
我们在我们的应用程序中连接组件。我们将通过更新Root
我们之前创建的包装器组件来使用该Provider
组件。
Root.js 修改
import { Provider } from 'react-redux';
// ...
const Root = (props) => {
// ...
return (
<Provider store={store}>
<App />
</Provider>
);
}
现在还没有注释掉的那两句,需要配置Store才有
现在Root.js代码如下
import React from 'react';
import { Provider } from 'react-redux';
import App from './App';
// import configureStore from '../redux/configureStore'
const Root = (props) => {
// const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
export default Root;
请注意,我们将store
值发送给我们的Provider
组件......但我们还没有创建商店!我们现在解决这个问题。
配置商店
为了创建商店,我们将使用new 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;
然后打开路由中的注释代码Root.js如下
Root.js
import React from 'react';
import { Provider } from 'react-redux';
import App from './App';
import configureStore from '../redux/configureStore'
const Root = (props) => {
const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
export default Root;
如果我们在浏览器中加载页面,我们会看到有一个巨大的错误,没有页面被渲染。
error redux告诉我们,我们的商店内没有减速器。如果没有reducer,它将不知道如何处理动作或如何创建状态等。为了超越这个错误,我们需要引用我们创建的rootReducer。
该createStore
函数希望我们将rootReducer作为第一个参数传递。它还会期望初始状态作为第二个参数传入。我们将从reducers.js
我们创建的文件中导入这两个值。
import { rootReducer, initialState } from './reducers'
// ...
export const configureStore = () => {
const store = createStore(
rootReducer, // root reducer
initialState, // our initialState
);
return store;
}
现在让我们通过调用函数创建Root.js
一个实例来更新我们的文件。store
configureStore()
const Root = (props) => {
const store = configureStore();
return (
<Provider store={store}>
<App />
</Provider>
);
}
连接视图(续)
我们的应用程序中的所有内容都设置为使用Redux而不会产生太多开销。提供的另一个便利redux
是使用包导出的函数将状态树的各个部分绑定到不同的组件。connect()
react-redux
该connect()
函数返回一个函数,该函数期望第一个参数是组件的参数。这通常被称为高阶组件。
该connect()
函数希望我们将至少一个参数传递给函数(但通常我们将传入两个参数)。它期望的第一个参数是一个将被调用的函数,state
并期望一个对象将数据连接到视图。让我们看看我们是否可以在代码中揭开这种行为的神秘面纱。
我们将此函数称为mapStateToProps
函数。因为它的职责是将状态映射到与组件原始合并的对象props
。
让我们创建Home视图,src/views/Home.js
并使用此connect()
函数绑定currentTime
我们的状态树中的值。
import { connect } from 'react-redux';
// ...
const mapStateToProps = state => {
return {
currentTime: state.currentTime
}
}
export default connect(
mapStateToProps
)(Home);
此connect()
功能自动传递任何在该函数的第一个参数的按键作为props
给Home
组件。
在我们的演示currentTime
中,Home
组件中的prop 将映射到状态树键currentTime
。让我们更新Home
组件以显示以下值currentTime
:
const Home = (props) => {
return (
<div className="home">
<h1>Welcome home!</h1>
<p>Current time: {props.currentTime}</p>
</div>
);
}
虽然这个演示不是很有趣,但它表明我们的Redux
应用程序已设置为我们data
致力于全局状态和我们的视图组件映射数据。
明天我们将通过动作创建者开始触发我们的 global state 全局状态更新,并通过将多个redux模块组合在一起工作。
完整代码演示
学习REACT正确的方法
React和朋友的最新,深入,完整的指南。
下一章:
Redux动作
本教程系列的完整源代码可以在GitHub repo上找到,其中包括所有样式和代码示例。
如果您在任何时候感到困难,还有其他问题,请随时通过以下方式与我们联系:
- 在文章末尾评论这篇文章
- 通过[email protected]发送电子邮件给我们
- 加入我们的gitter室
- 发送电子邮件至@fullstackreact