React Redux

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013003052/article/details/87907364


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}。

知识细节

  1. 原则一,Redux是单一数据源的,整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
  2. 原则二,State是只读的,唯一改变state的方法只有触发action,action是用来描述行为的已知对象。
  3. 原则三,使用reducers纯函数来执行修改state。
  4. 不要在reducer中修改传入参数。
  5. 不要在reducer中执行网络请求或者路由跳转。
  6. 不要在reducer中调用非纯函数,如Date.now()或Math.random()。
  7. reducer需要保持纯净,单纯执行计算。
  8. reducer中不修改state,使用Object.assign()创建副本来返回state。
  9. reducer中可以使用对象展开运算符,将一个对象的可枚举属性拷贝至另一个对象。
  10. reducer在default情况返回旧的state。
  11. Redux应用只有一个单一的store。
  12. connect()函数做了性能优化来避免很多不必要的重复渲染。(这样你就不必为了性能而手动实现React 性能优化建议中的shouldComponentUpdate方法。)
  13. state的默认值里字段不能为undefined,否则使用该reducer的组件中的props无法找到该字段。
展开阅读全文

没有更多推荐了,返回首页