这篇文章是作为一个新手在学习过程中对Redux概念的理解。有错误的地方希望各位能帮我指出。一起学习进步。
使用React native 开发的时候,一直在纠结是否要使用Redux,刚接触的时候会发现又有action,又有Reducer,还有dispatch,对于我这类刚刚从移动开发学习RN开发的人,很容易就搞的一团雾水,学习Redux也是看了好几遍然后又放弃了很多遍,知道公司业务需求开发状态比较复杂的项目的时候,终于下定决心学习使用Redux。
学习下来后发现其实最难入门的是理解Redux的概念和思想,理解后会觉得入门的难度降低很多。所以分享一下我这个刚入门人的体会。
理解Redux
我们在开发React 或者React native的时候,在一个compentent中,一般的做法是通过state来定义一些可能会引起页面变化的变量。比如登录状态,或者
中的文字等。通过改变state状态来刷新页面,state改变的时候会触发render来重新渲染该页面。
如果我们要通过改变state来刷新子控件的话。可以设置子控件的Props为当前控件的state
,通过改变state来控制页面刷新。或者调用子控件ref来使用子控件的方法。
然后再跨控件的话。比如相隔了好几个页面。就只能通过设置global全局变量或者使用通知监听的方式来实现界面刷新。global是开发过程中非常不推荐的方式。而如果一个状态的改变要刷新很多其他页面的UI。比如登录成功的状态要刷新个人中心页面其他页面的头像或者其他等等时。就会要写很多的监听。
而且react native中的componentWillMount和iOS中的viewWillAppear不同。切换页面前后是不会触发,componentWillUnmount也相当于iOS中的delloca(释放内存时才触发)。所以必定要使用到很多监听或global。如果这样改变页面多的state多的话,整个应用就会变得非常乱。因此Redux的作用在此时就可以显现出来了。
Redux的概念大概是这样的。把整个应用看成一个component的话,这整个应用有一个对应的this.state。所有页面都可以拿到这个state中的内容。当这个this.state中的state改变的时候。所有关联页面的页面就会刷新。这样,不用写global,不用写通知。就可以跨页面共享一个状态。毕竟所有redux中的状态都是可以全局拿到的。
有点像上面提到的
改变state的来改变子控件UI的做法。所有用到rudux的页面都是他的子控件。改变了state子页面就都会跟这变。
Store
首先我们要理解Store。这个就理解为所有状态的容器就行。它就是套在所有子页面外的顶级Component。所有的Redux状态都是存在这个顶级的component中的this.state。它通过
这样的方式来控制子空间的UI。改变了Store中的state就可以实现我们改变UI的操作。
我们现在把所有数据都存在了store中。那么下一步。如果我们要怎么改变Store中的state。调用类似Store.setState({...})
这样的操作吗?这样显得太随意也太简单了。毕竟Store中的数据可能影响整个程序。如果某个页面误改变一下就会引起很多页面的同时变动。出了错误也很难定位到是哪边改变出的问题。State应该设置为只读的。
所以Store中state这个值不能直接改变。我们需要一个比较完善的系统来管理所有的state。这时候就该action和reducer出手了。
Action
reducer规定,如果想改变这个Store中的状态。不能直接改变他的值。而是要要写一个叫action的东西。
那么action怎么样简单的理解呢。就是它字面上的意思。一个动作。比如我要登录。那么登录成功或者登录失败就是一个动作。怎么描述这个动作呢?一般会用下面的一个对象来描述
//定义一个action的种类
const ACTION_TYPE_LOGIN = 'action_type_login';
const loginSuccessAction = {
type:ACTION_TYPE_LOGIN,
Result:'success'
}
const loginFailedAction = {
type:ACTION_TYPE_LOGIN,
Result:'failed'
}
这样loginSuccessAction
和loginFailedAction
就分别代表了登录成功和登录失败这个一个动作。type
代表着action的种类。成功和失败都可以归结为登录这个种类。 type
是必须要的参数,后面的Reducer会根据这个属性来判断改变哪部分的状态。
dispatch
字面上的意思是部署。部署什么呢?部署一个动作。也就是告诉Redux。我登录成功了。你赶紧处理一下改变相应State吧。告诉Redux。使用起来就是调用dispatch(loginSuccessAction)
,然后,Redux就知道你登录成功了。他就会让一个叫Reducer的人来处理你这个动作。
Reducer
我们又碰到了一个新的名词Reducer。这是什么东西呢?Reducer可以理解为通过接收action动作,然后来改变我们的全局状态的一个处理者。
通过dispatch(loginSuccessAction)
来告诉Redux,我登录成功了。然后rudux就让Ruducer来处理这个登录成功动作,Reducer拿到了登录成功这个动作,同时也拿到了Store中当前的状态。通过这两个对象。生成了一个登录成功后Store应该变成的状态。
用代码表示就是这样的
function loginReducer (state,action){
switch (action.type) {
case TYPES.ACTION_LOGIN: // 初始状态
return Object.assign({}, state, {
result:action.Result
});
case ...: // 初始状态
default:
return state;
}
这里要注意的是Reducer并不是用来改变Store中的状态的。他是通过Action来生成一个新的状态的。然后return 给了Store,Store自己再替换自己的状态。
所以总结下来各一句话就是
Store:状态容器
Action:对于一个动作的描述,是一个对象。
dispatch:实施动作的方法。
reducer:接收一个action和Store的当前状态,返回新的Store该有状态的处理者。
Redux的最基本的概念差不多就是这样。
稍作进阶
上面的介绍完后,应该对Rudux的概念有了一些初步理解。那么我们要应用到项目中,还需要一些性能的处理和优化。
中间件Middleware
如果按照上面的步揍,我们完成登录操作要写多少个Action呢?至少有这些吧
登录成功,登录失败,登录错误,登录中。那我们就要写四个Action。那整个项目要写的action会突破天际吧。这时候。对于属于一个type
的action。我们可以把他们四合一,写一个生成action的方法然后执行就行.
function loginAction(result){
return {
type:ACTION_LOGIN,
result:result
}
}
然后调用store.dispatch(loginAction(result))
就可以了。
那么我们登录过程要在component中通过登录的result来dispatch好几次。可不可以把判断的过程也剥离出来呢?比如整个登录我只会在component中调用一次store.dispatch(login()),后面所有的登录结果的改变全都不在页面里处理。这样页面就可以只负责UI。状态的改变完全交给Redux。
login预想的方法是这样
function login(){
//这里就没法返回登录中的action
fetch(url).then(result=>{
return{
type:action_login,
result:result
}
}).catch(err=>{
return{
type:action_login,
result:'error'
}
})
}
但是store.dispatch()默认是只接收一个Action对象。而我们login()方法中肯定要涉及到异步操作。action对象不能立即返回出来。store.dispatch(login())在执行的时 login()不能直接有返回值。相当于执行了store.dispatch(null)。而且登录的整个流程要返回登录中+登录结果两个action,这样构造肯定没法实现。怎么解决这个问题呢?
如果我们能在login()中执行其他的dispatch()动作是不是就迎刃而解了。
如果能写成这样
function login(){
//返回一个方法
return dispath=>{
dispatch(login(loginAction('logging in')));
fetch(url).then(result=>{
dispatch(login(loginAction(result)));
}).catch(err=>{
dispatch(login(loginAction('error')));
})
}
}
function loginAction(result){
return {
type:ACTION_LOGIN,
result:result
}
}
但是redux默认是不允许这么写的。因为 return的 dispath=>{...}是一个方法而不是对象。我们可以通过安装一个中间件redux-thunk
来实现让store.dispatch()可以接收一个方法。而不是只接收action对象。
出现了一个新名词,中间件(middleware)。
Redux的流程其实很简单
store.dispatch(action) -> reducer处理action,返回一个新的state ->Store更新state ->相关UI更新。
middleware可以认为就是改造store.dispatch()
这个方法的。他让你在执行部署action的过程中,加入一些自己的处理。比如redux-logger
这个中间件可以用来添加日志功能,在dispatch()时打印出你所执行的所有动作。redux-trun
可以让dispatch()接受一个function。redux-promise
可以让其接收一个promise.
模块分割来优化性能
在component中,如果改变this.state中的任意一个状态,都会引发页面的render渲染。
Redux在一个应用中只允许有一个Store 容器来存储State。那么所有的State就会都在这个Store中。也就是说。如果改变了state的话。整个应用中用到store中的state的页面都会随之刷新。可想而知。不进行优化的话,必定会有很大的性能问题。
想到的第一个方法是shouldComponentUpdate
拦截不必要的Render。
shouldComponentUpdate(nextProps,nextState){
if(nextProps.ReduxState.loginResult != this.Props.ReduxState.loginResult){
return true
}
return false
}
但是这样的话需要我们逐个判断props改变是否跟新页面。全都这样判断稍大点的应用得写几千行代码。
这时候。我们需要把Rudux分模块切割开来。每个小模块只负责他负责的事情。UI的component用到哪个模块就把这个模块和它关联起来。
切割模块
import {combineReducers} from "redux";
import ...
const rootReducer = combineReducers({
LoginReducer,
PageManagerReducer,
MusicManagerReducer
...
});
然后UI 页面中中
class Page extends React.Component {
login = ()=>{
//调用Action的方法
this.props.dispatch(...someAction)
}
render(
{this.props.LoginReducer.someState}
)
}
const useReducers = store => {
return {
//store.LoginReducer就是此页面使用到的Reducer 就是rootReducer中的模块名,前面的LoginReducer代表的是页面使用时this.props中的参数名。
LoginReducer: store.LoginReducer,
};
};
//关联页面和用到的模块,关联后,store就会把子模块的State通过props传递给此页面。this.props
export default connect(useReducers)(Page);
这样UI页面中,只有在LoginReducer子模块中的状态发生改变时,页面才会刷新。
好了,掌握这些东西。我们就可以开始用Redux写React native应用了。
中间有很多代码示例没有写。晚点直接写个Demo吧。
学习的时候推荐阮一峰老师的Redux系列教程。实在和我一样概念不清的再来看看我个人的描述吧。
传送门:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html