iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 13
1
Modern Web

使用 Modern Web 技術來打造 Native App系列 第 13

Day 13:Navigation Part II:新的 API - NavigationExperimental

前言

Product Pain 是 React Native 聆聽社群聲音的重要平台,而在上面 Navigator 是個知名的「Pain」,也催生了 Navigation RFC 與新的 Navigation API - NavigationExperimental。這篇會稍微介紹它的特點以及它的用法。

與 Navigator 的不同處

NavigationExperimental 的設計主要有這些特點:

  • NavigationExperimental 使用單向的資料流,其中用了 Redux 的概念 Reducer 來處理 Navigation State
  • Navigation 跟 View 的邏輯完全拆開,所以可以跟任何用 Native 或 JavaScript 寫的 Navigation View 結合
  • 變得更模組化而可以使用 Animated 來操作動畫跟手勢

這些都是相當重要的特點,在前面介紹 Navigator 時看得出來,Navigator 本身掌控了 Navigation State,並且有 NavigationBar 這樣的 View Component,還利用 Prop 來設定 Transition 動畫。

NavigationExperimental 不會有這些的限制,能有更多的可能性。

實作步驟

為了實作基本的 Navigation,我們將會有以下步驟:

  1. 定義初始 State 以及頂層 Component
  2. 用 Reduce 處理 Navigation State
  3. 定義 Scene
  4. 建立 Navigation Stack

Import 的方式

首先,我們必須先把需要的 Component 和 Util Import 進來。參照官方文件的方式,我們這邊將使用 Destructuring 來把 NavigationExperimentalCardStackStateUtilsNavigationCardStackNavigationStateUtils 這兩個名字拆出來:

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

const {
  CardStack: NavigationCardStack,
  StateUtils: NavigationStateUtils,
} = NavigationExperimental;

定義初始 State 以及頂層 Component

先來定義初始 Navigation State,裡面有存現有的 Rotue Array 跟 Focus 的 index:

export default class NavigationExample extends Component {
  state = {
    navigationState: {
      index: 1,
      routes,
    },
  };

  onNavigationChange = (type, route) => {
    // ..
  };

  render() {
    return (
      <Text>先隨便放</Text>
    );
  }
}

至於上面看不到的 routes... 當然還是我們的出師表跟雅量:

import shortid from 'shortid';

const articles = [
  {
    key: shortid.generate(),
    index: 0,
    title: '出師表',
    paragraph1:
`臣亮言:先帝創業未半,而中道崩殂。
今天下三分,益州疲弊,此誠危急存亡之秋也。
然侍衛之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
誠宜開張聖聽,以光先帝遺德,恢弘志士之氣;`,
    paragraph2:
`不宜妄自菲薄,引喻失義,以塞忠諫之路也。
宮中府中,俱為一體,陟罰臧否,不宜異同。
若有作姦犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治,不宜篇私,使內外異法也。`,
  },
  {
    key: shortid.generate(),
    index: 1,
    title: '雅量',
    paragraph1:
`朋友買了一件衣料,綠色的底子帶白色方格,當她拿給我們看時,一位對圍棋十分感與趣的同學說:
「啊,好像棋盤似的。」
「我看倒有點像稿紙。」我說。
「真像一塊塊綠豆糕。」一位外號叫「大食客」的同學緊接著說。`,
    paragraph2:
`我們不禁哄堂大笑,同樣的一件衣料,每個人卻有不同的感覺。那位朋友連忙把衣料用紙包好,她覺得衣料就是衣料,不是棋盤,也不是稿紙,更不是綠豆糕。`,
  }
];

以上這段用到了 shortid 這個用來產生短 id 的套件,因為 key 不能重複。

用 Reduce 處理 Navigation State

接下來我們要來實作 onNavigationChange 這個 Function,它會看 Action Type 是 push 還是 pop,分別運用不同的 Reducer - NavigationStateUtils.pushNavigationStateUtils.pop 來產生新的 State (它們的 Source Code 不難懂),如果 State 改變了則進行 setState

onNavigationChange = (type, route) => {
  // 把 navigationState 從 State 抽出來
  let { navigationState } = this.state;

  switch (type) {
    case 'push':
      const newRoute = {
        ...route,
        key: shortid.generate(),
      };
      // Push 一個 Route,key 必須是唯一的
      // 用 NavigationStateUtils 的 Push Reducer 得到 Next State 
      navigationState = NavigationStateUtils.push(navigationState, newRoute);
      break;

    case 'pop':
      // 用 NavigationStateUtils 的 Pop Reducer 得到 Next State 
      navigationState = NavigationStateUtils.pop(navigationState);
      break;
  }

  // 如果有改變就 setState
  if (this.state.navigationState !== navigationState) {
    this.setState({ navigationState });
  }
};

定義 Scene

在這邊定義 Scene 跟上一篇定義 Scene 時沒有太大差別,只差在 route 還有 onPushRouteonPopRoute 這兩個 Handler 都是從 Props 傳進來:

class Scene extends Component {
  render() {
    const { route, onPushRoute, onPopRoute } = this.props;
    return (
      <View style={styles.container}>
        <View style={styles.header}>
          <View style={styles.button}>
            <Button
              title="Switch"
              onPress={() => {
                if (route.index === 0) {
                  onPushRoute(routes[1]);
                } else {
                  onPopRoute();
                }
              }}
            />
          </View>
          <Text style={styles.title}>{route.title}</Text>
        </View>
        <Text style={styles.paragraph}>
          {route.paragraph1}
        </Text>
        <Text style={styles.paragraph}>
          {route.paragraph2}
        </Text>
      </View>
    );
  }
}

其他的要填入的內容也都來自 route

建立 Navigation Stack

這步驟我們要建一個 Component 來放 NavigationCardStack 也一併 Render 前面定義的 Scene:

class MyNavigator extends Component {
  onPushRoute = route => this.props.onNavigationChange('push', route);
  onPopRoute = () => this.props.onNavigationChange('pop');

  renderScene = sceneProps => {
    return (
      <Scene
        route={sceneProps.scene.route}
        onPushRoute={this.onPushRoute}
        onPopRoute={this.onPopRoute}
      />
    );
  };

  render() {
    return (
      <NavigationCardStack
        onNavigateBack={this.onPopRoute}
        navigationState={this.props.navigationState}
        renderScene={this.renderScene}
      />
    );
  }
}

上面我們把 onNavigationChange 包裝成 onPushRouteonPopRoute 方便 Scene 去做操作。

最後再把之前先隨便放在頂層 Component NavigationExample 的 render Function 給換成回傳 MyNavigator,記得傳 navigationStateonNavigationChange 進去:

render() {
  return (
    <MyNavigator
      navigationState={this.state.navigationState}
      onNavigationChange={this.onNavigationChange}
    />
  );
}

然後就變出一個跟上一篇很像的 App!

基於 NavigationExperimental 的 Library

新一代的 React Native Navigation Library 大多都是基於 NavigationExperimental 來實作也能看出現在的趨勢,其中比較出名的有:

不過因為 React Native 的發布週期極短,NavigationExperimental 又是比較新的 API,這些第三方的 Library 不一定有辦法跟得上這個速度 (例如這當下 React Native Router Flux 還在用 React Native v0.26 的 NavigationExperimental),而 ExNavigation 現在也已經在 Maintenance Mode,所以筆者推薦可以直接利用底層的 API 封裝成自己方便使用的方式就好。

不過在 ExNavigation 的 Readme 可以看到這段:

An important note about the future

"ExNavigation 2" will be called "react-navigation" and it will live on the reactjs organization. It is currently being built and scheduled for a beta release in January, 2017. A migration path from ExNavigation will be provided.

This means that ExNavigation is currently in maintenance mode -- we aren't actively adding new features unless we need them for our own projects, because further work is being directed towards react-navigation. Pull requests are still welcome.

所以或許我們可以期待明年一月的 ExNavigation 2 也就是 React 官方的 react-navigation

結語

Navigation 是個困難且懸而未定的問題,雖不能一下就找到 Best Practice,但至少 NavigationExperimental 是個不錯的一步,從社群上開始棄 Navigator 採用 NavigationExperimental 也能略知一二。


上一篇
Day 12:Navigation Part I:Navigation 簡介與 Navigator
下一篇
Day 14:編輯的藝術 - TextInput 與 Keyboard
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言