Flux --> Redux --> Redux React 入门 基础实例使用
2017-01-21 20:00
716 查看
本文的目的很简单,介绍Redux相关概念用法 及其在React项目中的基本使用
假设你会一些ES6、会一些React、有看过Redux相关的文章,这篇入门小文应该能帮助你理一下相关的知识
一般来说,推荐使用 ES6+React+Webpack 的开发模式,但Webpack需要配置一些东西,你可以先略过,本文不需要Webpack基础
入门,只是一些基础概念和用法的整理,更完整的内容推荐去看看文档,英文,中文
(不过我个人认为,官方文档的例子相对来说太复杂了,很难让新手马上抓住重点)
(官方的例子正统且联系业务,不同类型的操作或数据放在不同文件中,很规范,但也很绕,所以本文使用的例子非常简单,且直接放在一个文件中 以便于理解)
搭飞机前往:
Flux思想、Redux基本概念、Redux的使用、Redux在React中的使用(同步)、Redux在React中的使用(异步,使用中间件)
根据它的概念,一个应用中的数据流动应是单向的,且应用中的所有数据保存在一个位置,数据变化时保证视图也同步变化,保证了数据和视图的状态是一一对应起来的
此应用应该分为四层:
view层:应用的视图,页面的(数据)展示
action层:(视图)发出的某些动作,比如点击事件
dispatcher层:派发器,接收action并处理这些动作,更新数据
store层:存放应用的数据,数据更新后,提醒view层更新视图
它的概念思想可能一时半会理解不了,没关系,过段时间就好了
是要解决什么问题呢?
在使用React的过程中,在组件间通信的处理上我们用了回调的方式,如果组件层级很深,不同组件间的数据交流就会导致回调及其触发的函数非常多,代码冗杂
需要一个状态管理方案,方便管理不同组件间的数据,及时地更新数据
而Flux思想中的Store层,切合了这个问题
主要区别是Flux的派发器dispatcher,Redux认为使用派发器就得增加事件订阅/发布的规则,倒不如直接用函数调用的方式来得实在,简单而统一,所以就将处理action的任务交给了store层(直接调用这个对象的dispatch方法)
在大多数情况下,Redux是不需要用的,如UI层非常简单,没有太多互动的
用户的使用方式非常简单
用户之间没有协作
不需要与服务器大量交互,也没有使用 WebSocket
视图层(View)只从单一来源获取数据
而在多交互,多数据源的时候可以考虑使用
用户的使用方式复杂
不同身份的用户有不同的使用方式(比如普通用户和管理员)
多个用户之间可以协作与服务器大量交互,或者使用了WebSocketView
要从多个来源获取数据
在需要管理复杂组件状态的时候,可以考虑使用
某个组件的状态,需要共享某个状态
需要在任何地方都可以拿到一个组件
需要改变全局状态一个组件
需要改变另一个组件的状态
这里先纯粹讲Redux,毕竟它和React是没啥关系的
首先是环境配置,基本上都会使用ES6,所以Babel的支持是必须的
然后是Redux的支持,如果使用Webpack打包编译,就用npm安装个redux包
这里采用直接在浏览器引入的方式,使用 这个库
最后build里的为demo代码用babel编译之后的es5文件
在全局之中有Redux这个对象,取其中的几个属性来用
3.1 Redux需要一个store来存放数据
这个store就由createStore创建
3.2 需要定义各个操作是什么,即action
通常来说它是一个对象,包含type属性表示是什么操作,以及其他属性携带一些数据
它可能长这样子,建议是遵循官方的 一些规范
我们不止会传type,还会传一些值,如果传不同的值就let一次就太冗杂了,一般来说就会用一个方法代替
3.3 需要定义怎么处理操作,在redux中它被称作reducer
为什么把这种操作称作reducer呢
redux引入了JS数组reduce方法的思想,JS的reduce长这样
当然了,只是看起来像,实际上差别挺大的,redux的reducer看起来像这样
它是一个函数,接收两个参数,第一个参数为数据(即某个状态state),第二个参数为action操作对象
为了切合store中数据与view中视图是一一对应的,reducer规定需始终返回新的state数据,不能直接在原有state中修改;
并且,建议在匹配不到action的时候始终返回默认的state状态,且建议在第一个参数中初始化默认的state值
3.4 在创建store的时候绑定reducer
redux基本上把所有操作都给了store,所以大部分方法都是用store来调用的
其实,你也可以认为Flux中的派发器(dispatcher)就是在里面自动绑定的
如上,创建store的时候传入reducer,可以接收第二个参数表示reducer使用的默认值
3.5 视图发出action动作
在某个时刻,发出了这些动作
3.6 使用store.getState()获取store中的数据
3.7 动作发出后,reducer匹配动作更新store中的数据,视图view层使用subscribe监听数据的改变
来看一下完整的代码
注意上面createStore中第二个参数是用于Redux DevTool的配置,即这个东西
使用这个工具可以便于开发
看看上面代码的输出
初始获取到的值为0,两次action后分别更新相关的数据状态。如果加上初始默认值10
3.8 使用多个reducer时,使用Redux的combineReducers方法
action当然不会只是up,可能是down,这时可以直接用switch语句切换;但如果action不是这里增减的操作,放在一起就有点乱套了
所以需要定义多个reducer,但createStore方法只接收一个reducer,所以就需要整合多个reducer为一个,再统一传入
它看起来像这样
接收一个reducer组成的对象,属性表示该reducer对应的state名(如state.upReducer),值表示这个reducer
当然,这个方法我们可以自己定义,看起来是这样
其实就是遍历reducer组,返回一个统一的新的reducer,且新的reducer中返回一个新的state
看看Redux中的实现,完整多了
View Code
加上个down操作,来看看完整代码
给reducer设个初始值,要注意的是,这个初始值是针对整个state的
如果只有一个reducer,那reducer函数中的state就是这个state
如果用combineReducer整理了多个reducer,那各个reducer函数中的state是整个state中的reducer同名属性的值
如上代码定义了初始值,看看执行结果
接下来的问题主要有三个:
如何将store中的数据同步给React组件
如何让React组件调用Redux的dispatch方法
上面两个
直接点,就是在React组件中调用Redux的subscribe方法来监听同步数据,再在某个时机调用dispatch即可
但官方并不建议使用subscribe这个方法,而是建议使用封装好的另一个库
React-Redux
然后在全局window下可以获取到这个对象,取一些用到的属性如
组件定义了一个action,即点一次执行一次增长(increase)函数,里面调用dispatch方法发出action,先看看其他东西
它看起来像是这样子
可以把它看成是一个中间件,首先接收几个参数完成配置阶段,然后传入React组件,包装成一个新的东东(它并没有直接修改Increase组件)
而一般来说,一般来说会传入两个参数(支持四个参数),顾名思义:
第一个参数(类型为函数)
如果不传或置入undefined或null,则表示不需要进行数据更新;否则表示将store中的数据通过props的形式传给React组件
第二个参数(类型为函数)
如果不传或置入undefined或null,则表示将React-Redux中默认的dispatch方法传给React组件;否则表示将redux中的dispatch发出动作通过props的形式传给React组件
注意到上面的React组件代码中,通过props获取到了dispatch方法,然后自行发出动作
如果要这样做,mapDispatchToProps 这里就不传入了,即
用回常见的方式,在React组件中改一改,直接从props中获取某个dispatch的发出动作
同时修改两个都传入
mapStateToProps 中第一个参数为一个对象,表示store中整体的state数据
当然,第一个参数也可以为函数,也可以接收第二个参数,表示自身拥有的属性(ownProps),具体可以看API
最后它返回了一个新的对象,表示要传给React组件的数据
与mapStateToProps类似,mapDispatchToProps 也可以接收两个参数,
第一个表示当前的dispatch方法,第二个表示自身拥有的 propsDispatch方法(即类似省略参数传给字组件的默认dispatch方法)
最后它返回了一个action发出动作(一个函数),传给React组件调用
使用ReactRedux提供的<Provider />,它看起来是这样
使用store属性传入上面的store对象
在children中置入有connect生成的APP组件,注意这里只能包含一个父层
如果向其中传入属性,如
那么,mapStateToProps中的第二参数ownProps就可以拥有这个name属性
完整代码
View Code
看一下运行结果
多个组件的使用类似单个,只不过需要注意两点
<Provider />中只能包含一个父级
mapStateToProps中第一个参数是指整体store中的数据
下面以两个组件的栗子,看看如何实现
4.7.1 首先定义两个组件,一增一减
4.7.2 定义对应的两个reducer
4.7.3 创建store
4.7.4 创建连接两个组件对应的两个mapStateToProps 和 mapDispatchToProps
注意state为整个store中的state,取值要取各reducer同名属性如 state.couterUp
4.7.5 各组件用connect包装
4.7.6 置入<Provider />中
注意只能有一个父级,所以得先简单包装一层
完整代码
View Code
Good ! 完成了,看看结果
4.7.7 再看connect方法剩余的两个参数
connect方法接收可接收四个参数,上面已经谈到了前两个,后两个不那么常用
第三个参数,这里不多说:[
第四个参数:[
这个options中有如下几个属性:
pure: true(默认)|false 表示是否在调用connect前三个参数的函数方法之前先检测前后store中的值是否改变,改变才调用,否则不调用
areStatesEqual: 函数,当pure为true时调用这个函数检测是否相等,返回true|false表示是否相等,默认以严格相等===来判断前后值是否相等
areOwnPropsEqual: 类似areStatesEqual,只不过它默认是用不严格相等==来判断
areStatePropsEqual: 类似areStatesEqual,只不过它默认是用不严格相等==来判断
areMergedPropsEqual: 类似areStatesEqual,只不过它默认是用不严格相等==来判断
来看个例子,现在要手动的定义这个参数
针对Decrease,在减1时直接返回了false
可以看到,减1的操作并没有传给Decrease组件,页面没有更新
顺便看看有connect包装后的组件
4.7.8 在React-Redux中使用异步
因Redux中操作的执行是同步的,如果要实现异步,比如某个操作用来发个异步请求获取数据,就得引入中间件来处理这种特殊的操作
即这个操作不再是普通的值,而是一个函数(如Promise异步),通过中间件的处理,让Redux能够解析
先修改上面的栗子,在Increase组件中不再是每次增加1,而是根据action中的value来指定,比如
这里定义了value是10,但假如value的值得由一个异步的请求才得出呢,要如何放进去
使用Redux提供的中间件applyMiddleware
这只是基础的中间件apply函数,它帮助Redux将中间件包装
现在来模拟一个异步请求
可一看到,dispatch中的action是一个函数(这个调用返回的还是一个函数),而Redux默认只支持对象格式的action,所以这样会报错
这里的fetchIncreaseValue看起来像这样
而请求后台后返回值
可以看到,异步获取数据之后才执行dispatch发出操作,这里需要一个dispatch关键字
为了拿到这个关键字,得和thunkMiddleware搭配使用(让这个方法能够在内层函数中使用),当然,你也可以再搭配其他中间件
如果使用Webpack打包,就安装好 redux-thunk 包再 import 进来
这里直接引入到浏览器中,引入这个库,然后直接使用(注意这里没有
{} )
然后在创建store的时候,传给redux的applyMiddleware即可
官方给的例子太复杂了,不过还是去看看吧,我这里抽出了主要的部分,
先来看看结果
使用这个Redux Dev Tool就得在createStore中配上最后一个参数,而createStore自身的某个参数又能给reducer设置初始值,且applyMiddleware也是在参数中定义
所以要注意的是:
如果用了这个Redux Dev Tool,就要保证applyMiddleware在第三个参数
类似这样省略第二个初始值参数,是会报错的
把注释去掉,放上一个空的初始即可,或者不用这个Dev Tool
可以去看看其他的Dev Tool
假设你会一些ES6、会一些React、有看过Redux相关的文章,这篇入门小文应该能帮助你理一下相关的知识
一般来说,推荐使用 ES6+React+Webpack 的开发模式,但Webpack需要配置一些东西,你可以先略过,本文不需要Webpack基础
入门,只是一些基础概念和用法的整理,更完整的内容推荐去看看文档,英文,中文
(不过我个人认为,官方文档的例子相对来说太复杂了,很难让新手马上抓住重点)
(官方的例子正统且联系业务,不同类型的操作或数据放在不同文件中,很规范,但也很绕,所以本文使用的例子非常简单,且直接放在一个文件中 以便于理解)
搭飞机前往:
Flux思想、Redux基本概念、Redux的使用、Redux在React中的使用(同步)、Redux在React中的使用(异步,使用中间件)
一、Flux
Flux是一种概念思想,或者说是一种应用架构根据它的概念,一个应用中的数据流动应是单向的,且应用中的所有数据保存在一个位置,数据变化时保证视图也同步变化,保证了数据和视图的状态是一一对应起来的
此应用应该分为四层:
view层:应用的视图,页面的(数据)展示
action层:(视图)发出的某些动作,比如点击事件
dispatcher层:派发器,接收action并处理这些动作,更新数据
store层:存放应用的数据,数据更新后,提醒view层更新视图
它的概念思想可能一时半会理解不了,没关系,过段时间就好了
二、Redux
上面说到,Flux只是一个思想,我们可以根据这个思想来自己实现出一个技术方案,来解决问题是要解决什么问题呢?
在使用React的过程中,在组件间通信的处理上我们用了回调的方式,如果组件层级很深,不同组件间的数据交流就会导致回调及其触发的函数非常多,代码冗杂
需要一个状态管理方案,方便管理不同组件间的数据,及时地更新数据
而Flux思想中的Store层,切合了这个问题
1. 什么是Redux
Redux是受Flux启发实现的一个技术方案,可以认为它是Flux的产物,但它并没有沿用Flux所有的思想主要区别是Flux的派发器dispatcher,Redux认为使用派发器就得增加事件订阅/发布的规则,倒不如直接用函数调用的方式来得实在,简单而统一,所以就将处理action的任务交给了store层(直接调用这个对象的dispatch方法)
2. 什么时候用Redux
Redux说简单简单,因为也就几个API,理解好概念就好用了;说复杂也复杂,因为它将一个应用分成了不同部分(action、处理action、store数据等),在正规的项目中是推荐将各部分区分到不同文件中的(如官方的例子),文件数量很多可能会比较难管理,当然,细粒化了也就减少了耦合度。最后还要加个操作把Redux的数据更新给React组件(如果用了React)在大多数情况下,Redux是不需要用的,如UI层非常简单,没有太多互动的
用户的使用方式非常简单
用户之间没有协作
不需要与服务器大量交互,也没有使用 WebSocket
视图层(View)只从单一来源获取数据
而在多交互,多数据源的时候可以考虑使用
用户的使用方式复杂
不同身份的用户有不同的使用方式(比如普通用户和管理员)
多个用户之间可以协作与服务器大量交互,或者使用了WebSocketView
要从多个来源获取数据
在需要管理复杂组件状态的时候,可以考虑使用
某个组件的状态,需要共享某个状态
需要在任何地方都可以拿到一个组件
需要改变全局状态一个组件
需要改变另一个组件的状态
3. 开始用Redux
上面讲了那么多字,还是看代码来得实在这里先纯粹讲Redux,毕竟它和React是没啥关系的
首先是环境配置,基本上都会使用ES6,所以Babel的支持是必须的
然后是Redux的支持,如果使用Webpack打包编译,就用npm安装个redux包
这里采用直接在浏览器引入的方式,使用 这个库
<body> <div id="box"></div> <script type="text/javascript" src="../lib/react.js"></script> <script type="text/javascript" src="../lib/react-dom.js"></script> <script type="text/javascript" src="../lib/redux.min.js"></script> <script type="text/javascript" src="../build/reduxStart.js"></script> </body>
最后build里的为demo代码用babel编译之后的es5文件
在全局之中有Redux这个对象,取其中的几个属性来用
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;
3.1 Redux需要一个store来存放数据
这个store就由createStore创建
3.2 需要定义各个操作是什么,即action
通常来说它是一个对象,包含type属性表示是什么操作,以及其他属性携带一些数据
它可能长这样子,建议是遵循官方的 一些规范
let upAction = { type: 'UP' };
我们不止会传type,还会传一些值,如果传不同的值就let一次就太冗杂了,一般来说就会用一个方法代替
let upAction = function(value) { return { type: 'up', value }; };
3.3 需要定义怎么处理操作,在redux中它被称作reducer
为什么把这种操作称作reducer呢
redux引入了JS数组reduce方法的思想,JS的reduce长这样
var arr = [1, 2, 3, 4]; var num = arr.reduce((a, b) => { return a + b; }); num // 10 var num = arr.reduce((a, b) => { return a + b; }, 5); num // 15
当然了,只是看起来像,实际上差别挺大的,redux的reducer看起来像这样
let upReducer = function(state = 0, action) { switch (action.type) { case 'up': return state + action.value; default: return state; } };
它是一个函数,接收两个参数,第一个参数为数据(即某个状态state),第二个参数为action操作对象
为了切合store中数据与view中视图是一一对应的,reducer规定需始终返回新的state数据,不能直接在原有state中修改;
并且,建议在匹配不到action的时候始终返回默认的state状态,且建议在第一个参数中初始化默认的state值
3.4 在创建store的时候绑定reducer
redux基本上把所有操作都给了store,所以大部分方法都是用store来调用的
其实,你也可以认为Flux中的派发器(dispatcher)就是在里面自动绑定的
let store = createStore(reducer); // let store = createStore(reducer, 10);
如上,创建store的时候传入reducer,可以接收第二个参数表示reducer使用的默认值
3.5 视图发出action动作
在某个时刻,发出了这些动作
store.dispatch(upAction(10)); store.dispatch(upAction(100));
3.6 使用store.getState()获取store中的数据
3.7 动作发出后,reducer匹配动作更新store中的数据,视图view层使用subscribe监听数据的改变
store.subscribe(() => console.log(store.getState()));
来看一下完整的代码
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;
let upAction = function(value) {
return {
type: 'up',
value
};
}
let upReducer = function(state = 0, action) { switch (action.type) { case 'up': return state + action.value; default: return state; } };
let store = createStore(upReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
console.log(store.getState());
store.subscribe(() => console.log(store.getState()));
store.dispatch(upAction(10)); store.dispatch(upAction(100));
注意上面createStore中第二个参数是用于Redux DevTool的配置,即这个东西
使用这个工具可以便于开发
看看上面代码的输出
初始获取到的值为0,两次action后分别更新相关的数据状态。如果加上初始默认值10
let store = createStore(upReducer, 10, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
3.8 使用多个reducer时,使用Redux的combineReducers方法
action当然不会只是up,可能是down,这时可以直接用switch语句切换;但如果action不是这里增减的操作,放在一起就有点乱套了
所以需要定义多个reducer,但createStore方法只接收一个reducer,所以就需要整合多个reducer为一个,再统一传入
它看起来像这样
let reducer = combineReducers({upReducer, downReducer}); // let reducer = combineReducers({ // upReducer: upReducer, // downReducer: downReducer // });
接收一个reducer组成的对象,属性表示该reducer对应的state名(如state.upReducer),值表示这个reducer
当然,这个方法我们可以自己定义,看起来是这样
let myCombineReducers = function(reducerObj) { let newState = {}; return function(state = {}, action) { for (let item in reducerObj) { newState[item] = reducerObj[item](state[item], action); } return newState; } };
其实就是遍历reducer组,返回一个统一的新的reducer,且新的reducer中返回一个新的state
看看Redux中的实现,完整多了
1 function combineReducers(reducers) { 2 var reducerKeys = Object.keys(reducers); 3 var finalReducers = {}; 4 for (var i = 0; i < reducerKeys.length; i++) { 5 var key = reducerKeys[i]; 6 7 if (true) { 8 if (typeof reducers[key] === 'undefined') { 9 (0, _warning2['default'])('No reducer provided for key "' + key + '"'); 10 } 11 } 12 13 if (typeof reducers[key] === 'function') { 14 finalReducers[key] = reducers[key]; 15 } 16 } 17 var finalReducerKeys = Object.keys(finalReducers); 18 19 if (true) { 20 var unexpectedKeyCache = {}; 21 } 22 23 var sanityError; 24 try { 25 assertReducerSanity(finalReducers); 26 } catch (e) { 27 sanityError = e; 28 } 29 30 return function combination() { 31 var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 32 var action = arguments[1]; 33 34 if (sanityError) { 35 throw sanityError; 36 } 37 38 if (true) { 39 var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache); 40 if (warningMessage) { 41 (0, _warning2['default'])(warningMessage); 42 } 43 } 44 45 var hasChanged = false; 46 var nextState = {}; 47 for (var i = 0; i < finalReducerKeys.length; i++) { 48 var key = finalReducerKeys[i]; 49 var reducer = finalReducers[key]; 50 var previousStateForKey = state[key]; 51 var nextStateForKey = reducer(previousStateForKey, action); 52 if (typeof nextStateForKey === 'undefined') { 53 var errorMessage = getUndefinedStateErrorMessage(key, action); 54 throw new Error(errorMessage); 55 } 56 nextState[key] = nextStateForKey; 57 hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 58 } 59 return hasChanged ? nextState : state; 60 }; 61 }
View Code
加上个down操作,来看看完整代码
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;
let upAction = function(value) {
return {
type: 'up',
value
};
}
let downAction = function(value) {
return {
type: 'down',
value
};
}
let upReducer = function(state = 0, action) { switch (action.type) { case 'up': return state + action.value; default: return state; } };
let downReducer = function(state = 0, action) {
switch (action.type) {
case 'down':
return state - action.value;
default:
return state;
}
};
let reducer = combineReducers({upReducer, downReducer});
let store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
console.log(store.getState());
store.subscribe(() => console.log(store.getState()));
store.dispatch(upAction(10)); store.dispatch(upAction(100));
store.dispatch(downAction(10));
store.dispatch(downAction(100));
给reducer设个初始值,要注意的是,这个初始值是针对整个state的
如果只有一个reducer,那reducer函数中的state就是这个state
如果用combineReducer整理了多个reducer,那各个reducer函数中的state是整个state中的reducer同名属性的值
let reducer = combineReducers({upReducer, downReducer}); let store = createStore( reducer, {upReducer: 10, downReducer: 10}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
如上代码定义了初始值,看看执行结果
四. 在React中使用Redux
Redux是一个独立的技术方案,我们将它运用到React项目中接下来的问题主要有三个:
如何将store中的数据同步给React组件
如何让React组件调用Redux的dispatch方法
上面两个
直接点,就是在React组件中调用Redux的subscribe方法来监听同步数据,再在某个时机调用dispatch即可
但官方并不建议使用subscribe这个方法,而是建议使用封装好的另一个库
React-Redux
4.1 引入库
与引入Redux类似,你可以使用Webpack引入包或浏览器直接引入这个库然后在全局window下可以获取到这个对象,取一些用到的属性如
let {Provider, connect} = ReactRedux;
4.2 先定义一个有增长操作的React组件
class Increase extends Component { constructor(props) { super(props); } componentWillReceiveProps(nextProps) { console.log(nextProps); } increase() { let {dispatch} = this.props; dispatch({ type: 'up' }); } render() { return <p onClick={this.increase.bind(this)}>increase: {this.props.number}</p> } }
组件定义了一个action,即点一次执行一次增长(increase)函数,里面调用dispatch方法发出action,先看看其他东西
4.3 定义一个reducer
function couterUp(state = {number: 100}, action) { switch (action.type) { case 'up': return { number: state.number + 1 }; default: return state; } }
4.4 创建一个store
let store = createStore(couterUp, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
4.4 使用ReactRedux的connect方法
要将Redux中的数据同步给React,需要用到这个方法它看起来像是这样子
let APP = connect( mapStateToProps, mapDispatchToProps )(Increase);
可以把它看成是一个中间件,首先接收几个参数完成配置阶段,然后传入React组件,包装成一个新的东东(它并没有直接修改Increase组件)
而一般来说,一般来说会传入两个参数(支持四个参数),顾名思义:
第一个参数(类型为函数)
如果不传或置入undefined或null,则表示不需要进行数据更新;否则表示将store中的数据通过props的形式传给React组件
第二个参数(类型为函数)
如果不传或置入undefined或null,则表示将React-Redux中默认的dispatch方法传给React组件;否则表示将redux中的dispatch发出动作通过props的形式传给React组件
注意到上面的React组件代码中,通过props获取到了dispatch方法,然后自行发出动作
increase() { let {dispatch} = this.props; dispatch({ type: 'up' }); }
如果要这样做,mapDispatchToProps 这里就不传入了,即
let APP = connect( mapStateToProps )(Increase);
用回常见的方式,在React组件中改一改,直接从props中获取某个dispatch的发出动作
render() { return <p onClick={this.props.increase}>increase: {this.props.number}</p> }
同时修改两个都传入
let APP = connect( mapStateToProps, mapDispatchToProps )(Increase);
4.5 mapStateToProps 和 mapDispatchToProps
我们定义一下这两个参数(函数),它看起来长这样function mapStateToProps(state) { return { number: state.number }; } function mapDispatchToProps(dispatch) { return { increase: () => dispatch({ type: 'up' }) }; }
mapStateToProps 中第一个参数为一个对象,表示store中整体的state数据
当然,第一个参数也可以为函数,也可以接收第二个参数,表示自身拥有的属性(ownProps),具体可以看API
最后它返回了一个新的对象,表示要传给React组件的数据
与mapStateToProps类似,mapDispatchToProps 也可以接收两个参数,
第一个表示当前的dispatch方法,第二个表示自身拥有的 propsDispatch方法(即类似省略参数传给字组件的默认dispatch方法)
最后它返回了一个action发出动作(一个函数),传给React组件调用
4.6 使用Provider
基本好了,只差一步:将connect包装组件后生成的新东东与实际页面联系起来使用ReactRedux提供的<Provider />,它看起来是这样
render( <Provider store={store}> <APP /> </Provider>, document.getElementById('box') );
使用store属性传入上面的store对象
在children中置入有connect生成的APP组件,注意这里只能包含一个父层
如果向其中传入属性,如
<APP name="app" />
那么,mapStateToProps中的第二参数ownProps就可以拥有这个name属性
完整代码
let {Component} = React; let {render} = ReactDOM; let {createStore, combineReducers} = Redux;
let {Provider, connect} = ReactRedux;
class Increase extends Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
console.log(nextProps);
}
render() { return <p onClick={this.props.increase}>increase: {this.props.number}</p> }
}
function couterUp(state = {number: 100}, action) { switch (action.type) { case 'up': return { number: state.number + 1 }; default: return state; } }
function mapStateToProps(state) { return { number: state.number }; } function mapDispatchToProps(dispatch) { return { increase: () => dispatch({ type: 'up' }) }; }
let APP = connect( mapStateToProps, mapDispatchToProps )(Increase);
let store = createStore(couterUp, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
render( <Provider store={store}> <APP /> </Provider>, document.getElementById('box') );
View Code
看一下运行结果
4.7 多个React组件中的使用
上面说的是单个React组件中的使用,实际使用中会有多个组件多个组件的使用类似单个,只不过需要注意两点
<Provider />中只能包含一个父级
mapStateToProps中第一个参数是指整体store中的数据
下面以两个组件的栗子,看看如何实现
4.7.1 首先定义两个组件,一增一减
class Increase extends Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
console.log('increase: ', nextProps);
}
render() { return <p onClick={this.props.increase}>increase: {this.props.number}</p> }
}
class Decrease extends Component {
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
console.log('decrease: ', nextProps);
}
render() {
return <p onClick={this.props.decrease}>decrease: {this.props.number}</p>
}
}
4.7.2 定义对应的两个reducer
function couterUp(state = {number: 100}, action) { switch (action.type) { case 'up': return { number: state.number + 1 }; default: return state; } }
function counterDown(state = {number: -100}, action) {
switch (action.type) {
case 'down':
return {
number: state.number - 1
};
default:
return state;
}
}
4.7.3 创建store
let couter = combineReducers({ couterUp, counterDown }); let store = createStore( couter, {couterUp: {number: 10}}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
4.7.4 创建连接两个组件对应的两个mapStateToProps 和 mapDispatchToProps
注意state为整个store中的state,取值要取各reducer同名属性如 state.couterUp
function mapStateToProps_1(state) { return { number: state.couterUp.number }; } function mapDispatchToProps_1(dispatch) { return { increase: () => dispatch({ type: 'up' }) }; } function mapStateToProps_2(state, props) { return { number: state.counterDown.number }; } function mapDispatchToProps_2(dispatch) { return { decrease: () => dispatch({ type: 'down' }) }; }
4.7.5 各组件用connect包装
let APP_1 = connect( mapStateToProps_1, mapDispatchToProps_1 )(Increase); let APP_2 = connect( mapStateToProps_2, mapDispatchToProps_2 )(Decrease);
4.7.6 置入<Provider />中
注意只能有一个父级,所以得先简单包装一层
let APP = () => (
<div>
<APP_1 />
<APP_2 name="APP_2"/>
</div>
);
render( <Provider store={store}> <APP /> </Provider>, document.getElementById('box') );
完整代码
1 let {Component} = React; 2 let {render} = ReactDOM; 3 let {createStore, combineReducers} = Redux; 4 let {Provider, connect} = ReactRedux; 5 6 class Increase extends Component { 7 constructor(props) { 8 super(props); 9 } 10 11 componentWillReceiveProps(nextProps) { 12 console.log('increase: ', nextProps); 13 } 14 15 render() { 16 return <p onClick={this.props.increase}>increase: {this.props.number}</p> 17 } 18 } 19 20 class Decrease extends Component { 21 constructor(props) { 22 super(props); 23 } 24 25 componentWillReceiveProps(nextProps) { 26 console.log('decrease: ', nextProps); 27 } 28 29 render() { 30 return <p onClick={this.props.decrease}>decrease: {this.props.number}</p> 31 } 32 } 33 34 function couterUp(state = {number: 100}, action) { 35 switch (action.type) { 36 case 'up': 37 return { 38 number: state.number + 1 39 }; 40 default: 41 return state; 42 } 43 } 44 45 function counterDown(state = {number: -100}, action) { 46 switch (action.type) { 47 case 'down': 48 return { 49 number: state.number - 1 50 }; 51 default: 52 return state; 53 } 54 } 55 56 function mapStateToProps_1(state) { 57 return { 58 number: state.couterUp.number 59 }; 60 } 61 62 function mapDispatchToProps_1(dispatch) { 63 return { 64 increase: () => dispatch({ 65 type: 'up' 66 }) 67 }; 68 } 69 70 function mapStateToProps_2(state, props) { 71 return { 72 number: state.counterDown.number 73 }; 74 } 75 76 function mapDispatchToProps_2(dispatch) { 77 return { 78 decrease: () => dispatch({ 79 type: 'down' 80 }) 81 }; 82 } 83 84 let couter = combineReducers({ 85 couterUp, 86 counterDown 87 }); 88 89 let store = createStore( 90 couter, 91 {couterUp: {number: 10}}, 92 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 93 ); 94 95 96 let APP_1 = connect( 97 mapStateToProps_1, 98 mapDispatchToProps_1 99 )(Increase); 100 101 let APP_2 = connect( 102 mapStateToProps_2, 103 mapDispatchToProps_2 104 )(Decrease); 105 106 let APP = () => ( 107 <div> 108 <APP_1 /> 109 <APP_2 name="APP_2"/> 110 </div> 111 ); 112 113 render( 114 <Provider store={store}> 115 <APP /> 116 </Provider>, 117 document.getElementById('box') 118 );
View Code
Good ! 完成了,看看结果
4.7.7 再看connect方法剩余的两个参数
connect方法接收可接收四个参数,上面已经谈到了前两个,后两个不那么常用
第三个参数,这里不多说:[
mergeProps(stateProps, dispatchProps, ownProps): props] (Function)
第四个参数:[
options] (Object)
这个options中有如下几个属性:
pure: true(默认)|false 表示是否在调用connect前三个参数的函数方法之前先检测前后store中的值是否改变,改变才调用,否则不调用
areStatesEqual: 函数,当pure为true时调用这个函数检测是否相等,返回true|false表示是否相等,默认以严格相等===来判断前后值是否相等
areOwnPropsEqual: 类似areStatesEqual,只不过它默认是用不严格相等==来判断
areStatePropsEqual: 类似areStatesEqual,只不过它默认是用不严格相等==来判断
areMergedPropsEqual: 类似areStatesEqual,只不过它默认是用不严格相等==来判断
来看个例子,现在要手动的定义这个参数
针对Decrease,在减1时直接返回了false
let APP_2 = connect( mapStateToProps_2, mapDispatchToProps_2, null, { pure: true, areStatesEqual: (next, prev) => { console.log(next.counterDown, prev.counterDown); return next.counterDown.number < prev.counterDown.number; } } )(Decrease);
可以看到,减1的操作并没有传给Decrease组件,页面没有更新
顺便看看有connect包装后的组件
4.7.8 在React-Redux中使用异步
因Redux中操作的执行是同步的,如果要实现异步,比如某个操作用来发个异步请求获取数据,就得引入中间件来处理这种特殊的操作
即这个操作不再是普通的值,而是一个函数(如Promise异步),通过中间件的处理,让Redux能够解析
先修改上面的栗子,在Increase组件中不再是每次增加1,而是根据action中的value来指定,比如
function mapDispatchToProps_1(dispatch) { return { increase: () => dispatch({ type: 'up', value: 10 }) }; }
function couterUp(state = {number: 100}, action) { switch (action.type) { case 'up': return { // number: state.number + 1 number: state.number + action.value }; default: return state; } }
这里定义了value是10,但假如value的值得由一个异步的请求才得出呢,要如何放进去
使用Redux提供的中间件applyMiddleware
let {createStore, combineReducers, applyMiddleware} = Redux;
这只是基础的中间件apply函数,它帮助Redux将中间件包装
现在来模拟一个异步请求
function mapDispatchToProps_1(dispatch) { return { // increase: () => dispatch({ // type: 'up', // value: 10 // }) increase: () => dispatch(fetchIncreaseValue('redux-ajaxTest.php')) }; }
可一看到,dispatch中的action是一个函数(这个调用返回的还是一个函数),而Redux默认只支持对象格式的action,所以这样会报错
这里的fetchIncreaseValue看起来像这样
function fetchIncreaseValue(url) { return function(dispatch) { return $.get(url).then(re => { re = JSON.parse(re); console.log(re); dispatch({ type: 'up', value: re.value }); }) } }
而请求后台后返回值
<?php echo json_encode(array('value' => 100)); ?>
可以看到,异步获取数据之后才执行dispatch发出操作,这里需要一个dispatch关键字
为了拿到这个关键字,得和thunkMiddleware搭配使用(让这个方法能够在内层函数中使用),当然,你也可以再搭配其他中间件
如果使用Webpack打包,就安装好 redux-thunk 包再 import 进来
这里直接引入到浏览器中,引入这个库,然后直接使用(注意这里没有
{} )
let thunkMiddleware = window.ReduxThunk.default;
然后在创建store的时候,传给redux的applyMiddleware即可
let store = createStore( couter, {couterUp: {number: 10}}, applyMiddleware(thunkMiddleware), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
官方给的例子太复杂了,不过还是去看看吧,我这里抽出了主要的部分,
先来看看结果
使用这个Redux Dev Tool就得在createStore中配上最后一个参数,而createStore自身的某个参数又能给reducer设置初始值,且applyMiddleware也是在参数中定义
所以要注意的是:
如果用了这个Redux Dev Tool,就要保证applyMiddleware在第三个参数
let store = createStore( couter, // {}, applyMiddleware(thunkMiddleware), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
类似这样省略第二个初始值参数,是会报错的
把注释去掉,放上一个空的初始即可,或者不用这个Dev Tool
let store = createStore( couter, applyMiddleware(thunkMiddleware) );
可以去看看其他的Dev Tool
相关文章推荐
- Flux --> Redux --> Redux React 入门 基础实例使用
- Flux --> Redux --> Redux React 入门 基础实例教程
- Flux --> Redux --> Redux React 基础实例教程
- 【LaTeX排版】LaTeX使用--入门基础<二>
- 【LaTeX排版】LaTeX使用--入门基础<一>
- javascript-replace()基础,一次完成将"<,>"替换"<>"实例
- ibatis学习笔记(四)>>>>>>>ibatis使用实例
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<jsp>(二十六)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<SQL_Server_视图_函数_存储过程_触发器等>(二十三)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<SQL_Server>(二十一)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<JDBC>(二十四)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<servlet_文件下载>(二十七)
- 斯坦福大学公开课:iPhone开发教程2010年冬> 基础入门
- [C#基础教程]C#泛型集合—Dictionary<K,V>使用技巧
- 实时开发框架Meteor基础入门系列<零>--简介
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<数据库连接配置>(二十八)
- java 从零开始,学习笔记之基础入门<线程及实例分析>(十九)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<Oracle_查询>(三十四)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<Oracle_基础>(三十三)
- 斯坦福大学公开课:iPhone开发教程2010年冬> 各种基础的类,功能,对象和实例的介绍