文章目录
ReactNative系列-文章
React Redux
是什么?
- Redux是javascript状态容器,提供可预测化的状态管理。
作用, 好处?
- 前端单页面应用越来越复杂,需要很多state状态,Redux可以让这些state的变化可预测。
安装
$ npm install --save redux
$ npm install --save react-redux
react-redux是redux针对react的绑定库。
基础
在使用Redux之前,有一些核心概念需要理解,它有三个重要的组成部分,分别是Action, Reducer, Store。
Action
Action是一个普通对象,定义了传递给Reducer的数据结构。当我们需要保存数据时,将先从它发起,一般会通过store.dispatch(action)将action传到Store(到Store之前先经过Reducer)。
Reducer
Reducer指定了如何响应Action,并发送到store。一般会在这里判断action对象中的类型,然后去更新state。当它将state更新并返回时,即是发送给了Store。
Store
在Redux应用中,所有的state都被保存在一个单一对象中,这个单一对象就是Store。Store维持着应用的state,它提供getState()方法获取state,以及提供dispatch(action)的方法更新state。
流程图
store.dispatch(action) -> reducer process -> reducer return new state -> store update store.state -> render
使用
与ReactNative结合简单使用
下面做一个加减法的demo简单使用演示。创建一个react-native的helloworld项目:
$ react-native init helloworld
将redux库依赖进来:
$ cd helloworld
$ npm install redux react-redux --save
先创建action,在项目根目录下创建/src/redux/actions/index.js
export const increase = value => ({
type: 'INC',
value
})
export const descrease = value => ({
type: 'DES',
value
})
然后创建reducer,在项目根目录下创建/src/redux/reducers/CounterReducer.js
const initState = {
totalValue: 0,
numbers: 0
}
const CounterReducer = (state = initState, action) => {
switch(action.type) {
case 'INC':
return Object.assign({}, state, {
totalValue: state.totalValue + action.value,
numbers: state.numbers + 1
});
case 'DES':
return Object.assign({}, state, {
totalValue: state.totalValue - action.value,
numbers: state.numbers + 1
});
default:
return state;
}
}
export default CounterReducer;
接着编写combineReducers辅助合并所有的Reducers,创建/src/redux/reducers/index.js
import { combineReducers } from 'redux';
import CounterReducer from './CounterReducer';
export default combineReducers({
CounterReducer
});
最后创建store,位于App.js,将其改造为:
import React, {Component} from 'react';
import MainScreen from './src/screens/MainScreen';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './src/redux/reducers';
export default class App extends Component {
render() {
const store = createStore(rootReducer);
return (
<Provider store={store}>
<MainScreen/>
</Provider>
);
}
}
MainScreen位于/src/screens/MainScreen.js,创建它
import React from 'react';
import { StyleSheet, View, Text, Button } from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { increase, descrease } from '../redux/actions';
class MainScreen extends React.Component {
static propTypes = {
totalValue: PropTypes.number.isRequired,
numbers: PropTypes.number.isRequired,
inc: PropTypes.func.isRequired,
des: PropTypes.func.isRequired
}
render() {
return(
<View style={styles.container}>
<Text style={styles.text}>{this.props.totalValue}</Text>
<Button title="inc" onPress={()=>{
this.props.inc(1);
}}/>
<Button title="des" onPress={()=>{
this.props.des(1);
}}/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center'
},
text: {
color: 'black'
}
})
const mapStateToProps = (state, ownProps) => ({
totalValue: state.CounterReducer.totalValue,
numbers: state.CounterReducer.numbers
});
const mapDispatchToProps = dispatch => ({
inc: value => dispatch(increase(value)),
des: value => dispatch(descrease(value))
});
export default connect(mapStateToProps, mapDispatchToProps)(MainScreen);
运行
上面的例子你或许会有些疑惑的地方:
createStore作用
通过const store = createStore(rootReducer)函数,创建一个Redux Store。在一个应用程序中,一般只有一个store,它将持有state树,改变state数据的唯一方式是通过store.dispatch(action)方法。
Provider的作用
<Provider store={store}>…</Provider>,将store赋给Provider组件的props中,Provider组件下的子组件可以调用connect()方法,来访问Redux Store。
connect()函数
Provider下的子组件调用connect()可以访问到store,它接受4个参数,connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])。
mapStateToProps(state, ownProps) 方法允许我们将store中的数据作为props绑定到组件中,只要store更新了就会调用mapStateToProps方法,该对象中的值将会更新到组件props中,然后界面数据得到更新,mapStateToProps返回的结果必须是object对象。
mapDispatchToProps(dispatch, [ownProps]) 第二个参数允许我们将action作为props绑定到组件中,mapDispatchToProps希望你返回包含对应action的object对象。这样我们就可以在props中直接去调用dispatch(action)方法。
combineReducers
将多个reducer合并为单个reducer函数,它将调用每个子reducer,并将其结果收集到一个状态对象中。所以在mapStateToProps中获取state时,需要注意相应reducer函数注册的键值。
Object.assign()
Object.assign创建一个副本,使用redux需要注意的是:1. 不要修改state,使用Object.assign创建副本返回;2. 在default情况下返回旧的state。
与网络请求结合使用
redux 与网络请求结合使用通用的方式是,使用异步的action。异步action可以利用redux-thunk这个中间件来辅助完成。
我们另外新建一个demo项目,用来演示:
$ react-native init reduxfetch
添加依赖库
$ cd reduxfetch
$ npm install redux react-redux redux-thunk --save
先创建action, 在项目根目录下新建/src/redux/actions/index.js
const requestGet = url => {
return {
type: 'GET',
value: url
}
}
const requestDone = respond => {
return {
type: 'DONE',
value: respond
}
}
const requestError = e => {
return {
type: 'ERROR',
value: e.message
}
}
// thunk action 创建函数
export const fetchGet = url => {
// Thunk middleware 知道如何处理下面的函数
return dispatch => {
dispatch(requestGet(url));
return fetch(url)
.then(res => {
return res.json();
})
.then(json => {
setTimeout(() => {
dispatch(requestDone(JSON.stringify(json)));
}, 2000);
})
.catch(e => {
dispatch(requestError(e));
})
}
}
然后创建reducer,新建/src/redux/reducers/RequestReducer.js
const initState = {
loading: false,
error: '',
url: '',
respond: ''
}
const RequestReducer = (state = initState, action) => {
switch(action.type) {
case 'GET':
return {
...state,
loading: true,
error: '',
url: action.value,
respond: ''
}
case 'DONE':
return {
...state,
loading: false,
error: '',
respond: action.value
}
case 'ERROR':
return {
...state,
loading: false,
error: action.value,
respond: ''
}
default:
return state;
}
}
export default RequestReducer;
新建/src/redux/reducers/index.js
import { combineReducers } from 'redux';
import RequestReducer from './RequestReducer';
const rootReducer = combineReducers({
RequestReducer
})
export default rootReducer;
然后创建一个界面用于展示,新建/src/screens/MainScreen.js
import React, {Component} from 'react';
import {StyleSheet, Text, View, Button, ScrollView, RefreshControl} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchGet } from '../redux/actions';
class MainScreen extends Component {
static propTypes = {
loading: PropTypes.bool.isRequired,
error: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
respond: PropTypes.string.isRequired,
fetchGet: PropTypes.func.isRequired
}
render() {
return (
<View style={styles.container}>
<Button title="请求" onPress={() => {
this.props.fetchGet('http://file.qdc.wiki/yasi/audioList.json');
}}/>
<ScrollView style={styles.instructions}
refreshControl={
<RefreshControl refreshing={this.props.loading}/>
}>
<Text style={{color:'black'}}>
{
'请求:' + this.props.url + '\n' +
'响应:' + this.props.respond + '\n' +
'错误:' + this.props.error
}
</Text>
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: 'white',
paddingTop: 40
},
instructions: {
flex: 1
},
});
const mapStateToProps = (state, ownProps) => {
return {
loading: state.RequestReducer.loading,
error: state.RequestReducer.error,
url: state.RequestReducer.url,
respond: state.RequestReducer.respond,
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
fetchGet: url => dispatch(fetchGet(url))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainScreen);
最后,创建一个store,将App.js改造一下
import React, {Component} from 'react';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import MainScreen from './src/screens/MainScreen';
import rootReducer from './src/redux/reducers';
const middleware = [ thunk ];
const store = createStore(
rootReducer,
applyMiddleware(...middleware)
)
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<MainScreen/>
</Provider>
);
}
}
运行
上面的例子你或许会有些疑惑的地方:
RequestReducer中的…state
我们原来在reducer中返回state为副本,可以通过Object.assign()方式来创建副本,当然也可以使用ES7提案的对象展开运算符来创建副本,它的一般型式是{…state, …newState}。
知识细节
- 原则一,Redux是单一数据源的,整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- 原则二,State是只读的,唯一改变state的方法只有触发action,action是用来描述行为的已知对象。
- 原则三,使用reducers纯函数来执行修改state。
- 不要在reducer中修改传入参数。
- 不要在reducer中执行网络请求或者路由跳转。
- 不要在reducer中调用非纯函数,如Date.now()或Math.random()。
- reducer需要保持纯净,单纯执行计算。
- reducer中不修改state,使用Object.assign()创建副本来返回state。
- reducer中可以使用对象展开运算符,将一个对象的可枚举属性拷贝至另一个对象。
- reducer在default情况返回旧的state。
- Redux应用只有一个单一的store。
- connect()函数做了性能优化来避免很多不必要的重复渲染。(这样你就不必为了性能而手动实现React 性能优化建议中的shouldComponentUpdate方法。)
- state的默认值里字段不能为undefined,否则使用该reducer的组件中的props无法找到该字段。