Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux可以作为任何UI层的store,所以它可以运行在Angular、Angular 2、 Vue、React甚至React-Native中。Redux 提供的订阅机制,可以与任何代码集成。这里仅仅记录一些Redux在React-Native中的实现。
为什么要用状态管理
在React中已经提供了state以及setState来实现页面的改动,那么为什么需要状态管理呢?无论是Mobx还是Redux都作为setState的替代品。那么state以及setState有什么缺陷,必须通过这种第三方的状态管理的介入去构建应用。
setState方法在React中是一个很重要的方法,每当调用setState的时候React会重新渲染render方法,其中调用state的值就会相应发生改变,也就完成了一次页面响应。在React的教程中,也大多是这种介绍。那么问题在哪里呢?
首先,setState并不能保证是一个同步的方法,有可能是同步的,也有可能是异步的。它是一个合成法方法,后续会调用一系列的方法来完成组件重新渲染,关于setState的问题详情可以看这里。总之,state的状态并不是可以预测的。其次,在实际的生产中,复杂的页面会导致render下的DOM结构非常复杂,会包含一百到几百行的JSX代码。假如这个时候去改变其中某一个button的选中状态,本意是只改动button这个组件,其他的组件保持当前状态不动。当调用setState的时候,它并不会智能的仅仅改变button的状态,而是重新渲染整个庞大的DOM树。虽然React在Virtual DOM做了优化,但这种大规模的无用渲染仍然会影响应用性能。当然有解决的方法,就是将大组件拆分,分成很多的小组件,然后在shouldComponentUpdate中去做优化。关于shouldComponentUpdate相关优化问题可以参考这里。还有的话就是,state数据与JSX混杂在同一个页面,耦合高十分不方便维护。一般来说页面的UI、逻辑、网络请求分开来写,既方便维护,代码逻辑也很清楚。
上述的问题只是本人在React-Native上实践遇到的,如果有任何错误,请及时通知我。
Redux思想
既然在state以及setState实践上遇到了问题,那么Redux是如何解决这些问题的呢?先来看看Redux的核心概念:Action、Reducer和Store。Store中存储了数据state,state这个对象就像 “Model”,区别是它并没有 setter(修改器方法),因此其它的代码不能随意修改它,造成难以复现的 bug。Action 就是一个普通 JavaScript 对象它是用来描述发生了什么。为了把 action 和 state 串起来,开发一些函数,reducer 只是一个接收 state 和 action,并返回新的 state 的函数。 以上差不多就是Redux的全部思想了,Store、Action、Reducer各司其职配合工作。
在这里还要强调Redux中的三个原则:单一数据源、State 是只读的、使用纯函数来执行修改。
- 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers。
在React-Native中使用
既然已经了解了Redux的基础概念,那么就在React-Native中使用它,才能进一步理解Redux是如何运作的。使用Redux实现一个简单的计数器。
就是这样的一个简单的例子,点击➕号数字加一,点击➖数字减一。当然在这里是可以用setState来实现的,为了学习Redux还是将它按照Redux的规范来编写。
首先要先引入redux,在项目的根目录执行
1 | npm install --save redux |
构建UI
首先我们先构建出这个UI页面,只要是接触过React-Native。那么是很容易实现的,大致的代码是这个样子的:
1 | export default class Count extends Component<Props> { |
UI出现了,那么就差点击事件的处理了。
Action
这里我们需要发起一个事件,在这里点击的时候要让数字加一,所以首先要构建一个Action描述一个这个事情。
1 | // action |
type相当于key,作为Action的一个标识,注意这里的type是不可以有重复的,保证其唯一性。还接受了一个参数number,就是点击的时候,要在当前的number基础上加一。
Reducer
上面的Action仅仅表示了要发生的事情,并没有描述这个事情该怎么做。Reducer的功能就是去具体的实现state的变化,在这里就是承担了加一这个事情。记住,Reducer是一个纯函数,它接收state和action作为参数,并且返回一个新的state。也即是(state, action) => newState。
1 | // reducer |
还记得之前的Action设置的type吗?在Reducer中它作为区分不同的Action的一个标识,Reduce通过不同的type可以处理很多的Action。注意到Reducer返回了一个新的对象,虽然只改变了state中的一个字段。这里是通过Object.assign方法来实现的,也可以使用ES6中的{..state, number: action.number + 1}来完成这一操作。
现在完成了➕事件的处理,就差把onPress事件和Action连接起来了。
Store
在一个应用中只存在一个store,为了方便所有子组件访问该store,一般来说会在顶层组件中提供Provider组件,以便于子组件可以通过props的方式访问到store,可以自己实现一个Provider,一般的情况使用的是第三方组件react-redux。在项目的根目录中引用该组件
1 | npm install --save react-redux |
在项目的入口文件中,用Provider将UI组件包裹起来
1 | <Provider> |
这里还有没将store传入Provider中,首先要创建一个store,通过Redux提供的createStore方法
1 | import { createStore } from 'redux'; |
createStore方法接受一个Reducer,将刚才写的Reducer传入其中即可完成一个store创建。下面就是将store与UI关联起来。在最开始写完的UI页面中加入如下代码
1 | import { connect } from 'react-redux'; |
主要是通过connect这个方法将store中的state以及action都传递给UI页面,可以通过props的方式访问store。在mapStateToProps以及mapDispatchToProps中定义参数名称以方便使用。完成以上所有代码之后,即可尝试运行代码,点击➕是否可以正常显示,减法和加法是同样的流程,这里并不做说明。
到这里就完成了一个由Redux构建的应用,可以看到为了实现一个方法的调用,做了很多的工作,从Action到Reducer再到state然后在渲染页面。可以说Redux的实现十分的繁琐,所以在具体的生产环节中,一定要思考清楚到底需不需要Redux,这里也有何时使用Redux的说明以供参考。
Middleware
关于Redux的中间件了解的并不多,这里只做简单的介绍。中间件实现的即是在Action与Reducer中间做一些操作的功能。比如,需要一个异步请求、需要调试每一次Actions执行的时候打印日志。这些都可以使用中间件完成。比如常用的redux-thunk、redux-logger等等。如果还是不知道中间件是做什么的,那么在项目中引用redux-logger
1 | npm install --save redux-logger |
完成之后,在创建store的时候将中间件添加进去
1 | import logger from 'redux-logger'; |
之后打开React-Native的调试页面,点击➕即可发现,控制台会自动的打印出相关的state信息,并不需要在代码中添加console.log操作,确实是极大的方便了调试。
redux-thunk主要是用来实现异步操作,比如网络请求之类的场景这里就不一一做尝试了。
总结
关于Redux我了解也不是很多,只能做一些简单的demo。在上述例子中并没有介绍Redux其他的应用技巧,在后续的学习中,逐步了解这些技巧的原理后再做记录。Redux确实在开发流程上做出了很多的规范,无论它是否真的适合生产中应用,都是只得学习它的思想的。