本文主要是介绍React实现原生APP切换效果,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常...
背景
最近需要使用 Hybrid
的方式开发一 个 APP
,交互和原生 APP
相似并且需要 IM
通信。根据目前公司已有的实现方案,每次进入一个新的页面时,可以调用 Native
提供的 createWebview
方法重新创建一个 Webview
,这样在打开一个页面时,就能像原生 APP
一样实现 push
或 pop
的效果。但新创建 Webview
后,之前 Webview
中的长链就会被挂起,IM
消息可能被中断,这样在功能使用上会存在着一些问题。
经过讨论,有想到两种可行的解决方案。
- 长链下沉到
Native
。我们调用Native
提供的API
来实现IM
的唯一。但它有个缺点,以后有调整,Native
也需要发版,并且不太好保证不同Webview
中IM
信息的一致性和及时性。 - 用
H5
实现一个原生APP
的切换效果。让系统成为一个单页应用。这样长链就可以是唯一的并且不需要下沉到Native
端。
最后决定尝试使用第二种方案。
需求概览
- 页面进入时从右向左推入、返回上一页时页面从左向右推出。
- 进入下一页时,下一页的数据需要重新请求。
- 返回上一页时,保持上一页的数据展示并且不用重新请求数据。
先看一下前后效果对比。
网页默认切换效果
仿原生切换效果
技术栈
- React v18.2.0
- react-router-dom v6.14.2
- react-transition-group v4.4.5
- react-activation v0.12.4
实现步骤
根据 react-router-dom 文档配置好路由
配置路由
// router.tsx // ... const router = createBrowserRouter([ { path: "/", element: <BaseLayout />, errorElement: <ErrorBoundary />, children: [ { index: true, element: <Home />, }, // 登录注册页面 { path: "/login", element: <Login />, }, ], }, ]); export default router;
每个页面都是 BaseLayout
的子节点。
// BaseLayout.tsx function BaseLayout() { // ... return <Outlet />; } export default BaseLayout;
在项目的入口添加 RouterProvider
import React from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider } from "react-router-dom"; import router from "@/router"; import "@/assets/styles/global.less"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> {/* router */} <RouterProvider router={router} /> </React.StrictMode> );
配置路由:createBrowserRouter | React Router API Reference
添加过渡动画
过渡动画使用 react-transition-group
库。
react-transition-group
组合着 react-router-dom v6
使用时,需要给每个 rouer
都添加一个 nodeRef
(跟着官方文档 demo
来)。
修改 router.tsx
。
- 将路由展平并
export
出去,方面在其他页面调用。 - 给导出去的
newRouter
每个路由都添加一个nodeRef
。
// router.tsx // ... // 所有的路由都在这里配置 const defaultRouters: RouteObject[] = [ { index: true, path: "/", element: <Home />, }, // 登录注册页面 { path: "/login", element: <Login />, } ]; /** * 将路由展平,并添加 nodeRef 字段 * @param routerParams RouteObject[] * @returns RouteObject[] */ function flatRouters(routerParams: RouteObject[]) { let newRouters: Array<RouteObject & { nodeRef: RefObject<any> }> = []; routerParams.forEach((router) => { newRouters.push({ ...router, nodeRef: createRef(), }); if (router.children?.length) { newRouters = newRouters.concat(flatRouters(router.childrenphp)); } }); return newRouters; } const newRouters = flatRouters(defaultRouters); // react-router-dom 创建的路由 const router = createBrowserRouter([ { path: "/", element: <BaseLayout />, errorjavascriptElement: <ErrorBoundary />, children: defaultRouters, }, ]); export default router; export { newRouters };
在 BaseLayout
中添加过渡。根据 useNavigationType
获取当前页面是 push
还是 pop
更改 cssTransition
的 className
。
// BaseLayoutandroid.tsx import React, { useEffect } from "react"; import { useoutlet, useNavigationType } from "react-router-dom"; import { CSSTransition, TransitionGroup } from "react-transition-group"; import { newRouters } from "@/router"; import "./style.less"; const ANIMATION_MAP = { PUSH: "forward", POP: "back", REPLACE: "fade-route", }; // 授权组件 function BaseLayout() { const candroidurrentOutlet = useOutlet(); javascript const navigateType = useNavigationType(); const { nodeRef } = newRouters.find((route) => route.path === location.pathname) ?? {}; const fullPath = `${location.pathname}${location.search}`; return ( <TransitionGroup childFactory={(child) => React.cloneElement(child, { classNames: ANIMATION_MAP[navigateType] }) } > <CSSTransition key={location.pathname} nodeRef={nodeRef} timeout={500} unmountOnExit > {() => ( <div ref={nodeRef}> {currentOutlet} </div> )} </CSSTransition> </TransitionGroup> ); } export default BaseLayout;
因为 react-transition-group
是结合着 css-transition
一起使用的,使用 CSSTransition
组件,它会自动地在页面过渡时,给节点加上:
*-enter
*-enter-active
*-enter-done
*-exit
*-exit-active
*-exit-done
- ...
等 className
,所以再添加一下对应的 CSS
动画效果,过渡的效果就实现了。
// style.less /* 路由前进时的入场/离场动画 */ .forward-enter { .base-layout; transform: translate3d(100vw, 0, 0); z-index: 2; } .forward-enter-active { .base-layout; transform: translate3d(0, 0, 0); transition: all 500ms; z-index: 2; } .forward-exit { .base-layout; transform: translate3d(0, 0, 0); z-index: 1; } .forward-exit-active { .base-layout; transform: translate3d(-100vw, 0, 0); transition: all 500ms; z-index: 1; } /* 路由后退时的入场/离场动画 */ .back-enter { transform: translate3d(-100vw, 0, 0); z-index: 1; } .back-enter-active { .base-layout; transform: translate3d(0, 0, 0); transition: all 500ms ease-out; z-index: 1; } .back-exit { .base-layout; transform: translate3d(0, 0, 0); z-index: 2; } .back-exit-active { .base-layout; transform: translate3d(100vw, 0, 0); transition: all 500ms ease-out; z-index: 2; }
到目前为止,和 Native
一样的切换效果就都实现了。
但 Native
还有一个特点,只有进入下一页时才会重新请求数据,返回上一页时,是直接展示之前的页面,不需要再重新请求数据。
这个可以使用虚拟任务栈的方式来缓存页面,以达到返回上一页时,不需要重新请求并重新渲染页面的效果。
react-transition-group: React Transition Group
使用虚拟任务栈缓存页面
虚拟任务栈是使用 react-activation
包来实现的。
安装好后,在 main.tsx
处使用 AliveScope
将 RouterProvider
包裹起来。
ReactDOM.createRoot(document.getElementById("root")!).render( <Provider store={store}> <AliveScope> {/* router */} <RouterProvider router={router} /> </AliveScope> </Provider> );
然后在 BaseLayout.tsx
处给子页面用 KeepAlive
包裹起来。
// BaseLayout.tsx // 授权组件 function BaseLayout() { // ... return ( <TransitionGroup childFactory={(child) => React.cloneElement(child, { classNames: ANIMATION_MAP[navigateType] }) } > <CSSTransition key={location.pathname} nodeRef={nodeRef} timeout={500} unmountOnExit > {() => ( <div ref={nodeRef}> <KeepAlive id={fullPath} saveScrollPosition="screen" name={fullPath} > {currentOutlet} </KeepAlive> </div> )} </CSSTransition> </TransitionGroup> ); } export default BaseLayout;
这样,所有访问过的页面都会被缓存起来。
返回上一页时,我们需要清理掉当前页面的缓存,使页面再次进入时,可以重新请求并渲染页面。
封装一个 useGoBack()
方法。
// useGoBack.tsx import { useNavigate } from "react-router-dom"; import { useAliveController } from "react-activation"; // 页面返回 hooks const useGoBack = () => { const navigate = useNavigate(); const { dropScope, getCachingNodes } = useAliveController(); return (pageNum = -1) => { const allCachingNodes = getCachingNodes() || []; navigate(pageNum); // 清除 keepAlive 节点缓存 const pageNumAbs = Math.abs(pageNum); const dropNodes = allCachingNodes.slice( allCachingNodes.length - pageNumAbs ); dropNodes.forEach((node) => { dropScope(node.name!); }); }; }; export default useGoBack;
使用 自定义 Hooks - useGoBack()
返回上一页的页面,当前页面就会从缓存中被清理掉,再将进入页面时,会重新走 useEffect
等生命周期。
注意
- 需要根据
useNavigationType
获取当前页面是push
还是pop
更改CSSTransition
的className
。 CSSTransition
下面要紧挨着需要过渡的div
,KeepAlive
要放在这个div
下面。react-activation
需要配置babel
。- 返回上一页时,一定要清理掉不需要的缓存页面,以防止缓存页面过多,页面使用卡顿。
- 要实现两个页面同时在页面上展示并过渡,需要使用
TransitionGroup
。
以上就是React实现原生APP切换效果的详细内容,更多关于React APP切换的资料请关注China编程(www.chinasem.cn)其它相关文章!
这篇关于React实现原生APP切换效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!