React-Native从零搭建开发App(长篇全文)

2018-03-1119:00:30WEB前端开发Comments3,117 views字数 15105阅读模式

一、须知

全文技术栈文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

  • 核心库:React-Native@0.54.0
  • 路由导航:React-Native-Navigation
  • 状态管理:Redux、Redux-Thunk、Redux-Saga、Redux-persist
  • 静态测试:Flow

本文适合有对React家族有一定使用经验,但对从零配置一个App不是很熟悉,又想要从零体验一把搭建App的同学。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

我自己就是这种情况,中途参与到项目中,一直没有掌控全局的感觉,所以这次趁着项目重构的机会,自己也跟着从零配置了一遍,并记录了下来,希望能跟同学们一起学习,如果有说错的地方,也希望大家指出来,或者有更好的改进方式,欢迎交流。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

如果有时间的同学,跟着亲手做一遍是最好的,对于如何搭建一个真实项目比较有帮助。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

整个项目已经上传到github,懒的动手的同学可以直接clone下来跟着看,欢迎一起完善,目前的初步想法是对一部分的同学有所帮助,后面有时间的话,可能会完善成一个比较健壮的RN基础框架,可以直接clone就开发项目那种文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

该项目github仓库传送门文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

这里对每个库或者内容只做配置和基础用法介绍文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

物理环境:mac,xcode文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

window系统的同学也可以看,不过需要自己搞好模拟器开发环境文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

二、快速建立一个RN App

React-native官网文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

如果RN的基础配置环境没有配置好,请点击上方链接到官网进行配置文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

react-native init ReactNativeNavigationDemo
cd ReactNativeNavigationDemo
react-native run-ios

因为一开始就计划好了用React-Native-Navigation作为导航库,所以名字起得长了点,大家起个自己喜欢的吧文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

成功后会看到这个界面文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

这时候可以看下目录结构,RN自动集成了babel、git、flow的配置文件,还是很方便的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

三、路由导航:React-Native-Navigation

React Native Navigation文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

为什么用React Native Navigation而不用React Navigation ?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

它是目前唯一一款使用原生代码来实现navigator的插件,使用后navigator的push/pop的动画将脱离js线程而改由原生的UI线程处理, 切屏效果会和原生态一样流畅, 再也不会出现由于js线程渲染导致的navigator切屏动画的卡顿效果了, 并且该插件还同时内置实现了原生态版本的tabbar文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

英文好的同学看着官方文档配就可以了,实在看不懂的可以对照着我下面的图看。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

iOS的需要用到xcode,没做过的可能会觉得有点复杂,所以我跑了一遍流程并截图出来了 至于android的配置,文档写的很清晰,就不跑了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

1、安装

yarn add react-native-navigation@latest

2、添加xcode工程文件

图中的路径文件是指./node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

3、把上面添加的工程文件添加到库中

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

4、添加路径

$(SRCROOT)/../node_modules/react-native-navigation/ios记得图中第5点设置为recursive文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

5、修改ios/[app name]/AppDelegate.m文件

把整个文件内容替换成下面代码文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "RCCManager.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;

#ifdef DEBUG
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
   jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  self.window.backgroundColor = [UIColor whiteColor];
  [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions];

  return YES;
}

@end

6、基础使用

1、先新建几个页面,结构如图文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

cd src
mkdir home mine popularize
touch home/index.js mine/index.js popularize/index.js

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

每个index.js文件里面都是一样的结构,非常简单文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import React, { Component } from 'react';
import { Text, View } from 'react-native';


type Props = {};
export default class MineHome extends Component<Props> {
  
  render() {
    return (
      <View>
        <Text>MineHome</Text>
      </View>
    );
  }
}

2、src/index.js 注册所有的页面,统一管理文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { Navigation } from 'react-native-navigation';

import Home from './home/index';
import PopularizeHome from './popularize/index';
import MineHome from './mine/index';

// 注册所有的页面
export function registerScreens() {
  Navigation.registerComponent('home',() => home);
  Navigation.registerComponent('popularize',() => popularize);
  Navigation.registerComponent('mine',() => mine);
}

在这里先插一句,如果要引入Redux的话,就在这里直接传入store和Provider文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

export function registerScreens(store,Provider) {
    Navigation.registerComponent('home',() => PageOne,store,Provider)
}

3、App.js 文件修改app的启动方式,并稍微修改一下页面样式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { Navigation } from 'react-native-navigation';
import { registerScreens } from './src/screen/index';

// 执行注册页面方法
registerScreens();

// 启动app
Navigation.startTabBasedApp({
  tabs: [
    {
      label: 'home',
      screen: 'home',
      title: '首页',
      icon: require('./src/assets/home.png'),
    },
    {
      screen: 'popularize',
      title: '推广',
      icon: require('./src/assets/add.png'),
      iconInsets: {
        top: 5, 
        left: 0,
        bottom: -5, 
        right: 0
      },
    },
    {
      label: 'mine',
      screen: 'mine',
      title: '我',
      icon: require('./src/assets/mine.png'),
    }
  ],
  appStyle: {
    navBarBackgroundColor: '#263136',//顶部导航栏背景颜色
    navBarTextColor: 'white'//顶部导航栏字体颜色
  },
  tabsStyle: {
    tabBarButtonColor: '#ccc',//底部按钮颜色
    tabBarSelectedButtonColor: '#08cb6a',//底部按钮选择状态颜色
    tabBarBackgroundColor: '#E6E6E6'//顶部条背景颜色
  }
});

启动App,目前模拟器能看到的界面文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

7、页面跳转和传递参数

在screen/home文件夹下面新建一个NextPage.js文件,记得到src/screen/index.js里面注册该页面文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

Navigation.registerComponent('nextPage', () => NextPage, store, Provider);

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

然后在src/screen/home/index.js文件里面加一个跳转按钮,并传递一个props数据文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

四、状态管理:Redux

redux中文文档文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

1、初始化

1、安装文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

yarn add redux react-redux

2、目录构建文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

目前有以下两种常见的目录构建方式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

一是把同一个页面的action和reducer写在同一个文件夹下面(可以称之为组件化),如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

二是把所有的action放在一个文件夹,所有的reducer放在一个文件夹,统一管理文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

这两种方式各有好坏,不在此探究,这里我用第二种文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

一通操作猛如虎,先建立各种文件夹和文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

cd src
mkdir action reducer store
touch action/index.js reducer/index.js store/index.js
touch action/home.js action/mine.js action/popularize.js
touch reducer/home.js reducer/mine.js reducer/popularize.js

以上命令敲完后,目录结构应该长下面这样,每个页面都分别拥有自己的action和reducer文件,但都由index.js文件集中管理输出文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

关于创建这三块内容的先后顺序,理论上来说,应该是先有store,然后有reducer,再到action文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

但写的多了之后,就比较随心了,那个顺手就先写哪个。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

按照我自己的习惯,我喜欢从无写到有,比如说 store里面要引入合并后的reducer,那我就会先去把reducer给写了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import combinedReducer from '../reducer'

但写reducer之前,好像又需要先引入action,所以我由可能跑去先写action文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

这里不讨论正确的书写顺序,我就暂且按照自己的习惯来写吧文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

3、action文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

我喜欢集中管理的模式,所以所有的antion我都会集中起来 index.js文件作为总的输出口文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

这里定义了所有的action-type常量文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

// home页面
export const HOME_ADD = 'HOME_ADD';
export const HOME_CUT = 'HOME_CUT';

// mine页面
export const MINE_ADD = 'MINE_ADD';
export const MINE_CUT = 'MINE_CUT';

// popularize页面
export const POPULARIZE_ADD = 'POPULARIZE_ADD';
export const POPULARIZE_CUT = 'POPULARIZE_CUT';

然后去写其他各自页面的action.js文件,这里只以home页面作为例子,其他页面就不写了,打开action/home.js文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import * as actionTypes from './index';

export function homeAdd(num) {
  return {
    type: actionTypes.HOME_ADD,
    num
  }
}

export function homeCut(num) {
  return {
    type: actionTypes.HOME_CUT,
    num
  }
}

最是返回了一个最简单的action对象文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

4、reducer文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

先写一个home页面的reducer,打开reducer/home.js文件 其他页面也同理文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import * as actionTypes from '../action/index';

// 初始state,我先随手定义了几个,后面可能会用到
const initState = {
  initCount: 0,
  name: '',
  age: '',
  job: ''
}

export default function count(state = initState, action) {
  switch (action.type) {
    case actionTypes.HOME_ADD:
      return {
        ...state,
        ...action.initCount: 
      }
    case actionTypes.HOME_CUT: 
      return {
        ...state,
        ...action.initCount
      }
    default:
      return state;
  }
}

然后把所有子reducer页面合并到reducer/index.js文件进行集中输出文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import homeReducer from './home';
import popularizeReducer from './popularize';
import mineReducer from './mine';

const combineReducers = {
  home: homeReducer,
  popularize: popularizeReducer,
  mine: mineReducer
}

export default combineReducers

5、创建store文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

创建好reducer之后,打开store/index.js文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import {createStore } from 'redux';

import combineReducers from '../reducer/index';

const store = createStore(combineReducers)

export default store;

就是这么简单文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

6、store注入文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

使用过redux的同学都知道,react-redux上场了,它提供了Provider和connect方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

前面有提到react-native-navigation注入redux的方式,其实差不多 但需要每个子页面都注入store、Provider文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

src/index.js修改如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { Navigation } from 'react-native-navigation';

import Home from './home/index';
import PopularizeHome from './popularize/index';
import MineHome from './mine/index';

// 注册所有的页面
export function registerScreens(store, Provider) {
  Navigation.registerComponent('home', () => Home, store, Provider);
  Navigation.registerComponent('popularize', () => PopularizeHome, store, Provider);
  Navigation.registerComponent('mine', () => MineHome, store, Provider);
}

App.js修改执行页面注册的方法即可文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { Provider } from 'react-redux';
import store from './src/store/index';

// 执行注册页面方法
registerScreens(store, Provider);

2、体验Redux

现在来体验一下redux,打开src/screen/home/index.js文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import两个方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

导入action文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import * as homeActions from '../../action/home';

定义两个方法,并connect起来文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

现在在页面上打印initCount来看一下,只要被connect过的组件,以及从该组件通过路由push跳转的子页面都可以通过this.props拿到数据文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

src/screen/home/index.js完整代码如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as homeActions from '../../action/home';

type Props = {};
class Home extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <Text>Home</Text>
        <Text>initCount: {this.props.home.initCount}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#ccc',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  }
})

function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

从页面可以看到,已经读到状态树里面的数据了,initCount为0文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

让我们再来试一下action的加法和减法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

src/screen/home/index.js完整代码如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as homeActions from '../../action/home';

type Props = {};
class Home extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <Text>Home</Text>
        <Text>initCount: {this.props.home.initCount}</Text>

        <TouchableOpacity
          style={styles.addBtn}
          onPress={() => {
            this.props.homeActions.homeAdd({
              initCount: this.props.home.initCount + 2
            });
          }}
        >
          <Text style={styles.btnText}>加2</Text>
        </TouchableOpacity>

        <TouchableOpacity
          style={styles.cutBtn}
          onPress={() => {
            this.props.homeActions.homeCut({
              initCount: this.props.home.initCount - 2
            });
          }}
        >
          <Text style={styles.btnText}>减2</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#ccc',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  addBtn: {
    backgroundColor: 'green',
    marginVertical: 20,
    width: 200,
    height: 59,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10
  },
  cutBtn: {
    backgroundColor: 'red',
    width: 200,
    height: 59,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10
  },
  btnText: {
    fontSize: 18,
    color: 'white'
  }
});

function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

现在点击两个按钮都应该能得到反馈文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

现在再来验证下一个东西,这个页面改完store里面的状态后,另一个页面mine会不会同步,也就是全局数据有没有共享了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

src/mine/index.js文件修改如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as homeActions from '../../action/home';

type Props = {};
class MineHome extends Component<Props> {
  
  render() {
    return (
      <View>
        <Text>initCount: {this.props.home.initCount}</Text>
      </View>
    );
  }
}


function mapStateToProps(state) {
  return {
    home: state.home
  };
}

function mapDispatchToProps(dispatch) {
  return {
    homeActions: bindActionCreators(homeActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(MineHome);

在该页面上读取同一个数据this.props.home.initCount,然后在第一个页面home上加减数据,再看mine页面,会发现initCount也同步变化 也就是说明:我们已经在进行状态管理了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

到这里是不是很开心,redux虽然有点绕,但如果跟着做下来应该也有了一定的轮廓了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

五、状态跟踪:Redux-logger

这个时候,我们会发现,虽然状态共享了,但目前还没有办法跟踪状态,以及每一步操作带来的状态变化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

但总不能每次都手动打印状态到控制台里面吧?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

redux-logger该上场了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

它大概长下面这样,把每次派发action的前后状态都自动输出到控制台上文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

具体使用看下官方文档,很简单,直接上代码吧文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

redux-logger文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

安装文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

yarn add redux-logger

它作为一个中间件,中间件的用法请回 redux中文文档 查阅文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

store/index.js文件修改如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { createStore, applyMiddleware } from 'redux';

import combineReducers from '../reducer/index';
import logger from 'redux-logger';

const store = createStore(combineReducers, applyMiddleware(logger));

export default store;

command+R刷新一下模拟器,再点击一下+2,看看控制台是不是长下面这样?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

React-Native从零搭建开发App(长篇全文)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

接下来每次派发action,控制台都会自动打印出来,是不是省心省事?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

六、异步管理:Redux-Thunk

基础理解

redux-thunk是什么请移步 redux-thunk github文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

出发点:需要组件对同步或异步的 action 无感,调用异步 action 时不需要显式地传入 dispatch文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

通过使用指定的 middleware,action 创建函数除了返回 action 对象外还可以返回函数。这时,这个 action 创建函数就成为了 thunk文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

当 action 创建函数返回函数时,这个函数会被 Redux Thunk middleware 执行。这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样 > thunk 的一个优点是它的结果可以再次被 dispatch文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

安装

yarn add redux-thunk

注入store

作为一个中间件,它的使用方式和上面logger一样,stroe/index.js直接引入即可文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import thunk from 'redux-thunk';
middleware.push(thunk);

使用方式

action/home.js文件修改如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import post from '../utils/fetch';

export function getSomeData() {
  return dispatch => {
    post('/get/data',{}, res => {
      const someData = res.data.someData;
      dispatch({
        type: actionTypes.HOME_GET_SOMEDATA,
        someData
      })
    })
  }
}

题外话:封装请求函数post

此处稍微插入一句,关于封装请求函数post(以下是精简版,只保留了核心思想)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

cd src
mkdir utils
touch utils/fetch.js

公用的方法和函数都封装在utils文件夹中文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

utils/fetch.js文件如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

export default function post(url, data, sucCB, errCB) {
  // 域名、body、header等根据各自项目配置,还有部分安全,加密方面的设置,
  const host = 'www.host.com';
  const requestUrl = `${host}/${url}`;
  const body = {};
  const headers = {
    'Content-Type': 'application/json',
    'User-Agent': ''
  };
    // 用的是fetch函数
  fetch(requestUrl, {
    method: 'POST',
    headers: headers,
    body: body
  }).then(res => {
    if (res && res.status === 200) {
      return res.json();
    } else {
      throw new Error('server');
    }
  }).then(res => {
    // 精简版判断
    if(res && res.code === 200 && res.enmsg === 'ok') {
      // 成功后的回调
      sucCB(res);
    }else {
      // 失败后的回调
      errCB(res);
    }
  }).catch(err => {
    // 处理错误
  })
}

七、异步管理:Redux-Saga

基本概念请移步文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

自述 | Redux-saga 中文文档文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

出发点:需要声明式地来表述复杂异步数据流(如长流程表单,请求失败后重试等),命令式的 thunk 对于复杂异步数据流的表现力有限文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

安装

yarn add redux-saga

创建saga文件

创建顺序有点像reducer 我们先创建saga相关文件夹和文件,最后再来注入store里面文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

cd src
mkdir saga
touch saga/index.js saga/home.js saga/popularize.js saga/mine.js

先修改saga/home.js文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { put, call, takeLatest } from 'redux-saga/effects';

import * as actionTypes from '../action/index';
import * as homeActions from '../action/home';
import * as mineActions from '../action/mine';

import post from '../utils/fetch';

function getSomeThing() {
  post('/someData', {}, res => {}, err => {});
}

// 这个函数中的请求方法都是随手写的,没引入真实API,
function* getUserInfo({ sucCb, errCB }) {
  try {
    const res = yield call(getSomeThing());
    const data = res.data;
    yield put(homeActions.getSomeData())
    yield put(homeActions.setSomeData(data))
    yield call(sucCb);
  } catch (err) {
    yield call(errCB, err);
  }
}

export const homeSagas = [
  takeLatest(actionTypes.HOME_GET_SOMEDATA, getUserInfo)
]

saga/mine.js文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

export const mineSagas = []

saga/popularize.js文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

export const popularizeSagas = []

saga/index.js文件作为总输出口,修改如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { all } from 'redux-saga/effects';

import { homeSagas } from './home';
import { mineSagas } from './mine';
import { popularizeSagas } from './popularize';

export default function* rootSaga() {
  yield all([...homeSagas, ...mineSagas, ...popularizeSagas]);
}

把saga注入store

store/index.js文件修改文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import createSagaMiddleware from 'redux-saga';
import rootSaga from '../saga/index';
// 生成saga中间件
const sagaMiddleware = createSagaMiddleware(rootSaga);

middleware.push(sagaMiddleware);

八:数据持久化:Redux-persist

GitHub - rt2zz/redux-persist: persist and rehydrate a redux store文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

顾名思义,数据持久化,一般用来保存登录信息等需要保存在本地的数据文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

因为store中的数据,在每次重新打开app后,都会回复到reducer中的initState的初始状态,所以像登录信息这种数据就需要持久化的存储了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

RN自带的AsyncStorage可以实现这个功能,但使用起来比较繁琐,而且没有注入到store中去,没办法实现统一状态管理,所以redux-persist就出场了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

安装

yarn add redux-persist

注入store

store/index.js文件完整代码如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage';

import combineReducers from '../reducer/index';
import rootSaga from '../saga/index';

const persistConfig = {
  key: 'root',
  storage,
  // 白名单:只有mine的数据会被persist
  whitelist: ['mine']
};
// 对reducer数据进行persist配置
const persistReducer = persistCombineReducers(persistConfig, combineReducers);

const sagaMiddleware = createSagaMiddleware();

// 中间件
const createStoreWithMiddleware = applyMiddleware(
  thunk,
  sagaMiddleware,
  logger
)(createStore);

const configuerStore = onComplete => {
  let store = createStoreWithMiddleware(persistReducer);
  let persistor = persistStore(store, null, onComplete);
  sagaMiddleware.run(rootSaga);
  return { persistor, store };
};

export default configuerStore;

这个地方,不再把middleware当做数组,而是直接写入applyMiddleware方法中文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

store也不再直接导出,而是到处一个生成store的函数configuerStore 相应,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

App.js文件的引入也要修改一点点文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

import configuerStore from './src/store/index';
const { store } = configuerStore(() => { });

九:静态测试:Flow

待更新,这有一篇链接可以先看React Native填坑之旅--Flow篇(番外)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

十、后话

到目前为止,我们已经引入了redux-logger、redux-thunk、redux-saga、redux-persist文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

核心开发代码库已经配置完毕了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

项目已经传上github,欢迎star文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

该项目github仓库传送门文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

接下来还有一些可以作为开发时的辅助性配置,比如Flow 、Babel(RN初始化时已经配好了)、Eslint等等文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

另外,既然是App,那最终目的当然就是要上架App Store和各大安卓市场,后面可能还会分享一下关于极光推送jPush、热更新CodePush、打包上传审核等方面的内容。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

作者:amandakelake
链接:https://juejin.im/post/5a9f93d96fb9a028d2077c19
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/1680.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/1680.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定