博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于Redux架构的单页应用开发总结(三)
阅读量:4086 次
发布时间:2019-05-25

本文共 34232 字,大约阅读时间需要 114 分钟。

写在前面

这篇具体讲讲Redux使用的一些技巧http://jafeney.com/2016/06/11/2016-06-12-redux/

React式编程思维

在没有遁入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的设计是基于组件化的,每个组件通过生命周期维护统一的statestate改变,组件便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 = [
'
',
'
',
'
',
].join('');
$('#J_mask').remove();
$('body').append(newNode);
$('#J_cancel').on('click', function() {
$('#J_mask').remove();
});
$('#J_confirm').on('click', function() {
if (typeof callback === 'function') {
callback();
}
$('#J_mask').remove();
});
}
};
module.exports = modal;

然后在页面的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 (

{ this.props.title }

this.close()}>
{ this.props.children }
this.confirm()}>{ this.props.confirmText || '确定' }
{ this.props.showCancel && (
this.close()}>取消) }
);
}
}
export default Modal

然后在containerrender()函数里通过标签的方式引入,并通过点击触发。

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() {
}
}
export default App

你会发现,上面的代码并没有刻意地操作某个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 (
)
}
}
}

这样我们在containerrender()函数里就可以调用它进行渲染优化

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 (
// something ...
);
}
}

全局模块的处理

其实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,
})
}
})

然后在reducersindex.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 (
this.onEnter()}>
会员登录
this.login()} className="login-btn">登录
免费注册 | 忘记密码 ?
{ user.getIn(['login', 'error', 'message']) }
)
}
}
// 下面是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/

你可能感兴趣的文章
WAV文件解析
查看>>
WPF中PATH使用AI导出SVG的方法
查看>>
QT打开项目提示no valid settings file could be found
查看>>
java LinkedList与ArrayList迭代器遍历和for遍历对比
查看>>
所谓的进步和提升,就是完成认知升级
查看>>
如何用好碎片化时间,让思维更有效率?
查看>>
No.182 - LeetCode1325 - C指针的魅力
查看>>
带WiringPi库的交叉笔译如何处理二之软链接概念
查看>>
Java8 HashMap集合解析
查看>>
自定义 select 下拉框 多选插件
查看>>
Linux常用统计命令之wc
查看>>
fastcgi_param 详解
查看>>
搞定Java面试中的数据结构问题
查看>>
React Native(一):搭建开发环境、出Hello World
查看>>
Winform多线程
查看>>
Spring AOP + Redis + 注解实现redis 分布式锁
查看>>
poj 1976 A Mini Locomotive (dp 二维01背包)
查看>>
《软件过程管理》 第九章 软件过程的评估和改进
查看>>
《数据库系统概论》 第三章 关系数据库标准语言SQL
查看>>
《计算机网络》第五章 运输层 ——TCP和UDP 可靠传输原理 TCP流量控制 拥塞控制 连接管理
查看>>