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操作需要终端配合,维护页面路由栈


