hippy-react 三端同構 — 路由
- 2020 年 4 月 6 日
- 筆記
1. 背景介紹
Hippy
提供了 Navigator
組件,用於頁面導航、跳轉。
但是 Navigator
組件有比較大的局限性, 該組件通過啟動一個新的 Hippy 實例實現,在 2.0 下實例之間可能無法互相通訊,iOS 上也必須作為根節點包裹所有子組件,使用有很大限制。
@hippy/react
以及 @hippy/react-web
中的 Navigator
組件功能相對比較欠缺,兩端都沒有比較好的實現頁面跳轉的功能。兩端的功能也存在著差異,導致無法實現原生和web的同構
以下是 @hippy/react
和 @hippy/react-web
中的 Navigator
組件的實現方式
1.1 @hippy/react
路由實現
Navigator
組件中,通過實例化一個 Hippy
實例進行渲染展示,同時對 Android 的回退鍵進行監聽
// https://github.com/Tencent/Hippy/blob/312d0d963cac2d8cf60ff97ddd554a01e575cea0/packages/hippy-react/src/components/navigator.tsx#L125 constructor(props: NavigatorProps) { super(props); const { initialRoute } = props; if (initialRoute && initialRoute.component) { const hippy = new Hippy({ appName: initialRoute.routeName, entryPage: initialRoute.component, }); hippy.regist(); this.routeList[initialRoute.routeName] = true; } this.handleAndroidBack = this.handleAndroidBack.bind(this); }
通過調用原生 callUIFunction
執行頁面返回
public pop(option: { animated: boolean }) { if (this.stack.size > 1) { const options = [option]; this.stack.pop(); callUIFunction(this.instance, 'pop', options); } }
這種方式,兩個頁面之間無法進行數據互通,也無法傳遞數據
1.2 @hippy/react-web
路由實現
相比於 @hippy/react
, @hippy/react-web
中的 Navigator
組件則沒有對應的實現功能
//https://github.com/Tencent/Hippy/blob/master/packages/hippy-react-web/src/components/navigator.tsx /* eslint-disable class-methods-use-this */ import React from 'react'; import { formatWebStyle } from '../adapters/transfer'; /** * Simply router component for switch in multiple Hippy page. * @noInheritDoc */ class Navigator extends React.Component { pop() { // TODO } push() { // TODO } render() { const { style } = this.props; const newProps = Object.assign({}, this.props, { style: formatWebStyle(style), }); return ( <div {...newProps} /> ); } } export default Navigator;
2. hippy項目路由實現
使用
react-router
來管理多頁面,實現Hippy
原生和web的多頁面切換
2.1 hippy router選擇
在 react
中,主要是由 react-router
來進行頁面切換,支援多頁面開發。同時也有native版的 react-router-native
react-router-native
是 react-router
的native版本,但是其基於 react-native
中比較完善的 Navigator
組件。經過分析和實現,無法在 Hippy
中直接使用 react-router-native
react-router
中的 MemoryRouter
,基於純js實現的路由,不需要依賴於 URL
,這使得其可以應用在native
下面這個是關於 MemoryRouter
描述
A <Router> that keeps the history of your 「URL」 in memory (does not read or write to the address bar). Useful in tests and non-browser environments like React Native.
因此使用 react-router
可以同時支援原生和web頁面切換,進行多頁面開發
2.1 hippy中react-router使用
- 通過
Platform.OS
對當前平台進行判斷 - 在原生項目中使用
MemoryRouter
, 在web中使用HashRouter
- 通過
react-router
對多頁面進行切換
以下是 hippy
中 react-router
的使用方式
import React, { Component } from 'react'; import { StyleSheet, View, } from '@hippy/react'; import { MemoryRouter, HashRouter, Route, } from "react-router-dom"; import routes from './route'; import { ISWEB } from './utils'; export default class App extends Component { render() { const Router = ISWEB ? HashRouter : MemoryRouter; return ( <View> <Router> { routes.map(item => { return ( <Route key={item.path} exact path={`${item.path}`}><item.component meta={item.meta || {}} /></Route> ); }) } </Router> </View> ); } }
3. hippy-react三端同構router使用
3.1 使用 react-router
存在的問題
react-router
能夠在一定層度上解決 hippy
中多頁面跳轉的功能,是也存在一些問題
- 原生切換沒有動畫,體驗與web的一樣
- 無法使用
react-router-transition
動畫 - 原生的返回操作,直接回關閉
hippy
項目 Link
的使用過程中,需要傳入component
。 原因是Link
組件默認使用a
標籤,而hippy
中不支援a
標籤
// hippy 中 Link的使用方式 import { View } from '@hippy/react'; <Link to="/about" component={View}>About</Link>
3.2 頁面切換兼容
hippy
項目中頁面切換除了項目中的頁面切換,還存在著與客戶端或者瀏覽器的交互
hippy
頁面切換到客戶端原生頁面,需要客戶端提供偽協議跳轉支援。在web環境下,需要使用瀏覽器基礎能力。因此需要進行兼容處理
hippy
項目中的頁面切換主要有一下三種場景
場景 | 處理方式
—|—
hippy
項目內 | react-router
hippy
-> 原生 | 原生偽協議支援
hippy
-> web頁面 | window.location
或者 window.open
3.2.1 頁面切換兼容
* **原理分析**
react-router
通過 Context
將跳轉路由的函數, 如 goback
, push
,傳遞給組件
當組件需要使用到 react-router
功能時,通過 withRouter
高階組件,向組件注入路由跳轉函數
// withRouter 使用方式 // https://reacttraining.com/react-router/web/api/withRoute import React from "react"; import PropTypes from "prop-types"; import { withRouter } from "react-router"; class ShowTheLocation extends React.Component { static propTypes = { // 聲明propTypes,從而獲取router方法 match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired }; } const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
withRouter
實現原理
// https://github.com/ReactTraining/react-router/blob/402ecabdc94e5aeb657c593d8af200625a09cdfe/packages/react-router/modules/withRouter.js#L11 <RouterContext.Consumer> {context => { return ( <Component {...remainingProps} {...context} ref={wrappedComponentRef} /> ); }} </RouterContext.Consumer>
從 withRouter
的源碼分析來看,其中 context
包含了 router
所有的方式,提供給組件使用,因此可以在 context
這一層,按照不同的平台,進行個性化處理
* **解決方案**
通過實現 withRouter
的邏輯,在 context
進行劫持處理
import { Platform } from '@hippy/react'; import { withHippyHistory } from './history.hippy'; import { withWebHistory } from './history.web'; const ISWEB = Platform.OS === 'web'; const wrapper = ISWEB ? withWebHistory : withHippyHistory return ( <Component {...remainingProps} {...wrapper(context)} ref={wrappedComponentRef} /> );
- 在終端中,重寫路由跳轉函數,調用原生提供的跳轉方法
// history.hippy.js import { callNativeWithPromise } from "@hippy/react"; import { parsePath } from './util'; const createHook = (history, ) => { function push (path, state) {} function replace () {} function go () {} function goBack () {} function goForward () {} function open ({ hippyUrl }) { return callNativeWithPromise('TgclubJsModule', 'callNativeAction', hippyUrl) } return { push, go, goBack, goForward, replace, open } } export function withHippyHistory (context) { const { history } = context; return { ...context, history: { ...history, ...createHook(history) } } }
- 提供額外的
open
函數,用於跳轉到非本項目的頁面,使得在對業務層無感知。
// history.web.js const createHook = (history) => { function open ({ webUrl, config }) { if (webUrl) { window.open(webUrl); } } return { open } } export function withWebHistory (context) { const { history } = context; return { ...context, history: { ...history, ...createHook(history) } } }
通過對 context
中函數的改造,統一不同平台的頁面切換調用方式。而在 wrapper
裡面針對平台進行特殊處理
4. 遺留問題
- 頁面切換動畫
hippy
項目內頁面跳轉適配系統返回上一頁動作replace
操作需要終端配合,維護頁面路由棧