首先要知道,Redux只是一种MVC的机制,它可以运行在任何的前端框架中。react-redux则是对Redux的核心模块(如store)进行封装,并加入了一些有利于react开发的模块,这样就可以更便捷的在react中运用redux。
本篇将详细介绍react-redux开发中的常用模块。在阅读本文前,假设您已经基本理解了Redux的运行机制,若对其机制还不是很清楚,请参考上一篇《Flux、Redux到react-redux发展衍变之Redux解读》
下面是两个关于react-redux应用的demo,可以作为本篇知识的参考案例:
TODOS:https://github.com/smallH/react-redux-todomvc-demo.git
购物车:https://github.com/smallH/react-redux-shoppingcart-demo.git
结合上篇,运行一下上面两个例子,就可以学会 'react-redux' 用法,本篇不再累赘,这里主要介绍其一些注意的细节和插件。
mapDispatchToProps的多种入参方式
mapDispatchToProps的作用是:告诉组件所需的actions函数并把它们作为入参props传递到组件里,供组件使用。
大多数情况下使用标准的引用方法如下:
// actions.js
export const add = () => ({ type: 'ADD' })
// Link.js
class Link extends React.Component {
render() {
const {addAction} = this.props;
return(
<div onClick={addAction}>增加一条</div>
)
}
}
export default Link
// ContainersLink.js 给Link组件定义一个addAction入参事件:增加一条记录
import { connect } from 'react-redux'
import { add } from '../actions'
import Link from '../components/Link'
const mapDispatchToProps = (dispatch, ownProps) => ({
addAction: () => {
dispatch(add())
}
})
export default connect(null, mapDispatchToProps)(Link)
有没有发现代码有点多,于是,我们可以优化为:
// Link.js
class Link extends React.Component {
render() {
const {add} = this.props;
return(
<div onClick={add}>增加一条</div>
)
}
}
export default Link
// ContainersLink.js
import { connect } from 'react-redux'
import { add } from '../actions'
export default connect(null, {add})(Link)
两种用法效果是一样的。另外,当actions.js里需要引用的函数较多时,可以写成:
// Link
class Link extends React.Component {
render() {
const { add } = this.props;
return(
<div onClick={ add }>增加</div>
)
}
}
export default Link
// ContainersLink.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as AllActions from '../actions'
export default connect(null, AllActions)(Link)
或者如果不想通过connect,则可以使用bindActionCreators(actions, dispatch)。
classnames 实现样式管理
react中样式管理的方式有很多,有很多项目习惯直接在每个组件最底下定义常量样式文件style,但这种方法有一定局限性。本篇中介绍通过classnames实现了对样式的条件引用,对于一些现实/隐藏组件的操作非常方便。
模块安装:
npm install classnames -S
用法有以下几种:
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
如给div增加active样式,代码可以是:
import classnames from 'classnames'
// 以下两种写法效果是一样的
let bool = true;
<div className={classnames({ "active": bool})}></div>
<div className={classnames({ "active": true })}></div>
上例中,可以用过计算bool值实现动态显示/隐藏组件。
combineReducers 合并多个reduce
随着应用变得复杂,需要对reducers函数进行拆分,拆分后的每一块独立负责管理state的一部分。combineReducers函数的作用是:把由多个不同reducer函数作为value,合并成一个最终的reducers函数,然后就可以对这个reducers调用createStore。合并后的reducers把各个子reduer返回的结果合并成一个state。state对象的结构由传入的多个reducer的key决定。如state 对象的结构会是这样:
{
reducer1: ...
reducer2: ...
}
如多个reducer合并实现:
// todos.js
const todos = (state = {}, action) => {
switch(action.type) {
case ActionTypes.UPDATE:
console.log("todo update!");
return state
default:
return state
}
}
// visibilityFilter.js
const visibilityFilter = (state = {}, action) => {
switch(action.type) {
case ActionTypes.UPDATE:
console.log("visibilityFilter update!");
return state
default:
return state
}
}
// 合并reducer
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
const rootReducer = combineReducers({
todos,
visibilityFilter
})
export default rootReducer
这里要注意,合并后reducers里的子reducer依然会遍历执行所有触发的actions函数。如当触发ActionTypes.UPDATE时,上例中会打印todo update! 和 visibilityFilter update!
除此之外,在reducers里的子reducer也允许二次合并,如把todos修改如下:
// todos
const todo_child1 = (state = [], action) => {}
const todo_child2 = (state = [], action) => {}
const todos = combineReducers({
todo_child1,
todo_child2
})
export default todos
这时state返回的是对象是 {{todo_child1, todo_child2}, visibilityFilter}
reselect 缓存机制,算法优化
reselect 模块使用到了缓存机制,Selector可以计算衍生的数据,可以让Redux做到存储尽可能少的state,在组件交互操作的时候,优化了state发生变化带来的压力。简单来说,它会执行函数时产生的结果保存至内存中,当下一次输入不变的情况下,直接从内存中获取输出结果。使用方法:
import { createSelector } from 'reselect'
const getTodos = state => state.todos.present
// 使用 Selector
export const getCompletedTodoCount = createSelector([getTodos], (todos) => (
todos.reduce((count, todo) => todo.completed ? count + 1 : count, 0)
))
// 不使用 Selector
export const getCompletedTodoCount = state =>
state.todos.reduce((count, todo) => (todo.completed ? count + 1 : count), 0);
上例中若不使用Selector,每次state发生变化都会重新计算一次。
redux-undo 实现撤销重做
可以实现对每次state状态变化的监听和记录,通过触发undo和惹到实现撤销重做的效果。如给todos模块增加undoable监听:
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
import undoable from 'redux-undo'
// todos 任务列表增删改查
// visibilityFilter设置滤逻条件
const rootReducer = combineReducers({
todos: undoable(todos),
visibilityFilter
})
export default rootReducer
撤销重做的触发:
import React from 'react'
import { ActionCreators as UndoActionCreators } from 'redux-undo'
import { connect } from 'react-redux'
import classnames from 'classnames'
let UndoRedo = ({
onUndo,
onRedo
}) => (
<div className={classnames({ "undoredu": true })}>
<div onClick={onUndo} className={classnames({ "undoredu-btn": true })}>
撤销
</div>
<div onClick={onRedo} className={classnames({ "undoredu-btn": true })}>
重做
</div>
</div>
)
const mapDispatchToProps = ({
onUndo: UndoActionCreators.undo,
onRedo: UndoActionCreators.redo
})
UndoRedo = connect(
null,
mapDispatchToProps
)(UndoRedo)
export default UndoRedo
这里要注意,要获取经过undoable包装后的todos状态值时,要由state.todos 改为 state.todos.present。
redux-thunk 获取dispatch控制权,实现异步处理
redux-thunk属于中间件,它可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装。就是在Action传递到Store之前再做点事情,常用于异步处理。先看看thunk源码如下:
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
使用方法:
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducer from './reducers'
import App from './containers/App'
const middleware = [thunk];
const store = createStore(reducer, applyMiddleware(...middleware))
render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root')
)
引入thunk后,这时,原来的Action里的函数会由:
export const addToCart = productId => {}
变为:
export const addToCart = productId => (dispatch, getState) => {}
可以发现,Action中的addToCart拿到了dispatch的控制权,这样就可以配合axios和Promise实现异步请求的实现。如:
// shop.js
const _get = ({
url,
query
}) => {
return axios({
method: 'get',
url,
params: { ...query}
}).then((res) => {
if(res.status == 200) {
return res.data
}
return Promise.reject(res);
}, (err) => {
return Promise.reject(err.message || err.data);
});
};
export const getNetProducts = () => {
const url = "/data/products.json"
const query = {}
return _get({
url,
query
}).then((data) => Promise.resolve(data)).catch((e) => Promise.reject(e));
}
export default {
getNetProducts: getNetProducts
}
// actions.js
import shop from '../api/shop'
import * as types from '../constants/ActionTypes'
const receiveProducts = products => ({
type: types.RECEIVE_PRODUCTS,
products
})
export const getAllProducts = () => dispatch => {
// 请求网络JSON数据
shop.getNetProducts().then((data) => {
dispatch(receiveProducts(data)) // 等到请求结束再触发实现异步
}).catch((e) => {
console.log("请求数据发生错误");
});
}
自定义中间件的结构写法如下:
const myMiddleware = (store) => (next) => (action) => {
// 对action数据进行操作
// 返回action对象
next(action)
}
const middleware = [thunk, myMiddleware];
一般情况下是不需要用到自定义中间件的,因为npm上有提供了大部分可用的中间件,可自己选择性查询使用。
整篇的《Flux、Redux到react-redux衍变发展》三部曲介绍至此总结完毕啦,希望这些知识点的归纳能对大家有用,我会继续努力分享更多前端知识总结给大家,共同进步。