Flux、Redux到react-redux发展衍变三部曲之react-redux解读

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zeping891103/article/details/84787301

首先要知道,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衍变发展》三部曲介绍至此总结完毕啦,希望这些知识点的归纳能对大家有用,我会继续努力分享更多前端知识总结给大家,共同进步。

猜你喜欢

转载自blog.csdn.net/zeping891103/article/details/84787301