在iOS 與Android上實現React Native應用的嘗試鏈接
我們生活在一個萬物兼可分享的年代,而分享的過程,幾乎最終都會分享某一個鏈接,那么,作為開發者,最常遇到的問題中應該包括如何通過一個URL地址快速的打開App,并導航至特定的頁面。
什么是深度鏈接(Deep Link)
深度鏈接是一項可以讓一個App通過一個URL地址打開,之后導航至特定頁面或者資源,或者展示特定UI的技術,Deep 的意思是指被打開的頁面或者資源并不是App的首頁,最常使用到的地方包括但遠遠不限于 Push Notification、郵件、網頁鏈接等。
其實這個技術在很久很久以前就已經存在了,鼠標點擊一下 mailto:pantao@parcmg.com 這樣的鏈接,系統會打開默認的郵件軟件,然后將 pantao@parcmg.com 這個郵箱填寫至收件人輸入欄里,這就是深度鏈接。
本文將從零開始創建一個應用,讓它支持通過一個如 deep-linking://articles/{ID} 這樣的 URL 打開 文章詳情 頁面,同時加載 {ID} 指定的文章,比如:deep-linking://articles/4 將打開 ID 為 4 的文章詳情頁面。
深度鏈接解決了什么問題?
網頁鏈接是無法打開原生應用的,如果一個用戶訪問你的網頁中的某一個資源,他的手機上面也已經安裝了你的應用,那么,我們要如何讓系統自動的打開應用,然后在應用中展示用戶所訪問的那一個頁面中的資源?這就是深度鏈接需要解決的問題。
實現深度鏈接的不同方式
有兩種方式可以實現深度鏈接:
- URL scheme
- Universal links
前端是最常見的方式,后者是 iOS 新提供的方式,可以一個普通的網頁地址鏈接至App的特定資源。
本文將創建一個名為 DeepLinkingExample 的應用,使得用戶可以通過打開 deep-linking://home 以及 deep-linking://articles/4 分別打開 App 的首頁以及 App 中 ID 為 4 的文章詳情頁面。
- react-native init DeepLinkingExample
- cd DeepLinkingExample
安裝必要的庫
緊跟 TypeScript 大潮流,我們的 App 寫將使用 TypeScript 開發。
- 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;