欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

React Redux

程序员文章站 2024-01-20 15:41:40
...


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

React Redux

使用

与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);

运行

React Redux

上面的例子你或许会有些疑惑的地方:

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>
    );
  }
}

运行

React Redux

上面的例子你或许会有些疑惑的地方:

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无法找到该字段。