壹 ❀ 引

在前面两篇文章中,我们介绍了reduxcontext部分概念与基本用法,这里我们做个简单复习。

redux属于应用数据流框架,主要用于应用状态的管理,比如react中的state。其数据流为view-->action-->reducer-->store-->view,比如用户点击了一个按钮,本质触发的是store.dispatch(action),然后reducer感知事件,触发actionType对应的更新数据方法,从而达到更新store的目的,而当store更新后,早在view订阅的store.subscribe又被触发,这样又通过store.getState拿到最新的store并将其设置为组件的statestate发生变化自然会让组件重新render,这便是一次完整的数据更新过程。

而随之问题也暴露出来了,多个组件需要使用store的数据都得引入store.js文件,而且dispatch以及subscribe等API都在store上,所以你需要使用这些方法的地方也一样得提前引入store。于是,我们紧接着介绍了context概念,context(上下文)的作用主要用于解决组件跨级传值问题,比如上面提到的API方法,我们就可以通过context.Providervaluestore传递下去,这样不管哪个组件都可以通过类似this.context.dispatch的写法调用store上的方法。除了全局传递外,比如A-->B-->C-->D场景,D需要拿到A的数据,但是又不希望BC作为数据传递的工具人,使用context.Consumer同样能解决这一类场景的问题。

接下来要介绍的react-reduxredux作者为react量身定制的库,使用上进一步简化了我们对于redux以及context的写法。由于react-redux是独立redux的存在,因此我们需要单独引入它。在项目根目录执行npm install --save react-redux,接下来我们来了解其在用法的变化,没关系,有前两篇文章的铺垫,这并不会很难!本文开始。

贰 ❀ react-redux

react-redux提供了ProviderconnectmapStateToPropsmapDispatchToProps四个API,我们来一一介绍它们。

贰 ❀ 壹 Provider

在之前context中我们使用Provider得先通过React.createContext()创建,不过有了react-redux后我们可以直接引用,比如:

import { Provider } from 'react-redux'

其用法与含义与之前完全相同,我们还是使用之前文章的例子,在index中做部分修改,将Provider的引用改为react-redux

import React, { Component } from 'react';
import { Provider } from 'react-redux'
import ReactDOM from 'react-dom';
import store from './Store.js';
import Counter from './Counter.js';
import Summary from './Summary.js';
class ControlPanel extends Component {
render() {
return (
<div>
<Counter caption="First" />
<Counter caption="Second" />
<hr />
<Summary />
</div>
);
}
}
ReactDOM.render(
<Provider value={store}>
<ControlPanel />
</Provider>,
document.getElementById('root')
);

注意,这次我们把Provider用在了ReactDOM.render中,也就是进行了真正意义上的全局包裹,之前文章的例子由于ControlPanel组件自身没有使用到store的数据,而是它的子组件需要用,所以之前的写法如下,也没有什么问题:

class ControlPanel extends Component {
render() {
return (
//我们使用了Provider包裹子组件,通过value传递store
<context.Provider value={store}>
<div>
<Counter caption="First" />
<Counter caption="Second" />
<hr />
<Summary />
</div>
</context.Provider>
);
}
}
ReactDOM.render(
<ControlPanel />,
document.getElementById('root')
);

这里只是做个写法纠正说明,并不是react-redux特性如此必须这么写,所以单独做个说明。保存后运行项目会报错,毕竟后续还有代码还没改完,我们先这样。

贰 ❀ 贰 connect

上面的代码修改,我们为全局上下文中添加了数据store,还记得在之前context介绍中如何使用上下文中的数据吗?两种方式,第一种是通过conponentName.contextType = context先为当前组件绑定上下文,之后在constructor中通过super(context)引入,之后就可以通过this.context访问上下文了,第二种方式通过Consumer中回调函数形参直接访问。

而在react-redux中我们通过connect方法帮助组件链接全局上下文,比如:

import { connect } from 'react-redux';
export default connect(mapStateToProps, mapDispatchToProps)(component);

上面代码中component就是你当前创建的组件,由于这个组件没有与上下文扯上关系,所以这里我们使用connect帮助它链接全局store,其次connect接受两个参数,分别是mapStateToPropsmapDispatchToProps,有什么用我们后面再介绍。

这里我们补充一个概念,react-redux将组件分为UI组件容器组件UI组件很好理解,只负责view渲染,不管理state变化,不管理业务逻辑,也不使用redux的API,它所接受的一切数据方法均由props提供。

而所谓容器组件作用与UI组件互补,它的工作是负责state变更以及业务逻辑处理,而这里的容器组件其实由react-redux生成。

注意上面connect这行代码,它本质上等同于:

// 帮UI组件注入数据方法,于是得到了一个新的容器组件,connect就像给一部手机通了电一样,让其有了生命力
const 容器组件 = connect(mapStateToProps, mapDispatchToProps)(UI组件);
export 容器组件;

也就是说我们通过connect帮助一个UI组件链接到了数据层,这里的数据可以是全局的store,也可以是上层组件传递下来的props。于是我们得到了一个被注入了数据的新组件。

说到这里,不知道大家能不能感受到这种做法与context使用全局store的差异性,context使用数据要么直接把上下文与组件绑定后使用,要么借用Consumer使用,就像被内嵌进组件一样,耦合度较高。而connect更像在组件外部给其开了一个传递数据的入口,不管你数据哪里来的,都通过我这里传递进入,方式上就比较统一了,无论是全局store还是上次的props,都可以通过这里以props的方式统一注入,那么组件内部呢都给我通过props的方式访问外部传递进来的方法或者数据。

当然,上述关于UI组件的分类说明比较严格了,实际项目开发中也存在很多包含了业务代码以及自身state的组件使用connect链接store的做法。这里只是科普UI组件容器组件的概念,有时候硬要将一个组件抽离成两个组件反而是一件麻烦事,具体看大家习惯。

上面说了connect用于帮助组件链接数据,那么具体做呢?其实就得依赖上面提到的两个参数了,我们接着说。

贰 ❀ 叁 mapStateToProps

顾名思义,建立stateprops的映射,mapStateToProps接受2个参数,比如:

const mapStateToProps = (state,ownProps) => {
return {
name:state.name,
age:ownProps.age
}
}

说直白点,比如从全局的storestore本身可以理解为全局的state)以及外层组件传递给你的props中提取并组合成当前组件所需要的数据。

上面的代码中返回了一个新的对象,这个对象包含了一个name一个age,数据来源分别是全局state与外层组件传递的props,那么当前组件通过this.props即可访问到这两个属性,而且,无论是外部的state还是外部传递的ownProps发生了变化,都会再次触发此方法(如果你没用这两个参数就不会触发,毕竟没有依赖关系),目的是同步更新传递当前组件的props

贰 ❀ 肆 mapDispatchToProps

看到方法名中的dispatch,直觉应该想到此方法应该跟store.dispatch有关。没错,我们在学习context时,凡是组件需要派发action时最终调用的都是this.context.dispatch,写法上多少有些繁琐。mapDispatchToProps的作用,其实也是将dispatch行为抽离了出去,然后也作为props一部分传进了组件,此方法接受两个参数,如下:

function mapDispatchToProps(dispatch, ownProps) {
return {
onIncrement: () => {
dispatch(Actions.increment(ownProps.caption));
},
}
}

dispatch作用其实与store.dispatch作用相同,只是react-redux对齐进行了封装,我们不用再借用this.context.dispatch调用,而是可以直接在mapDispatchToProps方法中使用,是不是用法上便捷了很多呢?其次,mapDispatchToProps返回了一个对象,包含了一个方法,那么在当前组件内部,调用this.props.onIncrement本质上其实就是在做派发。

ownPropsmapStateToProps的第二个参数作用相同,都是外层组件组件传递的props

叁 ❀ 一个例子

OK,我们介绍了react-redux带来的新概念,让我们使用这些API改写上一篇文章中的例子,近距离感受它们所带来的便捷性。在前面我们其实已经完成了对index.js的改写,接下来要做的是对Counter组件与Summary组件的改写,先上Counter.js的代码:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as Actions from './Actions.js';
import { connect } from 'react-redux'
class Counter extends Component {
render() {
const { onIncrement, onDecrement, caption, value } = this.props;
return (
<div>
<button onClick={onIncrement}>+</button>
<button onClick={onDecrement}>-</button>
<span>{caption} count: {value}</span>
</div>
);
}
} Counter.propTypes = {
caption: PropTypes.string.isRequired,
onIncrement: PropTypes.func.isRequired,
onDecrement: PropTypes.func.isRequired,
value: PropTypes.number.isRequired
};
// 用于将`store`的数据加工成当前组件所需要的数据
function mapStateToProps(state, ownProps) {
console.log(state);
console.log(ownProps);
return {
value: state[ownProps.caption]
}
}
// dispatch的操作都被提到这里了,组件瞬间就干净了
function mapDispatchToProps(dispatch, ownProps) {
return {
onIncrement: () => {
dispatch(Actions.increment(ownProps.caption));
},
onDecrement: () => {
dispatch(Actions.decrement(ownProps.caption));
}
}
} export default connect(mapStateToProps, mapDispatchToProps)(Counter);

这里我们尝试打印mapStateToProps两个参数,你会发现其实state就是全局storeownProps就是父级组件传下来的props

你会发现,Counter组件活生生被抽离成了一个UI组件,以前我们还在组件中定义包裹dispatch的方法,定义初始化state的方法,还需要添加subscribe订阅store的变化,但现在,store的变化感知以及组件state的初始化都交给了mapStateToProps来完成,前面说了,只要外层state变化,都会触发此方法重新渲染。

同理,对于dispatch方法的定义我们也交给了mapDispatchToProps来完成,而组件内部只需要通过this.props.onIncrement就能执行action派发,是不是相对于之前的写法,简洁了很多呢?

同理,我们修改Summary组件,代码如下:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
class Summary extends Component {
render() {
const {sum} = this.props;
return (
<div>Total Count: {sum}</div>
);
}
} Summary.propTypes = {
sum: PropTypes.number.isRequired
}; function mapStateToProps(state) {
let sum = 0;
for (const key in state) {
if (state.hasOwnProperty(key)) {
sum += state[key];
}
}
return {sum};
} export default connect(mapStateToProps)(Summary);

保存代码,这个小例子又运行起来,相较于redux结合context的写法,react-redux确实很大程度上精简了代码量。

肆 ❀ 总

那么到这里,我们通过react-redux再次改写了之前的例子,通过这篇文章,我们知道react-redux与传统redux的差异性,从写代码的角度,不得不说react-redux更加的精简与方便。当然,redux其实还有不少进阶知识我们还未提及,比如中间件,比如异步处理,再或者对于reducer的合并等等,这些知识在后面的文章我们会慢慢介绍,那么本文到此结束。

参考

Redux 中文文档

Redux 入门教程(三):React-Redux 的用法

深入浅出React和Redux第三章

从零开始的react入门教程(十),快速上手react-redux,相对于redux它究竟简化了什么?的更多相关文章

  1. [转] Redux入门教程(快速上手)

    学习前提 在我们开始以前,确保你熟悉以下知识: 函数式JavaScript 面向对象JavaScript JavaScript ES6 语法 同时,确保你的设备已经安装: NodeJS Yarn(或者 ...

  2. [译]:Xamarin.Android开发入门——Hello,Android快速上手

    返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...

  3. react 入门教程 阮一峰老师真的是榜样

    -  转自阮一峰老师博客 React 入门实例教程   作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Nati ...

  4. React入门教程1---初见面

    React入门教程1---初见面:https://blog.csdn.net/solar_lan/article/details/82799248 React 教程 React 是一个用于构建用户界面 ...

  5. 无废话ExtJs 入门教程十九[API的使用]

    无废话ExtJs 入门教程十九[API的使用] extjs技术交流,欢迎加群(201926085) 首先解释什么是 API 来自百度百科的官方解释:API(Application Programmin ...

  6. 无废话ExtJs 入门教程十六[页面布局:Layout]

    无废话ExtJs 入门教程十六[页面布局:Layout] extjs技术交流,欢迎加群(201926085) 首先解释什么是布局: 来自百度词典的官方解释:◎ 布局 bùjú: [distributi ...

  7. 无废话ExtJs 入门教程十五[员工信息表Demo:AddUser]

    无废话ExtJs 入门教程十五[员工信息表Demo:AddUser] extjs技术交流,欢迎加群(201926085) 前面我们共介绍过10种表单组件,这些组件是我们在开发过程中最经常用到的,所以一 ...

  8. 无废话ExtJs 入门教程十四[文本编辑器:Editor]

    无废话ExtJs 入门教程十四[文本编辑器:Editor] extjs技术交流,欢迎加群(201926085) ExtJs自带的编辑器没有图片上传的功能,大部分时候能够满足我们的需要. 但有时候这个功 ...

  9. 无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two]

    无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two] extjs技术交流,欢迎加群(201926085) 不管是几级下拉列表的联动实现本质上都是根据某个下拉列表的变化,去动态加载其 ...

  10. 无废话ExtJs 入门教程十[单选组:RadioGroup、复选组:CheckBoxGroup]

    无废话ExtJs 入门教程十[单选组:RadioGroup.复选组:CheckBoxGroup] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在表单里加了个一个单选组,一个复 ...

随机推荐

  1. java进阶(27)--HashSet与TreeSet

    一.HashSet: 1.特点:无序不可重复,实际上为放入HashMap中的key部分. 2.举例说明:

  2. GoLang 高性能编程之字符串拼接

    看代码突然想到一个问题:字符串在内存中是怎么表示的?花了大半天才理清,这里记录梳理下. 1. 字符 提到字符串需要先了解字符,没有字符哪能串起来呢.不像 int,float 这种直接在内存中以位数表示 ...

  3. Mysql有布尔(BOOL)类型吗

    转载请注明出处: 在MySQL中,没有专门的Boolean数据类型.相反,MySQL中使用TINYINT(1)来代表布尔类型,其中1表示真(True),0表示假(False).在MySQL中,TINY ...

  4. linux route 命令

    route 管理路由表         要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现.在Linux系统中,设置路由通常是为了解决以下问题:该Linux ...

  5. Feign拦截器和解码器

    一.业务需求 在Spring Cloud的项目中,A服务使用Feign调用B服务的某个接口,如果需要传递全局认证token或参数,在方法参数里面加相应字段的方式显然是不可取的. 首先想到的是AOP方式 ...

  6. # Linux操作补充

    取消高亮显示空格和Tab gvim ~/.vimrc # 在.vimrc文件中 set nohls # shell中执行 source ~/.vimrc ./vimrc是Gvim的配置文件 Gvim新 ...

  7. chorm如何静音特定网页

    如图所示,右键想要静音的网页标签,选择将这个网页静音即可

  8. ORA-01017: 用户名/密码无效;登录被拒绝

    总结 出现此错误的原因有多种: 您的用户名或密码实际上不正确 数据库配置不正确(tnanames.ora. $ORACLE_SID 参数) 现在,我们来看看这个错误的解决方案. ORA-01017 解 ...

  9. 【Git】用法小记

    解决windows环境下的CRLF与unix环境下的LF问题,windows提交时CRLF=>LF,签出时LF=>CRLF,unix环境保留 git config --global cor ...

  10. [转帖].NET Framework 中的传输层安全性 (TLS) 最佳做法

    https://learn.microsoft.com/zh-cn/dotnet/framework/network-programming/tls 传输层安全性 (TLS) 协议是一个行业标准,旨在 ...