前言
之前介绍了 redux 的基本用法和中间件,本文主要介绍 redux 在 react 中的使用。react-redux 库是 Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。在平时的开发中,可以使用 react-redux 库,也可以不使用,但是如果使用此框架,就会如虎添翼。后面会有实例来分别展示使用与不使用的区别。
UI 组件 与 容器组件
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件
UI 组件的特征:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
UI 组件的例子
const Title = value => <h1>{value}</h1>;
容器组件
容器 组件的特征:
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 可以使用 react-redux 的 API
总之,UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。顾名思义,connect 是一个桥梁,用来连接 UI 组件和容器组件。
connect 的核心是将开发者定义的组件,包装转换生成容器组件。所生成的容器组件能够使用 Redux store 中的哪些数据,全部由 connect 的参数来确定。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
上面代码中,TodoList 是 UI 组件,VisibleTodoList 就是由 React-Redux 通过 connect 方法自动生成的容器组件。
但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。
-
输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
-
输出逻辑:用户发出的动作如何通过 Action 对象,从 UI 组件传出去。
因此,connect方法的完整 API 如下。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
上面代码中,connect 方法接受两个参数:mapStateToProps 和 mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
mapStateToProps()
mapStateToProps 是一个函数,它的作用是给返回的容器组件注入 props,props 来源于 Redux store 中的状态,所以这个函数一定要返回一个纯 JavaScript 对象。如下图所示:
const mapStateToProps = (state) => {
return {
likeNum: state.likeNum,
hateNum: state.hateNum
}
}
mapStateToProps 会订阅 Store,每当 state 更新的时候,就会自动执行,从而触发 UI 组件的重新渲染。
connect 方法可以省略 mapStateToProps 参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
mapDispatchToProps()
mapDispatchToProps 是 connect 函数的第二个参数,它的作用是将 dispatch 作为 props 传递给容器组件。也就是说,它定义了哪些用户的操作应该当作 Action 传给 Store。
export const likeEvent = () => ({
type: 'like'
});
export const hateEvent = () => ({
type: 'hate',
});
const mapDispatchToProps = {
likeEvent,
hateEvent
}
小结 mapStateToProps 和 mapDispatchToProps 定义了展示组件需要用到的 store 的内容,其中 mapStateToProps 负责输入逻辑,就是将状态数据映射到展示组件的参数(props)上。mapDispatchToProps 负责输出逻辑,即将用户对展示组件的操作映射成 action。
讲到这里,大家可能有一个疑问:connect 是如何获取到 Redux store 中的内容的?这就要借助于 Provider 组件来实现了。
Provider 组件
React-Redux 提供Provider组件,可以让最外层的容器组件拿到state。一般做法是需要将 Provider 作为整个应用的根组件,并获取 store 作为props,如下所示:
ReactDOM.render(
<Provider store={store}>
<ReactReduxDemo />
</Provider>,
document.getElementById('root')
);
它的原理是 React 的 context 属性,store 放在了上下文对象 context 上面。然后,子组件就可以从 context 拿到store。
react 的 Context 属性
什么是 context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
在一个典型的 React 应用中,数据是通过 props 属性自上而下进行传递的,但这种做法在某些情况下比较繁琐的,比如某些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必通过组件树逐层传递 props。
创建 context
创建一个 context 对象
const { Provider, Consumer } = React.createContext();
- Provider(生产者):放置共享的数据
- Consumer(消费者):Consumer 需要嵌套在 Provider 下面,才能通过回调的方式拿到共享的数据源
context 的简单应用
ContextDemo
import React from "react";
import Father from './compontents/context/father';
export const { Provider, Consumer } = React.createContext('default');
class ContextDemo extends React.Component{
render(){
let name = 'son';
return (
<Provider value={name}>
<Father></Father>
</Provider>
)
}
}
export default ContextDemo;
Father Page
import React from "react";
import Son from './son'
class Father extends React.Component{
render(){
return (
<div>
<div>This is Father Page</div>
<Son />
</div>
)
}
}
export default Father;
Son Page
import React from "react";
import { Consumer } from '../../context';
class Son extends React.Component{
render(){
return (
<Consumer>
{
(name) => {
return <div>{`my name is ${name}`}</div>
}
}
</Consumer>
)
}
}
export default Son;
关于 React Context 有一篇讲的比较细致的文章 大家可以看下:www.jianshu.com/p/eba2b76b2…
不使用 react-redux 库的实例
以下是一个非常常见的场景,页面截图来自豆瓣 App,我们可以对影评 “点赞” 或者 “踩”,并且记录 “点赞” 和 “踩” 的数量。如下图所示:
入口
// redux 在 react 中的使用,不使用 react-redux 库
const render = () => {
ReactDOM.render(
<ReactAndReduxDemo
value = {store.getState()}
likeEvent = {() => store.dispatch({type: 'like'})}
hateEvent = {() => store.dispatch({type: 'hate'})}
/>,
document.getElementById('root')
);
}
<!--需要通过 subscribe 来监听 store 的变化-->
store.subscribe(render);
<!--用于第一次触发-->
render();
UI 组件
import React from "react";
import './index.css'
class ReactAndReduxDemo extends React.Component{
render(){
const { value, likeEvent, hateEvent } = this.props;
return (
<div className="wrap">
<div className="like" onClick={likeEvent}>点赞 <span>{value.likeNum}</span></div>
<div className="hate" onClick={hateEvent}>踩 <span>{value.hateNum}</span></div>
</div>
)
}
}
export default ReactAndReduxDemo;
小结:
不借助于 react-redux 库的实现会有以下问题:
- react 与 redux 连接处数据的传递不够简洁和优雅
- store 与 组件耦合在一起
- 每次 store 发生改变都需要手动去监听
使用 react-redux 库的实例
仍然是上面的案例,使用 react-redux 库来实现
入口
import { reducer } from './compontents/reducer';
import { createStore } from 'redux';
import { Provider} from 'react-redux';
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<ReactReduxDemo />
</Provider>,
document.getElementById('root')
);
定义 action
export const likeEvent = () => ({
type: 'like'
});
export const hateEvent = () => ({
type: 'hate',
});
reducer 编写
// 初始状态
var initialState = {
likeNum: 0, // 点赞的总数
hateNum: 0 // 踩的总数
}
export function reducer(state = initialState, action) {
switch(action.type){
case 'like':
return {
likeNum: state.likeNum + 1,
hateNum: state.hateNum
};
case 'hate':
return {
likeNum: state.likeNum,
hateNum: state.hateNum + 1
};
default:
return state;
}
}
页面展示
import React from "react";
import './index.css'
import { connect } from 'react-redux';
import { likeEvent, hateEvent } from '../action.js';
class ReactReduxDemo extends React.Component{
render(){
const { likeNum, hateNum, likeEvent, hateEvent } = this.props;
return (
<div className="wrap">
<div className="like" onClick={likeEvent}>点赞 <span>{likeNum}</span></div>
<div className="hate" onClick={hateEvent}>踩 <span>{hateNum}</span></div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
likeNum: state.likeNum,
hateNum: state.hateNum
}
}
const mapDispatchToProps = {
likeEvent,
hateEvent
}
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxDemo);