開發 React Native APP —— 從改造官方Demo開始(一)

L小庸發表於2018-02-28

本文使用的 Demo 完整程式碼在這 react_native_complete_demo

最開始接到公司通知要開發 React Native APP 的時候,很興奮,因為之前的技術棧主要是 Vue 和 Angular,對於 React 只是寫過幾個 Demo,一直想在實際專案中使用但沒有機會。不過公司給的開發時間很短,從設計需求到第一版送審只給了一個月時間。鑑於之前使用 Vue 的經驗(即便不是很熟的情況下也可以把官網 Demo 擼下來改改就能上線,功能及效能可以後續迭代優化)以及業務 API 90%以上都已和後臺同學聯調 OK,當時想一個月綽綽有餘。

雖說最後 APP 上線了(iOS安卓),但開發過程中踩了很多坑。

首先,難以找到一個開箱即用的 React Native APP Demo。目前存在的 Demo 要麼過於簡單,比如 React Native 官網提供的 Demo AwesomeProject ,這個 Demo 只提供了最簡功能,對於路由(導航元件)、狀態管理等並沒有涉及。雖然 React Native 教程中對於複雜應用應如何選擇元件及第三方庫都有提及,但並沒有給出完整示例。而另一方面,又有很多 React Native APP 雖已開源,但都是用於特定場合的完整 APP,有些 APP 的目錄結構本身就不友好,並且也沒有完整的說明文件。

其次,React 本身的學習曲線就相對陡峭,尤其涉及狀態管理部分,很難找到可以直接 copy-paste 的程式碼,除此之外原生 App 本身還有很多區別於 web 的需求。

鑑於以上原因,所以決定寫篇文章詳細介紹開發 React Native APP 的過程。

內容較多,分兩部分介紹,這部分主要內容為:

  • react navigation 作為路由(導航)元件的初步使用
  • 自定義元件
  • 通過 fetch API 傳送網路請求
  • 整合 redux,並實現 redux 狀態的持久化儲存

一 準備工作

1.1 開發工具

如果要開發 iOS 應用併發布到 APP Store 必須使用 Xcode 並有 Apple 開發者賬號。如果是開發安卓應用,有電腦就好,最好有梯子。

1.2 程式碼檢查及自動修正

開始改造程式碼之前,推薦先安裝 eslint 和 prettier 作為程式碼檢查和自動格式化工具,這樣可以確保自己寫的程式碼始終如一且避免低階錯誤,我使用 vscode 作為編輯器,之前寫過一篇文章VSCode 配置 react 開發環境,如果你也使用 vscode 可參考下。

最終的目的就是儲存操作後程式碼按照 eslint 的配置,自動格式化程式碼。

二 官方 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 很多依賴更新需要訪問谷歌服務,所以請自備梯子。

step_by_step/other/react/react_native/ 中詳細記錄了我在初次使用 React Native 過程中遇到的問題及解決方案,因為以記錄為目的,所以稍顯囉嗦,有興趣可以看下。

2.2 官方 Demo 目錄介紹

良好的目錄結構有助於今後的開發及維護,本文後半部分每新增新功能,除了程式碼部分,如果目錄結構有變,還會著重指出。首先,讓我們看下官方 Demo 的目錄結構:

AwesomeProject 目錄結構

上面的目錄結構說明如下,重要的有:

  • android/ android 原生程式碼(使用 android studio 要開啟這個目錄,如果直接開啟父目錄報錯)
  • ios/ ios 原生程式碼(使用 xcode 開啟這個目錄,如果直接開啟父目錄報錯)
  • index.js 打包 app 時進入 react native(js 部分) 的入口檔案(0.49 以後安卓、ios 共用一個入口檔案)
  • App.js 可以理解為 react native(js 部分) 程式碼部分的入口檔案,比如整個專案的路由在這裡匯入

上面是最重要的四個目錄/檔案,其他說明如下:

  • _test_/ 測試用(暫未使用)
  • app.json 專案說明,主要給原生 app 打包用,包括專案名稱和手機桌面展示名稱 React Native : 0.41 app.json
  • package.json 專案依賴包配置檔案
  • node_modules 依賴包安裝目錄
  • yarn.lock yarn 包管理檔案
  • 其他配置檔案暫時無需改動,在此不做說明

3 配置路由

這裡使用 react navigation 管理路由,大而全的介紹或者原理說明不是這部分的重點,這裡主要講怎麼用。

react navigation 常用 API 有三個:

  • StackNavigator:頁面間跳轉(每次跳轉後都會將前一個頁面推入返回棧,需要返回上個頁面特別好用)
  • TabNavigator:頂部或底部 tab 跳轉,一般在底部使用
  • DrawerNavigator:側滑導航

最為常用的是前兩個,接下來也只介紹前兩個的使用。

3.1 StackNavigator 實現頁面間跳轉

首先我們要調整下目錄結構,調整後的結構如下:

新增 StackNavigator 後目錄結構

  • src/ 放置所有原始的 react native 程式碼
  • config/ 配置檔案,比如路由配置
  • route.js 路由配置檔案
  • screens/ 所有頁面檔案
  • ScreenHome/ 這個目錄是放具體頁面檔案的,為了進一步進行程式碼分離,裡面又分為三個檔案:index.js 中包含邏輯部分,style.js 中包含樣式部分;view.js 中包含檢視或者說頁面元素部分。其他頁面文案結構與此相同。

注意頁面檔案的命名方式:大駝峰命名法,react native 推薦元件命名用大駝峰命名法,每個頁面相當於一個元件。

簡單介紹了 react navigation 下面進行具體改造:

1)首先配置路由:路由檔案 route.js 此時內容如下,這也是 StackNavigator 最簡單的使用方式:

/**
 * route.js
 */

// 引入依賴
import React from "react";
import { StackNavigator } from "react-navigation";

// 引入頁面元件
import ScreenHome from "../screens/ScreenHome";
import ScreenSome1 from "../screens/ScreenSome1";

// 配置路由
const AppNavigator = StackNavigator({
  ScreenHome: {
    screen: ScreenHome
  },
  ScreenSome1: {
    screen: ScreenSome1
  }
});

export default () => <AppNavigator />;
複製程式碼

2)更新 App.js,對接路由檔案:

/**
 * App.js
 */

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

  render() {
    // 渲染頁面
    return <Route />;
  }
}
複製程式碼

3)具體頁面設定,以 ScreenHome 為例

index.js 中自定義當前頁面路由邏輯和樣式,比如 title 及其樣式、在導航欄自定義按鈕等,到目前為止,我們只需要簡單設定 title 就好:

/**
 * ScreenHome/index.js
 */

export default class ScreenHome extends Component {
  // 自定義當前頁面路由配置,後面介紹的TabNavigator也使用這個物件中的屬性
  static navigationOptions = {
    // 設定 title
    title: "首頁"
  };

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

  render() {
    return view(this);
  }
}
複製程式碼

view.js 中在具體元素上定義具體跳轉頁面

/**
 * ScreenHome/index.js
 */

// 引入依賴略

export default self => (
  <View>
    <Text style={{ fontSize: 36 }}>home</Text>
    <Button
      title="goSomePage1"
      // 路由跳轉
      onPress={() => self.navigation.navigate("ScreenSome1")}
    />
  </View>
);
複製程式碼

經過上述配置,效果如下:

StackNavigator效果圖

3.2 TabNavigator 實現頁面底部 tab 切換

首先在 screens 目錄下新建 ScreenBottomTab 頁面,用於配置 TabNavigator。每個 tab 對應一個頁面,按需新建頁面,並且新建的頁面需要在 route.js 中進行配置,更新後的目錄結構如下:

新增tab導航後目錄結構

  • ScreenBottomTab 配置底部 tab 導航
  • ScreenTab1/2/3 新建頁面,配合底部 tab 導航

1)沒有 tab 圖示的最簡配置

此時只需要配置 ScreenBottomTab 裡面的 index.js 檔案就好,如下:

/**
 * ScreenBottomTab/index.js
 */

const ScreenTab = TabNavigator(
  // 配置 tab 路由
  {
    ScreenHome: {
      screen: ScreenHome
    },
    ScreenTab1: {
      screen: ScreenTab1
    },
    ScreenTab2: {
      screen: ScreenTab2
    },
    ScreenTab3: {
      screen: ScreenTab3
    }
  },
  // 其他配置選項
  {
    tabBarPosition: "bottom"
  }
);

export default ScreenTab;
複製程式碼

頁面檔案現在無需配置,需要注意的是 tab 下面的文字預設和在 StackNavigator 中定義的頭部導航 title 相同。

2)自定義 tab 圖示

tab 圖示除了自定義外,還需要根據是否選中顯示不同顏色,這可以通過配置 TabNavigatortabBarIcon 實現,修改的具體檔案是 tab 對應頁面的 index.js 檔案。

/**
 * ScreenHome/index.js
 */

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 }} />;
  },
};
複製程式碼

最終的效果如下:

底部 tab 導航效果圖

3.3 單個頁面實現 modal 模式的切換

對於 ios 常見的需求是:登入頁面是由下往上進入,而其他頁面是由左至右預設進入,react navigation 只提供了全域性配置頁面的方式,並沒提供單個頁面的互動方式,但這個功能還是可以實現的,這個在第二部分開發 React Native APP —— 從改造官方 Demo 開始(二)中介紹。

四 自定義元件

react native 已經封裝了很多常用元件,但有時我們仍然需要在次基礎上進行封裝,比如某些元件需要大量複用而原生元件樣式或者互動邏輯不符合需求。

這裡只介紹目錄結構的調整,具體程式碼可參考 Github 上專案程式碼,因為自定義元件的需求千差萬別,具體編寫過程也有很多教程,這裡不再具體介紹,只新增了自定義 Toast 元件。目錄結構調整如下:

元件目錄

  • components/ 自定義元件都放這裡
  • XgToast.js 自定義元件具體程式碼

檔案 config/pxToDp.js 用於尺寸自適應,在 XgToast.js 中有使用,開發 React Native APP —— 從改造官方 Demo 開始(二)中詳細介紹。

五 網路請求

react native 使用上有個最大的好處是可以不用考慮新語法相容性的問題,既然如此,自然使用設計更加優良的 API,在網路請求方面,本專案使用fetch API

新增網路請求後目錄結構調整如下:

網路請求目錄

  • xgHttp.js 配置 fetch api
  • xgRequest.js api 請求列表

5.1 配置 fetch api

xgHttp.js全部程式碼如下,裡面有簡單註釋,這裡不再詳解,fetch api 的使用可以參考 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 列表檔案

具體 api 請求程式碼我放在了 xgRequest.js 檔案中,以 get 請求為例,xgRequest.js 程式碼如下:

/**
 * xgRequest.js
 */

import XgHttp from "./xgHttp";

export default {
  todayOnHistory: urlPar => XgHttp("GET", "/japi/toh", { urlParams: urlPar })
};
複製程式碼

其中 "/japi/toh" 為介面地址,這裡我使用了聚合資料歷史上的今天 API。

再呼叫聚合資料歷史上的今天 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: {}
};
複製程式碼

然後在 view.js 中讀取 state 中的資料:

/**
 * ScreenTab1/view.js
 */

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

{
  /* 展示查詢資料 */
}
<Text>
  發生了啥事:{self.state.todayOnHistoryInfo.result
    ? self.state.todayOnHistoryInfo.result[0].des
    : "暫無資料"}
</Text>;
複製程式碼

上述 view.js 中的程式碼主要做兩件事:傳送呼叫指令,展示返回資料。

最終的效果圖如下:

網路請求效果

六 整合 redux

在 App 中有一些全域性狀態是所有頁面共享的,比如登入狀態,或者賬戶餘額(購買商品後所有展示餘額的頁面都要跟著更新)。在本專案中,使用 Redux 進行狀態管理。

引入 redux 後後目錄結構調整如下:

redux目錄

  • redux 存放 redux 相關配置檔案
  • actions.js redux action
  • reducers.js redux reducer
  • store.js redux store

如果對 redux 毫無概念,可以看下這篇文章 Redux 入門教程

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 為例:

首先,在 index.js 中關聯 redux

/**
 * ScreenTab2/index.js
 */
// redux 依賴
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as actionCreators from "../../redux/actions";

changeReduxStore(userInfo) {
  // 設定 redux store,相當於 dispatch,這裡觸發 actions 中的 'SET_USER_INFO'
  this.props.setUserInfo(userInfo);
}

// 將 store 中的狀態對映(map)到當前元件的 props 中,這樣才能在該組建中訪問 redux state
function mapStateToProps(state) {
  return { userInfo: state.userInfo };
}

// 將 actions 中定義的方法對映到當前元件的 props 中,這樣才能在該組建中觸發 action
function mapDispatchToProps(dispatch) {
  return bindActionCreators(actionCreators, dispatch);
}

// 將 store 和 當前元件連線(connect)起來
export default connect(mapStateToProps, mapDispatchToProps)(ScreenTab2);
複製程式碼

然後,就是在 view 中控制具體改變的資料

/**
 * ScreenTab2/view.js
 */

<Button title="改變名字" onPress={() => self.changeReduxStore({ name: 'vince' })} />
<Button title="改變性別" onPress={() => self.changeReduxStore({ gender: '女' })} />
<Button title="還原" onPress={() => self.changeReduxStore({ name: '小光', gender: '男' })} />
複製程式碼

最終效果圖如下:

整合redux後效果

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
 * 更改為持久化儲存
 */

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

function reducers(state = initialState, action) {
  // ... 程式碼未修改
}

export default reducers;
複製程式碼
  • 修改使用 redux 的檔案

1)修改根目錄下的 index.js

/**
 * index.js
 * 更改為持久化儲存
 */
import { PersistGate } from "redux-persist/es/integration/react";
import configureStore from "./src/redux/store";

const { persistor, store } = configureStore();

const ReduxApp = () => (
  // 配置 Provider 為根元件,同時傳入 store 作為其屬性
  <Provider store={store}>
    {/* redux 持久化儲存 */}
    <PersistGate persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>
);
複製程式碼

2)因為修改為持久化儲存的過程過程中把初始化的 state 存在了 reducers.js 中,所以在頁面元件對映 state 到當前頁面時需要還需要修改對應屬性的引入地址,依然以 ScreenTab2 為例:

/**
 * ScreenTab2/index.js
 * 更改為持久化儲存
 */

// 修改前
function mapStateToProps(state) {
  // 引用 state.userInfo
  return { userInfo: state.userInfo };
}

// 修改後
function mapStateToProps(state) {
  // 引用 state.reducers.userInfo
  return { userInfo: state.reducers.userInfo };
}
複製程式碼

經過上述修改,便可以實現 redux 的持久化儲存:初始化姓名是 小光,更改為 vince 後重新載入頁面,姓名還是 vince(而非初始狀態 小光)。效果圖如下:

redux持久化儲存

七 小結

經過這部分介紹,App 框架基本構建完成,在第二部分主要討論 UI/互動、App 釋出前的準備工作及如何釋出,具體內容包括:

  • 在使用 react navigation 的前提下,iOS 實現單個頁面從下往上(modal)的進入動畫
  • 尺寸自適應
  • 設定啟動頁,更換桌面圖示、app 展示名稱、appID
  • 打包釋出

參考資料

fetch API 簡介
Redux 入門教程
對 React-redux 中 connect 方法的理解

相關文章