本文共 34232 字,大约阅读时间需要 114 分钟。
这篇具体讲讲Redux使用的一些技巧http://jafeney.com/2016/06/11/2016-06-12-redux/
在没有遁入React之前,我是一个DOM操作控,不论是jQuery
还是zepto
,我在页面交互的实现上用的最多的就是DOM操作,把复杂的交互一步一步通过选择器和事件委托绑定到document上,然后逐个连贯起来。
1 2 3 4 5 6 7 8 | $(document).on('event', 'element', function(e){ e.preventDefault(); var that = this; var parent = $(this).parent(); var siblings = $(this).siblings(); var children = $(this).children(); // ..... }); |
这是jQuery
式的编程思维,React
和它截然不同。React
的设计是基于组件化的,每个组件通过生命周期维护统一的state
,state
改变,组件便update
,重新触发render
,即重新渲染页面。而这个过程操作的其实是内存里的虚拟DOM
,而不是真正的DOM节点,加上其内部的差异更新算法,所以性能上比传统的DOM操作要好。
举个简单的例子:
现在要实现一个模态组件,如果用jQuery式的编程思维,很习惯这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | /** * @desc 全局模态窗口 **/ var $ = window.$; var modal = { confirm: function(opts) { var title = opts.title || '提示', content = opts.content || '提示内容', callback = opts.callback; var newNode = [ ' |
然后在页面的JavaScript里通过选择器触发模态和传递参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var Modal = require('modal'); var $ = window.$; var app = (function() { var init = function() { eventBind(); }; var eventBind = function() { $(document).on('click', '#btnShowModal', function() { Modal.confirm({ title: '提示', content: '你好!世界', callback: function() { console.log('Hello World'); } }); }); }; init(); })(); |
如果采用React
式的编程思维,它应该是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | /** * @desc 全局模态组件 Component * @author Jafeney * @createDate 2016-05-17 * */ import React, { Component } from 'react' import './index.less' class Modal extends Component { constructor() { super() this.state = { jsMask: 'mask hidden' } } show() { this.setState({ jsMask: 'mask' }) } close() { this.setState({ jsMask: 'mask hidden' }) } confirm() { this.props.onConfirm && this.props.onConfirm() } render() { return ( |
然后在container
的render()
函数里通过标签的方式引入,并通过点击触发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import {React, component} from 'react'; import Modal from 'Modal'; class App extends Component { render() { |
你会发现,上面的代码并没有刻意地操作某个DOM元素的样式,而是通过改变组件的state
去触发自身的渲染函数。换句话说,我们不需要写繁琐的DOM操作,而是靠改变组件的state
控制组件的交互和各种变化。这种思维方式的好处等你熟悉React
之后自然会明白,可以大大地减少后期的代码量。
前面提到组件的state
改变即触发render()
,React
内部虽然做了一些算法上的优化,但是我们可以结合Immutable
做进一步的渲染优化,让页面更新渲染速度变得更快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * @desc PureRender 优化渲染 **/ import React, { Component } from 'react' import Immutable from 'immutable'; export default { // 深度比较 deepCompare: (self, nextProps, nextState) => { return !Immutable.is(self.props, nextProps) || !Immutable.is(self.state, nextState) }, // 阻止没必要的渲染 loadDetection: (reducers=[])=> { for (let r of reducers) { if (!r.get('preload')) return ( ) } } } |
这样我们在container
的render()
函数里就可以调用它进行渲染优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import React, { Component } from 'react' import PureRenderMixin from '../../mixins/PureRender'; class App extends Component { render() { let { actions, account, accountLogs, bankBind } = this.props; // 数据导入检测 let error = PureRenderMixin.loadDetection([account, accountLogs, bankBind]) // 如果和上次没有差异就阻止组件重新渲染 if (error) return error return ( |
其实Redux
最大的作用就是有效减少代码量,把繁琐的操作通过 action ----> reducer ----> store
进行抽象,最后维护统一的state
。对于页面的全局模块,简单地封装成mixin
来调用还是不够的,比如全局的request
模块,下面介绍如何用Redux
进行改造。
首先在types.js
里进行声明:
1 2 3 4 5 6 | // request export const REQUEST_PEDDING = 'REQUEST_PEDDING'; export const REQUEST_DONE = 'REQUEST_DONE'; export const REQUEST_ERROR = 'REQUEST_ERROR'; export const REQUEST_CLEAN = 'REQUEST_CLEAN'; export const REQUEST_SUCCESS = 'REQUEST_SUCCESS'; |
然后编写action
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /** * @desc 网络请求模块的actions **/ // fetch 需要使用 Promise 的 polyfill import { pendingTask, // The action key for modifying loading state begin, // The action value if a "long" running task begun end // The action value if a "long" running task ended } from 'react-redux-spinner'; import 'babel-polyfill' import fetch from 'isomorphic-fetch' import Immutable from 'immutable' import * as CONFIG from './config'; //请求的配置文件 import * as TYPES from './types'; export function request(route, params, dispatch, success=null, error=null, { method='GET', headers={}, body=null } = {}) { dispatch({type: TYPES.REQUEST_PEDDING, [ pendingTask ]: begin}) // 处理query const p = params ? '?' + Object.entries(params).map( (i)=> `${i[0]}=${encodeURI(i[1])}` ).join('&') : '' const uri = `${ CONFIG.API_URI }${ route }${ p }` let data = {method: method, headers: headers} if (method!='GET') data.body = body fetch(uri, data) .then((response) => { dispatch({type: TYPES.REQUEST_DONE, [ pendingTask ]: end}) return response.json() }) .then((data) => { if (String(data.code) == '0') { if (method !== 'GET' ) dispatch({type: TYPES.REQUEST_SUCCESS}); success && success(data); } else { console.log(data.error) dispatch({type: TYPES.REQUEST_ERROR, ...data}) error && error(data) } }) .catch((error) => { console.warn(error) }) } export function requestClean() { return { type: TYPES.REQUEST_CLEAN } } |
然后编写对应的reducer
操作state
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import Immutable from 'immutable'; import * as TYPES from '../actions/types'; import { createReducer } from 'redux-immutablejs' export default createReducer(Immutable.fromJS({status: null, error: null}), { [TYPES.REQUEST_ERROR]: (state, action) => { return state.merge({ status: 'error', code: action.code, error: Immutable.fromJS(action.error), }) }, [TYPES.REQUEST_CLEAN]: (state, action) => { return state.merge({ status: null, error: null, }) }, [TYPES.REQUEST_SUCCESS]: (state, action) => { return state.merge({ status: 'success', error: null, }) } }) |
然后在reducers
的index.js
里对外暴露接口
1 | export request from './request' |
为什么要做这一步呢?因为我们需要在configureStore.js
里利用combineReducers
对所有的reducer
进行进一步的结合处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' import * as reducers from './reducers' import { routerReducer, routerMiddleware } from 'react-router-redux' import { pendingTasksReducer } from 'react-redux-spinner' export default function configureStore(history, initialState) { const reducer = combineReducers({ ...reducers, routing: routerReducer, pendingTasks: pendingTasksReducer, }) const store = createStore( reducer, initialState, compose( applyMiddleware( thunkMiddleware, routerMiddleware(history) ) ) ) return store } |
接下来就可以在container
里使用了,比如登录模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | /** * @desc 登录模块 container * @createDate 2016-05-16 * @author Jafeney<692270687@qq.com> **/ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { replace } from 'react-router-redux' import { login } from '../../actions/user' import { requestClean } from '../../actions/request' import CheckUserMixin from '../../mixins/CheckUser' import PureRenderMixin from '../../mixins/PureRender' import '../style.less'; class Login extends Component { constructor() { super() } shouldComponentUpdate(nextProps, nextState) { // 如果已经登录不触发深度比较 if (nextProps.user.getIn(['login', 'status'])=='logged') { this.toMain() return true } return PureRenderMixin.deepCompare(this, nextProps, nextState) } // 检查登录态 componentDidMount() { let { user } = this.props; if (CheckUserMixin.isLogged(user)) this.toMain() } // 初始化页面 toMain() { this.props.actions.replace('/') this.props.actions.requestClean() } // 执行登录 login() { const userName = this.refs['J_username'].value, password = this.refs['J_password'].value if (userName && password) { this.props.actions.login({username: userName, password: password}) } } // 绑定回车事件 onEnter(event) { var e = event || window.event || arguments.callee.caller.arguments[0]; if(e && e.keyCode==13) { // enter 键 this.login() } } render() { let { user } = this.props return ( ) } } // 下面是redux的核心方法 function mapStateToProps(state) { return { user: state.user } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ login, requestClean, replace }, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(Login) |
注意:通过以上方式,在组件内部
actions
里挂载的方法就可以通过this.props
取得了。
转载地址:http://kveni.baihongyu.com/