使用 react-redux 绑定GitHub 上提供了针对多个流行框架的 Redux 绑定,包括 React、Angular 和 Vue。这些绑定使得将 Redux 与通过这些框架构建的应用程序相集成变得很容易。
Redux 中不包含 react-redux 绑定,所以您必须使用以下命令单独安装它们:npm install react-redux
绑定提供了一个 API,该 API 包含一个 React 组件和一个 JavaScript 方法:
- Provider 组件允许 Provider 组件中包含的组件访问 Redux 存储。
- void connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]) 函数将一个 React 表示组件连接到 Redux 存储。
Provider 组件很方便,您不需要在整个组件分层结构中手动传递存储作为一个属性。嵌套在 Provider 组件内的组件会自动获得 Redux 存储的访问权。
connect() 函数将一个表示组件连接到 Redux 存储,以便在存储更改时更新该组件。
与 Redux 存储的自动连接在 中,了解了如何将单个 React 组件连接到 Redux 存储。这种风格非常流行,以至于 redux-react 绑定提供了一个 connect() 方法来自动将无状态功能组件连接到存储。
在本文的整篇文章中,stoplight 都与交通信号灯 同义。
为了演示,我首先将修改 的交通信号灯应用程序的 App 组件,让其使用称为容器组件 的组件 — 这类组件会自动连接到 Redux 存储,并向一个封闭的无状态功能组件提供属性。清单 1 给出了更新后的 App 组件。
清单 1. 应用程序 (app.js),其中现在使用了容器组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import React, { Component } from 'react';
import { StoplightContainer } from './stoplight-container';
import { ButtonContainer } from './button-container';
import { createStore } from 'redux';
import { reducer } from './reducer';
export class App extends Component {
render() {
const store = createStore(reducer);
return(
<div>
<StoplightContainer store={store}/>
<ButtonContainer store={store}/>
</div>
)
}
}
|
没有像原始实现中一样返回包含 Stoplight 和 Buttons 组件的 DIV,App 组件的经过改良的 render() 方法返回了一个包含两个容器组件的 DIV:StoplightContainer 和 ButtonContainer。
请注意,我仍将 Redux 存储传递给了 App 组件中包含的组件。您会在下一节看到如何规避该需求。
清单 2 显示了 stoplight 容器。
清单 2. stoplight 容器 (stoplight-container.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import { connect } from 'react-redux';
import { Stoplight } from './stoplight';
const mapStateToProps = state => {
return {
goColor: state == 'GO' ? 'rgb(39,232,51)' : 'white',
cautionColor: state == 'CAUTION' ? 'yellow' : 'white',
stopColor: state == 'STOP' ? 'red' : 'white'
}
}
const mapDispatchToProps = null;
export const StoplightContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Stoplight);
|
简单来讲,StoplightContainer 连接到 Redux 存储,并将应用程序状态映射到它包含的 stoplight 的属性。应用程序状态为 GO、CAUTION 或 STOP,stoplight 的属性为 goColor、cautionColor 和 stopColor。
默认情况下,stoplight 的 3 个属性中的每一个都是 white。当状态为 GO 时,stoplight 容器会将 stoplight 的 goColor 属性映射到绿灯(编码为 rgb(39,232,51))。当状态为 CAUTION 时,会将 cautionColor 映射到 yellow。当状态为 STOP,会将 stopColor 映射到 red。
为了将应用程序状态映射到 stoplight 属性, 使用了 react-redux 绑定的 connect() 函数将 StoplightContainer 连接到 Redux 存储。
通过调用 connect() 函数并将 Stoplight 传递给 connect() 返回的函数,Redux 在 Redux 存储中的状态发生更改时自动更新 Stoplight。您只需调用 connect() 即可实现此目的。当存储发生更改时,Redux 会调用 StoplightContainer 的 mapStateToProps() 方法。Redux 将 StoplightContainer.mapStateToProps() 返回的对象的属性值复制到 StoplightContainer 中包含的 stoplight。
connect() 方法接受两个参数,这两个参数都是函数。第一个函数将来自 Redux 存储的状态映射到所包含的组件(在本例中为 Stoplight)的属性。第二个函数将 Redux 分派调用映射到属性;但是,stoplight 不启用任何行为,所以 stoplight 容器不会将分派调用映射到属性。结果,stoplight 容器的 mapDispatchToProps 函数为 null。
清单 3 显示了 Stoplight 组件的经过再次改良的实现,其中使用了它的 3 个属性作为 SVG 圆圈的 fill 属性。
清单 3. Stoplight (stoplight.js),已恢复为无状态功能组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| import React, { Component } from 'react';
export const Stoplight = ({
goColor,
cautionColor,
stopColor
}) => {
return(
<div style={{textAlign: 'center'}}>
<svg height='170'>
<circle cx='145' cy='60' r='15'
fill={stopColor}
stroke='black'/>
<circle cx='145' cy='100' r='15'
fill={cautionColor}
stroke='black'/>
<circle cx='145' cy='140' r='15'
fill={goColor}
stroke='black'/>
</svg>
</div>
)
}
|
Stoplight 组件已恢复为无状态功能组件,它从 StoplightContainer 组件接收自己的属性。
清单 4 显示了 ButtonContainer 组件。
清单 4. 按钮容器 (button-container.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| import { connect } from 'react-redux';
import { Buttons } from './buttons';
import { goAction, cautionAction, stopAction } from './actions';
const mapStateToProps = state => {
return {
lightStatus: state
}
}
const mapDispatchToProps = dispatch => {
return {
go: () => { dispatch(goAction) },
caution: () => { dispatch(cautionAction) },
stop: () => { dispatch(stopAction) }
}
}
export const ButtonContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Buttons);
|
ButtonContainer 组件将当前状态映射到一个名为 lightStatus 的 Buttons 属性。信号灯状态就是状态的值(GO、CAUTION 或 STOP)。
不同于 Stoplight 组件,Buttons 组件中的按钮会发起改变状态的行为,所以 ButtonContainer 将 dispatch() 调用映射到 Buttons 属性。这些属性是 Buttons 组件使用的函数,如清单 5 所示。
清单 5. Buttons 组件 (buttons.js),也已恢复为无状态功能组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| import React, { Component } from 'react';
export const Buttons = ({
go,
caution,
stop,
lightStatus
}) => {
return(
<div style={{textAlign: 'center'}}>
<button onClick={go}
disabled={lightStatus == 'GO' || lightStatus == 'CAUTION'}
style={{cursor: 'pointer'}}>
Go
</button>
<button onClick={caution}
disabled={lightStatus == 'CAUTION' || lightStatus == 'STOP'}
style={{cursor: 'pointer'}}>
Caution
</button>
<button onClick={stop}
disabled={lightStatus == 'STOP' || lightStatus == 'GO'}
style={{cursor: 'pointer'}}>
Stop
</button>
</div>
)
}
|
清单 5 中的 Buttons 组件使用它的 go、caution 和 stop 属性(都是函数)作为每个按钮的 onClick 处理函数的回调。这些属性来自 ButtonContainer 组件。请注意,像 Stoplight 组件一样,Buttons 组件已恢复为无状态功能组件。
表示组件与容器组件的分离被动视图设计模式将抓取和显示数据的操作混合在一个组件中,这会让该组件很难测试。相反,可通过一个容器组件来分离关注点,该组件抓取数据并传递给关联的表示组件。表示组件是一个无状态组件,仅显示数据且容易测试。此方法实质上实现了 。
react-redux 绑定不仅提供了与 Redux 存储的自动连接,还通过将关注点分离到容和关联的无状态组件来帮助执行良好的编程实践。容器组件实现 mapStateToProps()(用于将状态映射到数据)和 mapDispatchToProps()(用于将状态映射到行为)。这种分离有诸多好处:
- 表示组件很容易实现和推断。
- 表示组件很容易测试,因为它们不会改变数据。
- 表示组件可在不同数据源中重用。
- 容器组件很容易测试,因为它们没有表示代码。
react-redux 绑定的 connect() 函数已介绍得足够多了;现在让我们来看看 React Provider 组件。
Redux 提供程序使用 Redux 的 connect() 函数的一个良好的辅助影响是,Stoplight 和 Buttons 等无状态功能组件不再需要直接访问 Redux 存储 — 因为这些组件不再根据状态来计算它们的属性。相反,相应的容器组件将 Redux 存储与应用程序的无状态功能组件联系起来。这种安排使得无状态功能组件(比如 Stoplight 和 Button 的最终版本)更容易测试。
但是,容器组件仍访问应用程序状态,将它映射到容器包含的无状态组件的属性。要使 Redux 存储可用于应用程序的 React 组件,可以在组件分层结构中显式向下传递它,或者可以使用 Provider,如清单 6 所示。
清单 6. 使用 Provider 组件 (index.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import React from 'react';
import ReactDOM from 'react-dom';
import Redux, { createStore } from 'redux';
import { Provider } from 'react-redux';
import { reducer } from './reducer';
import { App } from './app';
ReactDOM.render(
<Provider store={createStore(reducer)}>
<App />
</Provider>,
document.getElementById('root')
)
|
您为 Provider 指定的属性自动可用于 Provider 组件中包含的任何 React 组件。在本例中,App 组件(以及 App 组件中包含的 StoplightContainer 和 ButtonContainer 组件)会自动获取 Redux 存储的访问权。
目前您已通过一个简单应用程序了解了 Redux 基础原理和 react-redux 绑定,该应用程序具有最简单的状态 — 单个字符串。要理解 Redux 的更高级方面,是时候查看一个更复杂的应用程序了。 |