Redux的全家桶与最佳实践

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

04203537_ak6W.png
image.png


Redux 的第一次代码提交是在 2015 年 5 月底(也就是一年多前的样子),那个时候 React 的最佳实践还不是明晰,作为一个 View 层,有人会用 backbone 甚至是 angular 和它搭配,也有人觉得这层 View 功能已经足够强大,简单地搭配一些 utils 就直接上。后来便有了 FLUX 的演讲,React 社区开始注意到这种新的类似函数式编程的理念,Redux 也作为 FLUX 的一种变体开始受到关注,再后来顺理成章地得到 React 的『钦点』,作者也加入了 Facebook 从事 React 的开发。生态圈经过了这一年的成熟,现在很多第三方库已经非常完善,所以这里想介绍一下目前 Redux 的一些最佳实践。

1. 复习一下 Redux 的基本概念

首先我们复习一下 Redux 的基本概念, 如果你已经很熟悉了,就直接跳过这一章吧。

Redux 把界面视为一种状态机,界面里的所有状态、数据都可以由一个状态树来描述。所以对于界面的任何变更都简化成了状态机的变化:

(State, Input) => NewState

这其中切分成了三个阶段:

  1. action
  2. reducer
  3. store

所谓的 action,就是用一个对象描述发生了什么,Redux 中一般使用一个纯函数,即 actionCreator 来生成 action 对象。

// actionCreator => action
// 这是一个纯函数,只是简单地返回 action
function somethingHappened(data){return {type: 'foo',data: data}
}

随后这个 action 对象和当前的状态树 state 会被传入到 reducer 中,产生一个新的 state

//reducer(action, state) => newState
function reducer(action, state){switch(action.type){case 'foo':return { data: data };default:return state;}
}

store 的作用就是储存 state,并且监听其变化。
简单地说就是你可以这样产生一个 store :

import { createStore } from 'redux'
//这里的 reducer 就是刚才的 Reducer 函数
let store = createStore(reducer);

然后你可以通过 dispatch 一个 action 来让它改变状态:

store.getState();//{}
store.dispatch(somethingHappened('aaa'));
store.getState(); // { data: 'aaa'}

好了,这就是 Redux 的全部功能。对的,它就是如此简单,以至于它本体只有 3KB 左右的代码,因为它只是实现了一个简单的状态机而已,任何稍微有点编程能力的人都能很快写出这个东西。至于和 React 的结合,则需要 react-redux 这个库,这里我们就不讲怎么用了。

2. Redux的一些痛点

大体上,Redux 的数据流是这样的:

界面 => action => reducer => store => react => virtual dom => 界面

每一步都很纯净,看起来很美好对吧?对于一些小小的尝试性质的 DEMO 来说确实很美好。但其实当应用变得越来越大的时候,这其中存在诸多问题:

  1. 如何优雅地写异步代码?(从简单的数据请求到复杂的异步逻辑)
  2. 状态树的结构应该怎么设计?
  3. 如何避免重复冗余的 actionCreator?
  4. 状态树中的状态越来越多,结构越来越复杂的时候,和 react 的组件映射如何避免混乱?
  5. 每次状态的细微变化都会生成全新的 state 对象,其中大部分无变化的数据是不用重新克隆的,这里如何提高性能?

你以为我会在下面一一介绍这些问题是怎么解决的?还真不是,这里大部分问题的回答都可以在官方文档中看到: 技巧 | Redux 中文文档 ,文档里讲得已经足够详细(有些甚至详细得有些啰嗦了)。所以下面只挑 Redux 生态圈里几个比较成熟且流行的组件来讲讲。

3. Redux 异步控制

官方文档里介绍了一种很朴素的异步控制中间件 redux-thunk (如果你还不了解中间件的话请看 Middleware | Redux 中文文档 ,事实上 redux-thunk 的代码很简单,简单到只有几行代码:

function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);};
}

它其实只干了一件事情,判断 actionCreator 返回的是不是一个函数,如果不是的话,就很普通地传给下一个中间件(或者 reducer);如果是的话,那么把 dispatchgetStateextraArgument 作为参数传入这个函数里,实现异步控制。

比如我们可以这样写:

//普通action
function foo(){return {type: 'foo',data: 123}
}//异步action
function fooAsync(){return dispatch => {setTimeout(_ => dispatch(123), 3000);}
}

但这种简单的异步解决方法在应用变得复杂的时候,并不能满足需求,反而会使 action 变得十分混乱。

举个比较简单的例子,我们现在要实现『图片上传』功能,用户点击开始上传之后,显示出加载效果,上传完毕之后,隐藏加载效果,并显示出预览图;如果发生错误,那么显示出错误信息,并且在2秒后消失。

用普通的 redux-thunk 是这样写的:

function upload(data){return dispatch => {// 显示出加载效果dispatch({ type: 'SHOW_WAITING_MODAL' });// 开始上传api.upload(data).then(res => {// 成功,隐藏加载效果,并显示出预览图dispatch({ type: 'PRELOAD_IMAGES', data: res.images });dispatch({ type: 'HIDE_WAITING_MODAL' });}).catch(err => {// 错误,隐藏加载效果,显示出错误信息,2秒后消失dispatch({ type: 'SHOW_ERROR', data: err });dispatch({ type: 'HIDE_WAITING_MODAL' });setTimeout(_ => dispatch({ type: 'HIDE_ERROR' }), 2000);})}
}

这里的问题在于,一个异步的 upload action 执行过程中会产生好几个新的 action,更可怕的是这些新的 action 也是包含逻辑的(比如要判断是否错误),这直接导致异步代码中到处都是 dispatch(action) ,是很不可控的情况。如果还要进一步考虑取消、超时、队列的情况,就更加混乱了。

所以我们需要更强大的异步流控制,这就是 GitHub - yelouafi/redux-saga: An alternative side effect model for Redux apps 。下面我们来看看如果换成 redux-saga 的话会怎么样:

import { take, put, call, delay } from 'redux-saga/effects'
// 上传的异步流
function *uploadFlow(action) {// 显示出加载效果yield put({ type: 'SHOW_WAITING_MODAL' });// 简单的 try-catchtry{const response = yield call(api.upload, action.data);yield put({ type: 'PRELOAD_IMAGES', data: response.images });yield put({ type: 'HIDE_WAITING_MODAL' });}catch(err){yield put({ type: 'SHOW_ERROR', data: err });yield put({ type: 'HIDE_WAITING_MODAL' });yield delay(2000);yield put({ type: 'HIDE_ERROR' });}     
}function* watchUpload() {yield* takeEvery('BEGIN_REQUEST', uploadFlow)
}

是不是规整很多呢?redux-saga 允许我们使用简单的 try-catch 来进行错误处理,更神奇的是竟然可以直接使用 delay 来替代 setTimeout 这种会造成回调和嵌套的不优雅的方法。

本质上讲,redux-sage 提供了一系列的『副作用(side-effects)方法』,比如以下几个:

  1. put (产生一个 action)
  2. call (阻塞地调用一个函数)
  3. fork (非阻塞地调用一个函数)
  4. take (监听且只监听一次 action)
  5. delay (延迟)
  6. race (只处理最先完成的任务)

并且通过 Generator 实现对于这些副作用的管理,让我们可以用同步的逻辑写一个逻辑复杂的异步流。

下面这个例子出自于 官方文档 ,实现了一个对于请求的队列,即让程序同一时刻只会进行一个请求,其它请求则排队等待,直到前一个请求结束:

import { buffers } from 'redux-saga';
import { take, actionChannel, call, ... } from 'redux-saga/effects';function* watchRequests() {// 1- 创建一个针对请求事件的 channelconst requestChan = yield actionChannel('REQUEST');while (true) {// 2- 从 channel 中拿出一个事件const {payload} = yield take(requestChan);// 3- 注意这里我们使用的是阻塞的函数调用yield call(handleRequest, payload);}
}function* handleRequest(payload) { ... }

更多关于 redux-saga 的内容,请参考 Read Me | redux-saga (中文文档: 自述 | Redux-saga 中文文档 )。

4. 提高 selector 的性能

把 react 与 redux 结合的时候,react-redux 提供了一个极其重要的方法: connect ,它的作用就是选取 redux store 中的需要的 state 与 dispatch , 交由 connect 去绑定到 react 组件的 props 中:

import { connect } from 'react-redux';
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'// 我们需要向 TodoList 中注入一个名为 todos 的 prop
// 它通过以下这个函数从 state 中提取出来:
const mapStateToProps = (state) => {// 下面这个函数就是所谓的selectortodos: state.todos.filter(i => i.completed)// 其它props...
}const mapDispatchToProps = (dispatch) => {onTodoClick: (id) => {dispatch(toggleTodo(id))}
}// 绑定到组件上
const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps
)(TodoList)export default VisibleTodoList

在这里需要指定哪些 state 属性被注入到 component 的 props 中,这是通过一个叫 selector 的函数完成的。

上面这个例子存在一个明显的性能问题,每当组件有任何更新时都会调用一次 state.todos.filter 来计算 todos ,但我们实际上只需要在 state.todos 变化时重新计算即可,每次更新都重算一遍是非常不合适的做法。下面介绍的这个 reselect 就能帮你省去这些没必要的重新计算。

你可能会注意到, selector 实际上就是一个『 纯函数』

selector(state) => some props

而纯函数是具有可缓存性的,即对于同样的输入参数,永远会得到相同的输出值 (如果对这个不太熟悉的同学可以参考 JavaScript函数式编程 ,reselect 的原理就是如此,每次调用 selector 函数之前,它会判断参数与之前缓存的是否有差异,若无差异,则直接返回缓存的结果,反之则重新计算:

import { createSelector } from 'reselect';var state = {a: 100
}var naiveSelector = state => state.a;// mySelector 会缓存输入 a 对应的输出值
var mySelector = createSelector(naiveSelector, a => {console.log('做一次乘法!!!');return a * a;}
)console.log(mySelector(state));    // 第一次计算,需要做一次乘法
console.log(mySelector(state));    // 输入值未变化,直接返回缓存的结果
console.log(mySelector(state));    // 同上
state.a = 5;                            // 改变 a 的值
console.log(mySelector(state));    // 输入值改变,做一次乘法
console.log(mySelector(state));    // 输入值未变化,直接返回缓存的结果
console.log(mySelector(state));    // 同上

上面的输出值是:

做一次乘法!!!
10000
10000
10000
做一次乘法!!!
25
25
25

之前那个关于 todos 的范例可以这样改,就可以避免 todos 数组被重复计算的性能问题:

import { createSelector } from 'reselect';
import { connect } from 'react-redux';
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'const todoSelector = createSelector(state => state.todos,todos => todos.filter(i => i.completed)
)const mapStateToProps = (state) => {todos: todoSelector// 其它props...
}const mapDispatchToProps = (dispatch) => {onTodoClick: (id) => {dispatch(toggleTodo(id))}
}// 绑定到组件上
const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps
)(TodoList)export default VisibleTodoList

更多可以参考 GitHub - reactjs/reselect: Selector library for Redux

5. 减少冗余代码

redux 中的 action 一般都类似这样写:

function foo(data){return {type: 'FOO',data: data}
}//或者es6写法:
var foo = data => ({ type: 'FOO', data})

当应用越来越大之后,action 的数量也会大大增加,为每个 action 对象显式地写上 type 和 data 或者其它属性会造成大量的代码冗余,这一块是完全可以优化的。

比如我们可以写一个最简单的 actionCreator:

function actionCreator(type){return function(data){return {type: type,data: data}}
}var foo = actionCreator('FOO');
foo(123); // {type: 'FOO', data: 123}

redux-actions 就可以为我们做这样的事情,除了上面这种朴素的做法,它还有其它比较好用的功能,比如它提供的 createActions 方法可以接受不同类型的参数,以产生不同效果的 actionCreator,下面这个范例来自官方文档:

import { createActions } from 'redux-actions';const { actionOne, actionTwo, actionThree } = createActions({// 函数类型ACTION_ONE: (key, value) => ({ [key]: value }),// 数组类型ACTION_TWO: [(first) => first,               // payload(first, second) => ({ second }) // meta],// 最简单的字符串类型
}, 'ACTION_THREE');actionOne('key', 1));
//=>
//{
//  type: 'ACTION_ONE',
//  payload: { key: 1 }
//}actionTwo('Die! Die! Die!', 'It\'s highnoon~');
//=>
//{
//  type: 'ACTION_TWO',
//  payload: ['Die! Die! Die!'],
//  meta: { second: 'It\'s highnoon~' }
//}actionThree(76);
//=>
//{
//  type: 'ACTION_THREE',
//  payload: 76,
//}

更多可以参考 GitHub - acdlite/redux-actions: Flux Standard Action utilities for Redux.

转载于:https://my.oschina.net/cllgeek/blog/1584693

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/539926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php二分查找算法时间复杂度,一个运用二分查找算法的程序的时间复杂度是什么...

一个运用二分查找算法的程序的时间复杂度是“对数级别”。二分查找是一种效率较高的查找方法,算法复杂度即是while循环的次数,时间复杂度可以表示“O(h)O(log2n)”。本教程操作环境:windows7系统、Dell G3电脑。一个运用二分查找算法的程序的…

将不确定变为确定~头压缩是否有必要,MVC如何实现头压缩

网页的头部压缩在页面体积大的情况下非常有必要做,它会使页面体积有一个明显的减小,同时加到网页从服务端下载到客户端的速度,以下是我做的一个测试: 没有使用头压缩时: 使用了头压缩后: 我们可以看到&…

android .9.png ”点九” 图片制作方法

“点九”是andriod平台的应用软件开发里的一种特殊的图片形式,文件扩展名为:.9.png 智能手机中有自动横屏的功能,同一幅界面会在随着手机(或平板电脑)中的方向传感器的参数不同而改变显示的方向,在界面改变方向后,界面上的图形会因为长宽的变化而产生拉伸…

深入理解HTTP Session

session在web开发中是一个非常重要的概念,这个概念很抽象,很难定义,也是最让人迷惑的一个名词,也是最多被滥用的名字之一,在不同的场合,session一次的含义也很不相同。这里只探讨HTTP Session。为了说明问题…

Linux访问其他进程空间,Linux环境进程间通信系列(五):共享内存

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区…

冲刺NO.8

Alpha冲刺第八天 站立式会议 项目进展 项目稳步进行,项目的基础部分如基本信息管理,信用信息管理等部分已相对比较完善。 问题困难 技术困难在短期内很难发生质的变化,而本项目由于选择了队员不太熟悉的程序框架,所以所以项目的交…

linux由众多微内核组成,什么是linux

大家对Linux这个词比较陌生吧,那么Linux是什么呢?Linux是什么Linux是一种自由和开放源码的类Unix操作系统。目前存在着许多不同的Linux,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,从手机、平板电脑、路由器和视…

SqlServer2008备份与还原(完整图示版)

一、备份 1、在需要备份的数据库上,右键——任务——备份,如下: 2、选择备份到哪个路径和备份名字: 点击“添加”,如下, 3、上面点击“确定”后,回到第一个页面,选中刚才添加的路径和…

Jquery mobile问题总汇

转载&#xff1a;http://www.wglong.com/main/artical!details?id4#q6 1页面缩放显示问题 问题描述&#xff1a; 页面似乎被缩小了&#xff0c;屏幕太宽了。 解决办法&#xff1a; 在head标签内加入&#xff1a; <meta name"viewport" content"widthdevice…

linux环境OpenRASP使用教程,集成openRASP与攻击测试

1.介绍openRASP是一个百度的安全框架&#xff0c;将其集成到我们的web项目中&#xff0c;就像是给web项目安装了一款“安全管家”的软件&#xff0c;它可以检测到攻击&#xff0c;并进行拦截。2.集成openRASP到项目中openRASP针对不同的服务器&#xff0c;提供了不同的安装方法…

ExtJs 备忘录(4)—— Form表单(四) [ 数据提交 ]

一、截图和示例共用Ext.FormPanel1.1  截图由于本文主要关注的是表单提交的几种方式&#xff0c;所以仅用了一个表单项以便于测试和减少示例代码。1.2  示例共用Ext.FormPanel <script type"text/javascript">Ext.onReady(function() { Ext.Qui…

struts2访问jsp页面404

问题描述 在搭建struts2环境的时候&#xff0c;拷贝了web.xml&#xff0c;拷贝了struts.xml&#xff0c;拷贝了jar包。运行&#xff0c;正常&#xff0c;访问jsp页面&#xff0c;报404错误。 web.xml <?xml version"1.0" encoding"UTF-8"?> <w…

centos7定制linux镜像,自定制Centos7.3系统镜像(ISO)

本文主要介绍如何根据官方的Centos镜像文件&#xff0c;在保留原有默认安装的RPM包的基础下&#xff0c;添加自己所需要的RPM包的&#xff0c;最终生成一个自定制版的ISO&#xff0c;节省了宝贵的时间并确保了安装的定制性。对于其他没有介绍的修改&#xff0c;后续在实践中会进…

调用打开另外一个APK

2019独角兽企业重金招聘Python工程师标准>>> Intent mIntent new Intent(); mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ComponentName comp new ComponentName("com.mm.android.direct.gdmssphoneLite", "com.mm.android.direct.gdmsspho…

Jquery Mobile dialog的生命周期

JQuery Mobile对htm5的移动开发绝对是个好用的东西&#xff0c;今天简单谈谈JQuery Mobile中的dialog的使用。 1.对话框的弹出。 2.对话框的生命周期。 3.对话框内事件的注册。 1&#xff09;第一个问题&#xff1a;对话框的弹出。 如果要弹出一个对话框&#xff0c;可以在页面…

c语言源程序最多可能由组成,一个C语言源程序由若干函数组成,其中至少应含有一个()。...

个C语言源由I am interested in the training course, which _____ at Hilton Hotel in Beijing from March 8 to 12, 2018.程序成“万物莫不有对”体现了中国传统哲学的矛盾观。用户在进行产品的三维设计时&#xff0c;干函可以采用以下( )的设计方法。数组少意识是人脑对客观…

Citrix Netscaler版本管理和选择

Citrix Netscaler版本管理和选择 来源 http://blog.51cto.com/caojin/1898164 随着Citrix Netscaler的快速发展&#xff0c;有很多人在维护设备时经常搞不懂Netscaler软件版本是如何查看和选择&#xff0c;当前软件是否需要升级&#xff0c;当前软件是否稳定等。基于以上问题&a…

oracle实例与数据库

一、名称 Oracle数据库服务器。单叫数据库或服务器都不全面。 二、组成 oracle数据库服务器由二部份组成&#xff1a;实例和数据库 实例&#xff1a; 可理解为对象&#xff0c;看不见。数据库&#xff1a; 理解为类&#xff0c;看得见的&#xff0c;E:\app\Administrator\…

WEB前端面试题汇总整理01

1.JS找字符串中出现最多的字符 例如&#xff1a;求字符串nininihaoa中出现次数最多字符 var str "nininihaoa"; var o {}; for (var i 0, length str.length; i < length; i) {var char str.charAt(i);if (o[char]) {o[char]; //次数加1} else {o[char] 1;…

c语言 文件游程统计,游程 码表 如何形成

游程 码表 如何形成求一个程序 将下表用huffman 树存储表示用传统的霍夫曼建立的树 好像不能形成此码表。我感觉应该有一个特等的算法。我也试了好几种方法&#xff0c;感觉都不行&#xff0c;希望大家给点建议。(码表 要利于编码和解码)(部分码表)白游程 码子 黑游程 码子(长…