开发 React Native APP —— 从改造官方Demo开始

栏目: 服务器 · 发布时间: 7年前

本文步骤参考L小庸的文章,https://juejin.im/post/5a9602745188257a5c609b2f, 感谢L小庸

RN的生态圈很火爆,但是很难找到一个开箱即用的 React Native APP Demo。目前存在的 Demo 要么过于简单,比如 React Native 官网提供的 Demo AwesomeProject ,这个 Demo 只提供了最简功能,对于路由(导航组件)、状态管理等并没有涉及。虽然 React Native 教程中对于复杂应用应如何选择组件及第三方库都有提及,但并没有给出完整示例。还有一些demo可能版本比较旧,对于新手来说,语法和代码组织方式都有变化,结合官方api看的话,会比较懵逼,哪哪对不上的赶脚。而另一方面,又有很多 React Native APP 虽已开源,但都是用于特定场合的完整 APP,有些 APP 的目录结构本身就不友好,并且也没有完整的说明文档。 其次,相对于vue,React 本身的学习曲线就相对陡峭,尤其涉及状态管理部分,很难找到可以直接 copy-paste 的代码,除此之外原生 App 本身还有很多区别于 web 的需求。

找了很多demo,L小庸的demo真的很棒,个人没有直接download小庸的github代码,基本上市按照步骤自己敲或者copy一部分代码,先让整个demo可以跑起来,再慢慢研究相关的功能和语法,API等,虽然敲的过程也遇到了很多麻烦,运行不起来等问题,但全程撸完一遍代码,有一个比较完整的demo实现,也算有一点点成就感。

个人的代码还未更新到github上,按照本文的步骤,step by step就运行起来。后续会掺杂一些对某部分内容的额外的理解或更多使用场景的demo,在代码里会写比较详细的注释,后续都更新到github上。

鉴于以上原因,所以决定写篇文章记录下学习过程,再次感谢L小庸的文章和demo, 内容较多,这部分主要内容为:

  • react navigation 作为路由(导航)组件的初步使用 
  • 自定义组件 
  • 通过 fetch API 发送网络请求 
  • 集成 redux,并实现 redux 状态的持久化存储

一 准备工作

使用自己喜欢的编辑器,安装RN相关插件,个人使用的sublime text3,配置了插件后,使用起来也还是不比较顺手的。

二 官方 Demo 下载及介绍

官方 demo 虽然不完整,但却是一个很好的开始。介绍完官方 Demo(包括环境配置),后文会一步步介绍如何从这个不完整的官方 Demo 改造成可用于生产的 APP。

2.1 环境配置

下载官方 Demo:AwesomeProject,然后运行。

所需的环境配置官方文档讲的很清楚,这里不在赘述。需要指出的是 React Native 对于运行 Demo 提供了两种方法:一种是在 Expo 客户端中运行,另一种是编译成原生代码(安卓编译成 Java,iOS 编译成 objective-C)后在模拟器或者在真机上运行。推荐直接使用第二种,如果想发布 APP 这也是绕不过去的。

如果之前没有开发过原生 APP,还需要熟悉下原生 APP 的开发工具:安卓使用 Android Studio,iOS 使用 Xcode。它们如何配合 React Native 使用在 官方文档有说明,遇到问题自行谷歌一般都有解决方案。

需要说明的是 Android Studio 很多依赖更新需要访问谷歌服务,所以请自备梯子。

这段完全copy自L小庸的文章,个人没有mac,所以很多细节并不了解,也先记录着,方便后续采坑参考。

2.2 官方 Demo 目录介绍

开发 React Native APP —— 从改造官方Demo开始

上面的目录结构说明如下,重要的有:

android/
ios/
index.js
 App.js

上面是最重要的四个目录/文件,其他说明如下:  

app.json
package.json
node_modules
yarn.lock

3 配置路由

这里使用 react navigation 管理路由,大而全的介绍或者原理说明不是这部分的重点,这里主要讲怎么用。

react navigation 常用 API 有三个:

createStackNavigator
createTabNavigator
DrawerNavigator

最为常用的是前两个,demo中也只用到了前两个。

PS:需要注意的, react navigation 不同版本 的方法名可能不同,本人在敲L小庸的代码时,安装了依赖后各种跑不起来,如下的图困扰了很久,由于是新手,完全不知道错在哪里,仔细查看api,尝试使用createStackNavigator后终于运行成功,有点坑~官方好像也没有地方说明版本升级的变化~~还是要仔细看api文档~~不过这火爆的生态圈,版本升级,连方法名都换了,如果用于生产环境,个人感觉坑很大…………

开发 React Native APP —— 从改造官方Demo开始

3.1 createStackNavigator实现页面间跳转

首先我们要调整下目录结构 ,调整后的结构如下:

开发 React Native APP —— 从改造官方Demo开始

  • src/    放置所有原始的 react native 代码
  • config/    配置文件,比如路由配置
  • route.js   路由配置文件
  • screens/   所有页面文件
  • ScreenHome/ 这个目录是放具体页面文件的,为了进一步进行代码分离,里面又分为三个文件: index.js 中包含逻辑部分, style.js 中包含样式部分; view.js 中包含视图或者说页面元素部分。其他页面文案结构与此相同。

注意页面文件的命名方式:大驼峰命名法,react native 推荐组件命名用大驼峰命名法,每个页面相当于一个组件。

1)首先配置路由:路由文件 route.js 此时内容如下,这也是 createStackNavigator 最简单的使用方式,我的demo里使用的是简写的方式配置的路由,关于是否可以使用简写以及区别,还没有看的特别明白(https://reactnavigation.org/docs/en/hello-react-navigation.html),后面看明白了再补上

/**
 * route.js
 */

// 引入依赖
import React, { Component } from 'react'
import { createStackNavigator, createAppContainer } from 'react-navigation'

//引入页面组件
import ScreenHome from "../screens/ScreenHome";
import ScreenSome1 from '../screens/ScreenSome1'


// 配置路由
const navigator = createStackNavigator({
  ScreenHome: { screen: ScreenHome }, 
  /*或者
  Home: {
    screen: HomeScreen
  }
  */
  ScreenSome1: { screen: ScreenSome1 }
})

const App = createAppContainer(navigator)

export default App复制代码

// App.js

import React, { Component } from 'react';
import App from './src/config/route'

export default class RootApp extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    // 渲染页面
    return <App />;
  }
}
复制代码
/**
 * ScreenHome/index.js
 */
import React, {Component} from 'react';
import view from './view'

export default class ScreenHome extends Component {
  // 自定义当前页面路由配置,后面用到的createBottomTabNavigator也使用这个对象中的属性  static navigationOptions = {
    title: '首页',
  };

  constructor(props) {
    super(props);
    this.navigation = props.navigation;
  }

  render() {
    return view(this);
  }
}

复制代码
// 引入依赖
import React, {Component} from 'react';
import {Text, View, Button} from 'react-native'

export default self => (
  <View>
    <Text style={{ fontSize: 36 }}>home</Text>
    <Button
      title="ScreenSome1"
      // 路由跳转
      onPress={() => self.navigation.navigate("ScreenSome1")}
    />
  </View>
);
复制代码

开发 React Native APP —— 从改造官方Demo开始

3.2 createTabNavigator实现页面底部 tab 切换

首先在 screens 目录下新建 ScreenBottomTab 页面,用于配置 TabNavigator 。每个 tab 对应一个页面,按需新建页面,并且新建的页面需要在 route.js 中进行配置,更新后的目录结构如下:

开发 React Native APP —— 从改造官方Demo开始

/**
 * ScreenTab1/index.js
 */
import React, {Component} from 'react';
import {Text, View, Button} from 'react-native'

export default class ScreenSome1 extends Component {
  // 自定义当前页面路由配置,后面用到的createBottomTabNavigator也使用这个对象中的属性  static navigationOptions = {
    // 设置 title
    title: "TAB1"
  };

  constructor(props) {
    super(props);
    this.navigation = props.navigation;
  }

  render() {
    return(
          <View>
	    <Text style={{ fontSize: 36 }}>TAB1</Text>
	  </View>
    );
  }
}
复制代码

1)没有 tab 图标的最简配置

此时只需要配置 ScreenBottomTab 里面的 index.js 文件就好,如下:

/**
 * ScreenBottomTab/index.js
 */
import  { createBottomTabNavigator } from 'react-navigation'

import ScreenHome from '../../screens/ScreenHome';
import ScreenTab1 from '../../screens/ScreenTab1';
import ScreenTab2 from '../../screens/ScreenTab2';
import ScreenTab3 from '../../screens/ScreenTab3';

const ScreenTab = createBottomTabNavigator(
  // 配置 tab 路由
  {
    ScreenHome: ScreenHome,
    ScreenTab1: ScreenTab1,
    ScreenTab2: ScreenTab2,
    ScreenTab3: ScreenTab3,
  },
  // 其他配置选项
  {
    tabBarPosition: "bottom"
  }
);

export default ScreenTab;复制代码

route.js

// 引入依赖
import { createStackNavigator, createAppContainer } from 'react-navigation'

// 引入页面组件
import ScreenBottomTab from '../screens/ScreenBottomTab';

// 配置路由
const navigator = createStackNavigator({
  ScreenBottomTab: ScreenBottomTab,
})

const App = createAppContainer(navigator)

export default App复制代码

效果如下:

开发 React Native APP —— 从改造官方Demo开始

2)自定义 tab 图标

tab 图标除了自定义外,还需要根据是否选中显示不同颜色,这可以通过配置 createBottomTabNavigator的 tabBarIcon 实现,修改的具体文件是 tab 对应页面的 index.js 文件。demo里只是为了展示功能,icon使用的是一个。

/**
 * ScreenHome/index.js
 */
import React, {Component} from 'react';
import { Image } from 'react-native'
import view from './view'

export default class ScreenHome extends Component {
  static navigationOptions = {
    title: '首页',
    tabBarIcon: ({ focused }) => {
      const icon = focused
        ? require('../../assets/images/tab_home_active.png')
        : require('../../assets/images/tab_home.png');
      return <Image source={icon} style={{ height: 22, width: 22 }} />;
    },
  };

  constructor(props) {
    super(props);
    this.navigation = props.navigation;
  }

  render() {
    return view(this);
  }
}
复制代码

效果如下:

开发 React Native APP —— 从改造官方Demo开始

四 自定义组件

五 网络请求

react native 使用上有个最大的好处是可以不用考虑新语法兼容性的问题,既然如此,自然使用设计更加优良的 API,在网络请求方面,本项目使用fetch API。

添加网络请求后目录结构调整如下:

开发 React Native APP —— 从改造官方Demo开始

5.1 配置 fetch api

/**
 * xgHttp.js
 */

// 请求服务器host
const host = "http://api.juheapi.com";

export default async function(
  method,
  url,
  { bodyParams = {}, urlParams = {} }
) {
  const headers = new Headers();
  headers.append("Content-Type", "application/json");

  // 将url参数写入URL
  let urlParStr = "";
  const urlParArr = Object.keys(urlParams);
  if (urlParArr.length) {
    Object.keys(urlParams).forEach(element => {
      urlParStr += `${element}=${urlParams[element]}&`;
    });
    urlParStr = `?${urlParStr}`.slice(0, -1);
  }

  const res = await fetch(
    new Request(`${host}${url}${urlParStr}`, {
      method,
      headers,
      // 如果是 get 或者 head 方法,不添加请求头部
      body: method === ("GET" || "HEAD") ? null : JSON.stringify(bodyParams)
    })
  );

  if (res.status < 200 || res.status > 299) {
    console.log(`出错啦:${res.status}`);
  } else {
    return res.json();
  }
}复制代码

上面的配置还不完善,比如,生产环境中很多接口都有验证功能,一般是 token + 用户 id,上面的配置并没有这个功能。但现在实现这个功能还会涉及到在哪存放 token,一展开又有很多内容,缺少验证功能暂时并不影响 APP 的完整度,所以这个坑后续填。

5.2 请求 api 编写及使用

具体 api 请求代码我放在了 xgRequest.js 文件中,以 get 请求为例, xgRequest.js 代码如下:

/**
 * xgRequest.js
 */

import XgHttp from "./xgHttp";

export default {
  todayOnHistory: urlPar => XgHttp("GET", "/japi/toh", { urlParams: urlPar })
};
复制代码

再调用聚合数据历史上的今天 API 的时候使用了我自己的 APPKEY,每天免费调用 100 次,超出后回报错 request exceeds the limit! ,如果你想进行更多的测试,注册后替换成自己的 APPKEY 就可以。

首先,调用接口,获取数据。

接口调用是在页面文件的 index.js 中进行的,以 ScreenTab1/index.js 为例:

/**
 * ScreenTab1/index.js
 */

const urlPar = {
  // 大佬们,这个是我申请的聚合数据应用的key,每天只有100免费请求次数
  key: '7606e878163d494b376802115f30dd4e',
  v: '1.0',
  month: Number(this.state.inputMonthText),
  day: Number(this.state.inputDayText),
};

// 拿到返回数据后就可以进一步操作了
const todayOnHistoryInfo = await XgRequest.todayOnHistory(urlPar);复制代码

然后,展示数据。

拿到数据以后就可以在做进一步操作了,一般就是在页面中展示了。 react 是数据驱动的框架 ,对于动态变化的展示数据一般是放在 react native 的 state 对象中, state 一经改变,便会触发 render() 函数重新渲染 DOM 中变化了的那部分。

首先是在 index.js 中把需要动态展示的数据先写入 state

/**
 * ScreenTab1/index.js
 */

// 将需要动态更新的数据放入 state
this.state = {
  todayOnHistoryInfo: {}
};复制代码

index.js完整代码:

import React, { Component } from 'react';
import { Image,Alert } from 'react-native';
import view from './view';
import XgRequest from '../../config/xgRequest';

export default class ScreenTab1 extends Component {

  static navigationOptions = {
    title: '网络请求(TAB1)',
    tabBarIcon: ({ focused }) => {
      const icon = focused
        ? require('../../assets/images/tab_home_active.png')
        : require('../../assets/images/tab_home.png');
      return <Image source={icon} style={{ height: 22, width: 22 }} />;
    },
  };

  constructor(props) {
    super(props);
    this.navigation = props.navigation;

    // 将需要动态更新的数据放入 state
    this.state = {
      todayOnHistoryInfo: {},
      inputMonthText: '',
      inputDayText: '',
    };
  }

  async getTodayOnHistoryInfo() {
    if (!this.state.inputMonthText || !this.state.inputDayText) {
      this.xgToast.show('请输入有效数据', 2000, 'error');
      return;
    }
    try {
      const urlPar = {
        // 大佬们,这个是我申请的聚合数据应用的key,每天只有100免费请求次数
        key: '7606e878163d494b376802115f30dd4e',
        v: '1.0',
        month: Number(this.state.inputMonthText),
        day: Number(this.state.inputDayText),
      };
      const todayOnHistoryInfo = await XgRequest.todayOnHistory(urlPar);

      // 捕获错误,具体捕获过程需与写api的同学商量确定
      if (todayOnHistoryInfo.error_code) {
        this.xgToast.show(todayOnHistoryInfo.reason, 2000, 'error');
      } else {
        // 更新state,render函数自动重新渲染DOM中变化了的那部分
        this.setState({ todayOnHistoryInfo });
      }
    } catch (e) {
      console.log(e);
    }
  }

  render() {
    return view(this);
  }
}
复制代码
/**
 * ScreenTab1/view.js
 */

{
  /* 查询 */
}
<Button title="查询" onPress={() => self.getTodayOnHistoryInfo()} />;

{
  /* 展示查询数据 */
}
<Text>
  发生了啥事:{self.state.todayOnHistoryInfo.result
    ? self.state.todayOnHistoryInfo.result[0].des
    : "暂无数据"}
</Text>;复制代码

view.js完整代码,其中style.js可直接copy先看效果

import React from 'react';
import { View, Button, Text, TextInput } from 'react-native';
import styles from './style';

// 引入 toast 组件
import XgToast from '../../components/XgToast';

export default self => (
  <View style={{ alignItems: 'center' }}>
    <Text style={{ fontSize: 24 }}>历史上的今天</Text>

    <TextInput
      style={[styles.input]}
      placeholder="month"
      onChangeText={text => self.setState({ inputMonthText: text })}
    />
    <TextInput
      style={[styles.input]}
      placeholder="day"
      onChangeText={text => self.setState({ inputDayText: text })}
    />
    <Button title="查询" onPress={() => self.getTodayOnHistoryInfo()} />


   
    <Text>
      发生了啥事:{self.state.todayOnHistoryInfo.result
        ? self.state.todayOnHistoryInfo.result[0].des
        : '暂无数据'}
    </Text>

    <XgToast
      ref={(element) => {
        self.xgToast = element;
      }}
    />
  </View>
);复制代码

style.js

import { StyleSheet } from 'react-native';
import pxToDp from '../../config/pxToDp';

export default StyleSheet.create({
  inputContainer: {
    height: pxToDp(100),
    paddingTop: pxToDp(20),
    borderBottomWidth: pxToDp(1),
    borderBottomColor: '#ddd',
  },
  input: {
    textAlign: 'center',
    height: pxToDp(80),
    width: pxToDp(600),
    marginTop: pxToDp(30),
    marginBottom: pxToDp(30),
    color: '#000',
    fontSize: pxToDp(30),
    borderBottomColor: '#000',
    borderBottomWidth: pxToDp(0.5),
  },
}); 复制代码

效果如下:

开发 React Native APP —— 从改造官方Demo开始

六 集成 redux

在 App 中有一些全局状态是所有页面共享的,比如登录状态,或者账户余额(购买商品后所有展示余额的页面都要跟着更新)。在本项目中,使用 Redux 进行状态管理。

引入 redux 后后目录结构调整如下:

开发 React Native APP —— 从改造官方Demo开始

如果对 redux 毫无概念,可以看下这篇文章 Redux 入门教程

按照小庸的demo敲了之后,发现Redux 实际上是非常难用的,,,如果之前使用过vuex的话,在使用 Redux 的过程中,会发现需要自己配置的东西太多(不喜勿喷,只是表达个人想使用感受而已),为了简化 Redux 的操作, Redux 作者开发了 react-redux ,虽然使用的便捷性上还没法和 vuex 比,但总算是比直接使用 Redux 好用很多。

在集成 Redux 进行状态管理之前我们先思考一个问题:集成过程中难点在哪?

因为在一个 App 中 Redux 只有一个 Store,这个 Store 应该为所有(页面)组件共享,所以,集成的难点就是 如何使所有(页面)组件可以访问到这个唯一的 store,并且可以触发 action 。为此,redux-react 引入了 connect 函数和 Provide 组件,他们必须配合使用才能实现 redux 的集成。

通过这 connectProvide 实现 store 在组件间共享的思想是:

  1. Redux store 可以(注意是“可以”,并不是“一定”,需要配置,见第 2 条)对 connect 方法可见,所以在组件中可以通过调用 connect 方法实现对 store 数据的访问;
  2. 实现 Redux store 对 connect 的可见的前提条件是, 需要保证这个组件为 Provide 组件的子组件 ,这样通过将 store 作为 Provide 组件的 props,就可以层层往下传递给所有子组件;
  3. 但子组件必须通过 connect 方法实现对 store 的访问,而无法直接访问。

6.1 引入依赖

首先是安装依赖 redux,react-redux:

yarn add redux react-redux复制代码

6.2 配置 redux

这里指的是配置 actions , reducersstore

据说应用大了,最好将 redux 分拆,但现在项目还小,暂时没有做拆分。

  • 配置 actions
/**
 * actions.js
 */

export function setUserInfo(userInfo) {
  return {
    // action 类型
    type: "SET_USER_INFO",

    // userinfo 是传进来的参数
    userInfo
  };
}
export function clearReduxStore() {
  return {
    type: "CLEAR_REDUX_STORE"
  };
}
复制代码
  • 配置 reducers
/**
 * reducers.js
 */

import { initialState } from "./store";

function reducer(state = initialState, action) {
  switch (action.type) {
    case "SET_USER_INFO":
      // 合并 userInfo 对象
      action.userInfo = Object.assign({}, state.userInfo, action.userInfo);

      // 更新状态
      return Object.assign({}, state, { userInfo: action.userInfo });
    case "CLEAR_REDUX_STORE":
      // 清空 store 中的 userInfo 信息
      return { userInfo: {} };
    default:
      return state;
  }
}

export default reducer;
复制代码

注意 SET_USER_INFO 这条路径下的代码,使用了 Object.assign() 。这是因为 reducer 函数每次都会返回全新的 state 对象, 这意味着如果 state 对象含有多个属性而在 reducer 函数返回时没有合并之前的 state ,可能会导致 state 对象属性丢失

这是一个很常见的错误,因为通常我们在触发 actions 时只需要传入更改的那部分 state 属性,而不是将整个 state 再传一遍。

redux 经典计数器教程在触发 state 变化时通常这样写 return { defaultNum: state.defaultNum - 1 }; ,因为计数器例子中只有一个属性,即 defaultNum ,所以合并之前的 state 就没有意义了,但生产环境中的应用 state 对象中往往不止一个属性,此时上述的写法就会出错。

  • 配置 store
/**
 * store.js
 */

import { createStore } from "redux";
import reducers from "./reducers";

// 定义初始值
const initialState = {
  userInfo: {
    name: "小光",
    gender: "男"
  }
};

const store = createStore(reducers, initialState);

export default store;复制代码

6.3 组件中使用

配置完 redux,接下来就是使用了。

  • 配置 index.js

在配置 index.js 中 主要是配置 Provide 作为根组件,并传入 store 作为其属性,为接下来组件使用 redux 创造条件。

/**
 * index.js
 */
import React from "react";
import { AppRegistry } from "react-native";
import { Provider } from "react-redux";
import App from "./App";
import store from "./src/redux/store";

const ReduxApp = () => (
  // 配置 Provider 为根组件,同时传入 store 作为其属性
  <Provider store={store}>
    <App />
  </Provider>
);

AppRegistry.registerComponent("AwesomeProject", () => ReduxApp);
复制代码
  • 配置组件

这里以 ScreenTab2 为例,注意,引入的style.js可直接copy使用

首先,在 index.js 中关联 redux

/**
 * ScreenTab2/index.js
 */
// redux 依赖
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionCreators from '../../redux/actions';
import React, { Component } from 'react';
import { Image } from 'react-native';

import view from './view';

class ScreenTab2 extends Component {
  static navigationOptions = {
    title: 'Redux(TAB2)',
    tabBarIcon: ({ focused }) => {
      const icon = focused
        ? require('../../assets/images/tab_home_active.png')
        : require('../../assets/images/tab_home.png');
      return <Image source={icon} style={{ height: 22, width: 22 }} />;
    },
  };

  constructor(props) {
    super(props);
    this.navigation = props.navigation;
  }

  changeReduxStore(userInfo) {
    // 设置 redux store
    this.props.setUserInfo(userInfo);
  }

  render() {
    return view(this);
  }
}

// 将 store 中的状态映射(map)到当前组件的 props 中
function mapStateToProps(state) {
  return { userInfo: state.userInfo };
}

// 将 actions 中定义的方法映射到当前组件的 props 中
function mapDispatchToProps(dispatch) {
  return bindActionCreators(actionCreators, dispatch);
}

// 将 store 和 当前组件连接(connect)起来
export default connect(mapStateToProps, mapDispatchToProps)(ScreenTab2);

复制代码

然后,就是在 view 中控制具体改变的数据

import React from 'react';
import { View, Text, Button } from 'react-native';
import pxToDp from '../../config/pxToDp';
import styles from './style';

export default self => (
	<View>
		<View>
	    <Text style={{ fontSize: pxToDp(36) }}>名字:{self.props.userInfo.name}</Text>
	    <Text style={{ fontSize: pxToDp(36) }}>性别:{self.props.userInfo.gender}</Text>
		</View>
	  <View style={{ alignItems: 'center' }}>
	    <View style={styles.buttonContainer}>
	    	<Button title="改变名字" onPress={() => self.changeReduxStore({ name: 'vince' })} />
	    </View>
	    <View style={styles.buttonContainer}>
	    	<Button style={styles.buttonContainer} title="改变性别" onPress={() => self.changeReduxStore({ gender: '女' })} />
	    </View>
	    <View style={styles.buttonContainer}>
	    	<Button style={styles.buttonContainer} title="还原" onPress={() => self.changeReduxStore({ name: '小光', gender: '男' })} />
	    </View>
	  </View>
	</View>
);
 复制代码

style.js

import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  buttonContainer: {
    margin:20
  },
});

复制代码

最终效果图如下:

开发 React Native APP —— 从改造官方Demo开始

6.4 持久化存储

手机 App 一般都有这样的需求: 除非用户主动退出,不然即便 App 进程被杀死,App 重新打开后登录信息依旧会保存

在本项目中,为了便于各组件共享登录状态,我把登录状态写在了 redux store 中,但原生 redux 有个特性:页面刷新后 redux store 会回恢复初始状态。为了达到上述需求,就需要考虑 redux store 持久化存储方案。本项目中使用了 redux-persist ,下面介绍如何配置:

  • 引入依赖
    yarn add redux-persist复制代码
  • 修改 redux 配置
1)修改 store.js

除了引入 redux-persist 外,这里使用了 react native 提供的AsyncStorage 作为持久化存储的容器。另外,初始化 state 移到了 reducers.js 中。

/**
 * store.js
 * 更改为持久化存储
 */

import { createStore } from "redux";

// 引入 AsyncStorage 作为存储容器
import { AsyncStorage } from "react-native";

// 引入 redux-persist
import { persistStore, persistCombineReducers } from "redux-persist";

import reducers from "./reducers";

// 持久化存储配置
const config = {
  key: "root",
  storage: AsyncStorage
};

const persistReducers = persistCombineReducers(config, {
  reducers
});

const configureStore = () => {
  const store = createStore(persistReducers);
  const persistor = persistStore(store);

  return { persistor, store };
};

export default configureStore;
复制代码

2)修改 reducers.js

只是将初始化 state 移入。至于为什么要将初始化 statestore.js 移入 reducers.js 实在是无奈之举:不然在 store.js 中创建 store 报错,后续再填坑,暂时先放在 reducers.js 中。

/**
 * reducers.js
 * 更改为持久化存储
 */
//import { initialState } from "./store";

// 初始化 state 放在这里
const initialState = {
  userInfo: {
    name: "小光",
    gender: "男"
  }
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case "SET_USER_INFO":
      // 合并 userInfo 对象
      action.userInfo = Object.assign({}, state.userInfo, action.userInfo);

      // 更新状态
      return Object.assign({}, state, { userInfo: action.userInfo });
    case "CLEAR_REDUX_STORE":
      // 清空 store 中的 userInfo 信息
      return { userInfo: {} };
    default:
      return state;
  }
}

export default reducer;
复制代码
  • 修改使用 redux 的文件
1)修改根目录下的 index.js

/**
 * index.js
 * 更改为持久化存储
 */
import React from "react";
import { PersistGate } from "redux-persist/es/integration/react";
import configureStore from "./src/redux/store";
import { AppRegistry } from "react-native";
import { Provider } from "react-redux";
import App from "./App";

const { persistor, store } = configureStore();

const ReduxApp = () => (
  // 配置 Provider 为根组件,同时传入 store 作为其属性
  <Provider store={store}>
    <PersistGate persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>
);

AppRegistry.registerComponent("AwesomeProject", () => ReduxApp);复制代码

2)因为修改为持久化存储的过程过程中把初始化的 state 存在了 reducers.js 中,所以在页面组件映射 state 到当前页面时需要还需要修改对应属性的引入地址,依然以 ScreenTab2 为例:

//修改前
// 将 store 中的状态映射(map)到当前组件的 props 中
/*function mapStateToProps(state) {
  return { userInfo: state.userInfo };
}*/

// 修改后
function mapStateToProps(state) {
  // 引用 state.reducers.userInfo
  return { userInfo: state.reducers.userInfo };
}复制代码

经过上述修改,便可以实现 redux 的持久化存储:初始化姓名是 小光 ,更改为 vince 后重新加载页面,姓名还是 vince (而非初始状态 小光 )。效果图如下:

开发 React Native APP —— 从改造官方Demo开始

七 小结

经过这部分介绍,App 框架基本构建完成,


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Chinese Authoritarianism in the Information Age

Chinese Authoritarianism in the Information Age

Routledge / 2018-2-13 / GBP 115.00

This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具