Redux框架的使用
前言
Redux框架是JavaScript的状态容器,在ReactNative中也是一样的作用,进行state状态管理。
一般的简单项目是不需要使用Redux的,因为使用Redux会增加代码量,导致复杂性增加。
不过在github上下载的很多ReactNative项目都是使用了Redux进行状态管理,如果不会连Demo都看不太懂,so,这个框架对ReactNative开发者来说还是一定要会的。
Redux入门
Redux最重要的几个名词:Store、Reducer、Action、state
Store:
保存应用数据的容器,整个程序只有一个store
Reducer:
用于state更新的一个函数,给他一个action和state(非必须),它会返回一个新的state,进行更新view。
Action:
事件预处理的函数,里面的type类型必须返回,表示当前action的名称,用于在reducer中的逻辑区分,更新state
state:
对应的是界面中的state
整个Redux框架的逻辑流程:
QQ截图20190413203923.png这个图片是阮一峰的关于redux的相关文章中使用的,感觉总结的很好,文章也的很好很权威,只是对Redux在ReactNative的使用帮助有点小。
结合上面的流程图进行流程解释(RN中的流程):
在界面上发出action事件,store调用reducer,并传入两个参数,一个state,一个action。reducer收到action之后返回一个新的state给store,进行更新state,这时界面上的state就会改变,然后调用shouldComponentUpdate()方法,判断是否进行渲染。返回true就render,返回false就不处理。
在RN中的使用步骤:
第一步:安装框架
首先先安装三个框架,分别是react-redux、redux、redux-thunk,安装命令分别为:
npm install react-redux --save
npm install redux --save
npm install redux-thunk --save
第二步:在应用程序的根组件外设置<Provider></Provider>组件,如:
<Provider store={store}>
<Main />
</Provider>
注意:这个Provider标签必须用在程序的根节点,不能用在程序内部其他节点,否则会报组件中store找不到的错误。
第三步:创建store
在根组件上设置provider组件是需要设置store,所以这个地方就需要创建store了。
import { createStore } from 'redux';
const store = createStore(reducer);
注意createStore的导包是redux,不要导入错了,createStore()的入参是reducer。也可以通过combineReducers()把多个reducers合并成一个再传给createStore(),如:
import counterReducer from './ReducerCounter';
import loginReducer from './ReducerLogin';
const Reducer = {
loginReducer,
counterReducer,
}
export default Reducer;
第四步:创建reducer
reducer有两个入参,一个是state,一个是action,一般的处理逻辑是根据action的type字段来更新相应的state。
注意state的类型 如果此处返回的是对象{} 就需要按照对象的取值方法,而且在页面中定义state的时候需要指定属性名。如:
countValue: state.counterReducer.state
如果是普通的数字,则可以直接运算,且在页面中只需要指定reducer名称即可。如:
countValue: state.counterReducer
下面的reducer是一个定时器的reducer案例代码:
const initCount = 10
//注意state的类型 如果此处返回的是对象{} 就需要按照对象的取值方法,而且在页面中定义state的时候需要指定属性名,如: countValue: state.counterReducer.state。如果是普通的数字,则可以直接运算,且在页面中只需要指定reducer名称即可。如:countValue: state.counterReducer
const CountReducer = (state = initCount, action) => {
console.log(state)
console.log("CountReducer:处理数据action.type==" + action.type + "\t\tstate==" + parseInt(state));
switch (action.type) {
case constants.COUNTER_ADD:
// return state + 1
return {
state: state.state + 1
}
case constants.COUNTER_REDUCE:
// return state - 1
return {
// ...state,
// …state就是展开操作符,这里这么用的作用是返回新的对象,不修改原来的对象
state: state.state - 1
}
default:
// return state
return {
state
}
}
}
export default CountReducer;
第五步:创建action预处理函数:
这个action是用于界面中发送给reducer的,而reducer里面的action:type字段是一定需要的,所以action创建的时候就需要定义type字段了,有如下两种方式:
//第一种
export function countAdd() {
return dispatch => {
dispatch(send());
}
}
function send() {
return {
type: constants.COUNTER_ADD
};
}
//第二种就是直接return了
export function countReduce() {
return {
type: constants.COUNTER_REDUCE
}
}
当然,处理type字段是必须的以外,还可以定义其他的字段,用于信息回传,如登录的action写法:
function loginSucceed(loginState, user) {
return {
type: loginState,
userInfo: user,
}
}
第六步:绑定页面,定义state、方法。
绑定页面的方法是connect(),导入的包是react-redux。
import { connect } from 'react-redux';
它的使用方式大概是:
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Login就是界面组件,mapStateToProps是关联页面中的state,mapDispatchToProps是发送action的方法。
mapStateToProps的使用方式一般为:
//定义属性 并和reducer关联
const mapStateToProps = (state) => {
return {
userInfo: state.loginReducer.userInfo,
loginState: state.loginReducer.loginState,
description: state.loginReducer.description,
}
}
这个函数需要传入state,不过connect的时候会自动关联当前页面的state,上面的loginReducer就是前面创建store时注册的reducer,state.loginReducer.userInfo就是这个reducer里面返回的属性名,需要对应上。
mapDispatchToProps的使用方式一般为:
//定义方法 并通过dispatch发送事件 dispatch()括号内需要发送action预处理事件
const mapDispatchToProps = (dispatch) => {
return {
login: (userInfo) => {
return dispatch(loginActions.login(userInfo));
},
clearData: () => {
return dispatch(loginActions.clearData());
}
};
}
这个函数需要传入dispatch对象,不过connect的时候会自动分配一个dispatch,在这个函数内可以定义当前页面可以使用的方法,如:login,register等,loginActions就是前面定义的action预处理事件,通过dispatch()方法发送出去,等待reducer响应。
第七步:发送action
store已创建、reducer已创建、action也创建了。所以发送action算是最后一步了。
dispatch(loginActions.clearData());
发送action事件之后,等待reducer响应并改变相应的state,如果state改变,在发送action的页面就是回调shouldComponentUpdate这个生命周期方法。如:
//监听到界面props或者state更新时调用此方法,返回false不更新界面,返回true更新界面
shouldComponentUpdate() {
//这个地方打印的状态是上一次的,在render中打印的才是正确的,因为这个地方state还没进行赋值
// const { userInfo, loginState, description } = this.props;
// console.log('页面收到更新,登录状态为:' + loginState + "\t登录描述为:" + description);
console.log("shouldComponentUpdate");
return true;
}
通过这个方法返回true或者false来进行控制页面是否需要重新render页面。需要注意的是回调这个方法时,只是监听到了state变化,而不是state已近变化。所以如果在这个方法内打印相关state变量,就会打印上一次的state值。
第八步:清理store
清理store也是通过发送action预事件,在reducer中改变state值,达到清理数据的目的,如:
//清理store中缓存的数据
export function clearData(){
console.log('清除信息');
return dispatch=>{
dispatch(clearDataInfo(constants.CLEAR_DATA));
}
}
//在reducer中的处理为:
case constants.CLEAR_DATA:
return {
loginState: action.type,
userInfo: null,
description: ""
};
在页面中一般是在componentWillUnmount销毁页面的周期中使用,当然也可以在需要的地方使用:
componentWillUnmount() {
console.log('页面销毁');
descriptions = '登录状态:未登录';
userInfos = '登录信息:没有登录信息';
//清理store中缓存的数据 不清理再次进入的时候还会展示上一次的数据
this.props.clearData();
}
如果不清理,下次进入的时候store还是存储的最后一次的数据,不过这样的清理方式终究太麻烦,最好的方式就是写一个公共的action事件,网上应该有更多比较好的处理方式。
如果把store、reducer、action、component写在一个文件内,可能更好理解一点,如下:
import React, { Component } from 'react';
import { Provider, Button } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunkMiddleware from 'redux-thunk';
import *as types from '../src/constants/counterTypes';
//组件
class Counter extends Component {
render() {
const { value, addCount } = this.props;
return (
<Button title='发送action'
onPress={addCount}
/>
);
}
}
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
)
}
}
const reducers = {
counter: counter
}
//合并多个reducer
const composedReducer = combineReducers(reducers)
//创建store
const store = createStore(
composedReducer,
applyMiddleware(thunkMiddleware)
);
//action预处理事件
const increaseAction = { type: 'increase' };
//关联state
function mapStateToProps(state) {
return {
value: state.count
};
}
//关联方法 映射到组件中
function mapDispatchToProps(dispatch) {
return {
addCount: () => dispatch(increaseAction)
};
}
//连接组件
let App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
//reducer处理
function counter(state = { count: 0 }, action) {
switch (action.type) {
case 'increase':
return {
//…state就是展开操作符,这里这么用的作用是返回新的对象,不修改原来的对象
...state,
count: state.count + 1,
}
default:
// return state;
return state === undefined ? [] : state;
}
}
上面的这种全部写在一起只是为了更好的理解,在正式的项目中肯定是不能这样写的,正式项目中应该做好分包和命名,如下图:
QQ截图20190415123357.png使用过程中遇到的坑
坑1:Provider
必须用在应用的根元素外面,负责传递唯一的Store给应用,如果不是放在程序的根节点,而是放在程序内部的某一个js文件, 则会报错:
Invariant Violation: Invariant Violation: Could not find "store" in
the context of "Connect(ReduxTest)". Either wrap the root component
in a <Provider>, or pass a custom React context provider to
<Provider> and the corresponding React context consumer to Connect
(ReduxTest) in connect options.
坑2:
错误日志:
Error: Reducer "todos" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.
大体意思是reducer内的函数返回不能是undefined,所以需要这样处理:
、
function counter(state = initialState, action) {
switch (action.type) {
case types.INCREMENT:
return {
...state,
count: state.count + 1,
}
break;
case types.DECREMENT:
return {
...state,
count: state.count - 1,
}
break;
default:
//这里需要判断是否为undefined
// return state;
return state === undefined ? [] : state;
}
}
解决办法:地址
该文章只是为了更好的入门redux,而且redux还有更多的用法,后面有空的时候还是会写出来的。文章中有些描述是自己的理解,如果有错误,请一定要指出。
感谢
还有两个学习网址找不到了,抱歉。
网友评论