在 iOS 與 Android 上實現 React Native 應用深度連接,使得應用能夠經過 URL 打開到指定頁面

原文:https://pantao.parcmg.com/press/react-native-deep-linking-for-ios-android.html
代碼:https://github.com/pantao/react-native-deep-linking-examplejavascript

咱們生活在一個萬物兼可分享的年代,而分享的過程,幾乎最終都會分享某一個連接,那麼,做爲開發者,最常遇到的問題中應該包括如何經過一個URL地址快速的打開App,並導航至特定的頁面。html

什麼是深度連接(Deep Link)

深度連接是一項可讓一個App經過一個URL地址打開,以後導航至特定頁面或者資源,或者展現特定UI的技術,Deep 的意思是指被打開的頁面或者資源並非App的首頁,最常使用到的地方包括但遠遠不限於 Push Notification、郵件、網頁連接等。前端

其實這個技術在好久好久之前就已經存在了,鼠標點擊一下 mailto:pantao@parcmg.com 這樣的連接,系統會打開默認的郵件軟件,而後將 pantao@parcmg.com 這個郵箱填寫至收件人輸入欄裏,這就是深度連接。java

本文將從零開始建立一個應用,讓它支持經過一個如 deep-linking://articles/{ID} 這樣的 URL 打開 文章詳情 頁面,同時加載 {ID} 指定的文章,好比:deep-linking://articles/4 將打開 ID4 的文章詳情頁面。node

深度連接解決了什麼問題?

網頁連接是沒法打開原生應用的,若是一個用戶訪問你的網頁中的某一個資源,他的手機上面也已經安裝了你的應用,那麼,咱們要如何讓系統自動的打開應用,而後在應用中展現用戶所訪問的那一個頁面中的資源?這就是深度連接須要解決的問題。react

實現深度連接的不一樣方式

有兩種方式能夠實現深度連接:android

  • URL scheme
  • Universal links

前端是最多見的方式,後者是 iOS 新提供的方式,能夠一個普通的網頁地址連接至App的特定資源。ios

本文將建立一個名爲 DeepLinkingExample 的應用,使得用戶能夠經過打開 deep-linking://home 以及 deep-linking://articles/4 分別打開 App 的首頁以及 App 中 ID 爲 4 的文章詳情頁面。git

react-native init DeepLinkingExample
cd DeepLinkingExample

安裝必要的庫

緊跟 TypeScript 大潮流,咱們的 App 寫將使用 TypeScript 開發。github

yarn add react-navigation react-native-gesture-handler
react-native link react-native-gesture-handler

咱們將使用 react-navigation 模塊做爲 App 的導航庫。

添加 TypeScript 相關的開發依賴:

yarn add typescript tslint tslint-react tslint-config-airbnb tslint-config-prettier ts-jest react-native-typescript-transformer -D
yarn add @types/jest @types/node @types/react @types/react-native @types/react-navigation @types/react-test-renderer

添加 tsconfig.json

{
  "compilerOptions": {
    "target": "es2017",                       /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "es2015",                       /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "lib": [                                  /* Specify library files to be included in the compilation:  */
      "es2017",
      "dom"
    ],
    "resolveJsonModule": true,
    "allowJs": false,                         /* Allow javascript files to be compiled. */
    "skipLibCheck": true,                     /* Skip type checking of all declaration files. */
    "jsx": "react-native",                    /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    "declaration": true,                      /* Generates corresponding '.d.ts' file. */
    "sourceMap": true,                        /* Generates corresponding '.map' file. */
    "outDir": "./lib",                        /* Redirect output structure to the directory. */
    "removeComments": true,                   /* Do not emit comments to output. */
    "noEmit": true,                           /* Do not emit outputs. */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    "noImplicitAny": true,                    /* Raise error on expressions and declarations with an implied 'any' type. */
    "strictNullChecks": true,                 /* Enable strict null checks. */
    "strictFunctionTypes": true,              /* Enable strict checking of function types. */
    "noImplicitThis": true,                   /* Raise error on 'this' expressions with an implied 'any' type. */
    "alwaysStrict": true,                     /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    "noUnusedLocals": true,                   /* Report errors on unused locals. */
    "noUnusedParameters": true,               /* Report errors on unused parameters. */
    "noImplicitReturns": true,                /* Report error when not all code paths in function return a value. */
    "noFallthroughCasesInSwitch": true,       /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": "./",                          /* Base directory to resolve non-absolute module names. */
    "paths": {                                /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
      "*": [
        "*.android",
        "*.ios",
        "*.native",
        "*.web",
        "*"
      ]
    },
    "typeRoots": [                            /* List of folders to include type definitions from. */
      "@types",
      "../../@types"
    ],
    // "types": [],                           /* Type declaration files to be included in compilation. */
    "allowSyntheticDefaultImports": true,     /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

    /* Experimental Options */
    "experimentalDecorators": true,           /* Enables experimental support for ES7 decorators. */
    "emitDecoratorMetadata": true             /* Enables experimental support for emitting type metadata for decorators. */
  },
  "exclude": [
    "node_modules",
    "web"
  ]
}

添加 tslint.json 文件

{
  "defaultSeverity": "warning",
  "extends": [
    "tslint:recommended", 
    "tslint-react",
    "tslint-config-airbnb",
    "tslint-config-prettier"
  ],
  "jsRules": {},
  "rules": {
    "curly": false,
    "function-name": false,
    "import-name": false,
    "interface-name": false,
    "jsx-boolean-value": false,
    "jsx-no-multiline-js": false,
    "member-access": false,
    "no-console": [true, "debug", "dir", "log", "trace", "warn"],
    "no-empty-interface": false,
    "object-literal-sort-keys": false,
    "object-shorthand-properties-first": false,
    "semicolon": false,
    "strict-boolean-expressions": false,
    "ter-arrow-parens": false,
    "ter-indent": false,
    "variable-name": [
      true,
      "allow-leading-underscore",
      "allow-pascal-case",
      "ban-keywords",
      "check-format"
    ],
    "quotemark": false
  },
  "rulesDirectory": []
}

添加 .prettierrc 文件:

{
  "parser": "typescript",
  "printWidth": 100,
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all"
}

編寫咱們的應用

在項目根目錄下建立一個 src 目錄,這個將是項目原代碼的目錄。

添加 src/App.tsx 文件

import React from 'react'

import { createAppContainer, createStackNavigator } from 'react-navigation'

import About from './screens/About'
import Article from './screens/Article'
import Home from './screens/Home'

const AppNavigator = createStackNavigator(
  {
    Home: { screen: Home },
    About: { screen: About, path: 'about' },
    Article: { screen: Article, path: 'article/:id' },
  },
  {
    initialRouteName: 'Home',
  },
)

const prefix = 'deep-linking://'

const App = createAppContainer(AppNavigator)

const MainApp = () => <App uriPrefix={prefix} />

export default MainApp

添加 src/screens/Home.tsx 文件

import React from 'react';

添加 src/screens/About.tsx

import React from 'react'

import { StyleSheet, Text, View } from 'react-native'

import { NavigationScreenComponent } from 'react-navigation'

interface IProps {}

interface IState {}

const AboutScreen: NavigationScreenComponent<IProps, IState> = props => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>About Page</Text>
    </View>
  )
}

AboutScreen.navigationOptions = {
  title: 'About',
}

export default AboutScreen

const styles = StyleSheet.create({
  container: {},
  title: {},
})

添加 src/screens/Article.tsx

import React from 'react'

import { StyleSheet, Text, View } from 'react-native'

import { NavigationScreenComponent } from 'react-navigation'

interface NavigationParams {
  id: string
}

const ArticleScreen: NavigationScreenComponent<NavigationParams> = ({ navigation }) => {
  const { params } = navigation.state

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Article {params ? params.id : 'No ID'}</Text>
    </View>
  )
}

ArticleScreen.navigationOptions = {
  title: 'Article',
}

export default ArticleScreen

const styles = StyleSheet.create({
  container: {},
  title: {},
})

配置 iOS

打開 ios/DeepLinkingExample.xcodeproj

open ios/DeepLinkingExample.xcodeproj

點擊 Info Tab 頁,找到 URL Types 配置,添加一項:

  • identifier:deep-linking
  • URL Schemes:deep-linking
  • 其它兩項留空

打開項目跟目錄下的 AppDelegate.m 文件,添加一個新的 import

#import "React/RCTLinkingManager.h"

而後在 @end 前面,添加如下代碼:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}

至此,咱們已經完成了 iOS 的配置,運行並測試是否成功。

react-native run-ios

打開 simulator 以後,打開 Safari 瀏覽器,在地址欄中輸入:deep-linking://article/4 ,你的應用將會自動打開,並同時進入到 Article 頁面。

一樣的,你還能夠在命令行工具中執行如下命令:

xcrun simctl openurl booted deep-linking://article/4

配置 Android

要爲Android應用也建立 External Linking,須要建立一個新的 intent,打開 android/app/src/main/AndroidManifest.xml,而後在 MainActivity 節點添加一個新的 intent-filter

<application ...>
  <activity android:name=".MainActivity" ...>
    ...
    <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="deep-linking" />
    </intent-filter>
    ...
  </activity>
</application>

Android 只須要完成上面的配置便可。

執行:

react-native run-android

打開系統瀏覽器,輸入:

deep-linking://article/4

系統會自動打開你的應用,並進入 Article 頁面

也能夠在命令行工具中使用如下命令打開:

adb shell am start -W -a android.intent.action.VIEW -d "deep-linking://article/3" com.deeplinkingexample;

附錄

點擊如下連接便可:

相關文章
相關標籤/搜索