React实现app页面切换效果

在移动端web页面切换的时候增加页面进入退出的动画效果,在vue中可以使用keepalive标签和transition轻松实现,在react中,可以使用react-activation和动画库实现类似效果,这里使用react-transition-group。参考:微信公众号 - 前端学习总结 

使用react-router-dom配置好路由,这里使用v6版本的数据路由书写方式。需要添加的动画的页面使用同一个outlet。

使用React-transition-group

配置路由

/// router.tsx

const router = createBrowserRouter([
  {
    path: "/",
    element: <BaseLayout />,
    errorElement: <ErrorBoundary />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      // 登录注册页面
      {
        path: "/login",
        element: <Login />,
      },
    ],
  },
]);

export default router;

/// BaseLayout.tsx

function BaseLayout() {
  // ...
  return <Outlet />;
}
export default BaseLayout;

/// App.jsx

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>
);

添加过渡动画

react-transition-group 结合react-router v6使用时 需要给每个router配置一个nodeRef。官方示例:react-transition-group/with-react-router

// 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.children));
    }
  });
  return newRouters;
}

const newRouters = flatRouters(defaultRouters);

// react-router-dom 创建的路由
const router = createBrowserRouter([
  {
    path: "/",
    element: <BaseLayout />,
    errorElement: <ErrorBoundary />,
    children: defaultRouters,
  },
]);

export default router;

export { newRouters };

在 BaseLayout 中添加过渡。根据 useNavigationType 获取当前页面是 push 还是 pop 更改 CSSTransition 的 className

// BaseLayout.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 currentOutlet = useOutlet();
  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;
}

使用虚拟任务栈缓存页面

在进入下一页时重新请求,返回上一页时展示缓存页面。

使用React-activation 包实现缓存页面

// app.tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
  <Provider store={store}>
    <AliveScope>
      {/* router */}
      <RouterProvider router={router} />
    </AliveScope>
  </Provider>
);

// BaseLayout.tsx
// 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;

这样,所有访问过的页面都会被缓存起来。

返回上一页时,我们需要清理掉当前页面的缓存,使页面再次进入时,可以重新请求并渲染页面。

封装一个自定义 Hooks useGoBack() 。

使用 自定义 Hooks - useGoBack() 返回上一页的页面,当前页面就会从缓存中被清理掉,再将进入页面时,会重新走 useEffect 等生命周期。

// 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;

注意

  • 需要根据 useNavigationType 获取当前页面是 push 还是 pop 更改 CSSTransition 的 className

  • CSSTransition 下面要紧挨着需要过渡的 divKeepAlive 要放在这个 div 下面。

  • react-activation 需要配置 babel

  • 返回上一页时,一定要清理掉不需要的缓存页面,以防止缓存页面过多,页面使用卡顿。

  • 要实现两个页面同时在页面上展示并过渡,需要使用 TransitionGroup

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现中英文切换,最常见的做法是使用国际化(i18n)库,例如 react-intl。以下是基本的实现步骤: 1. 安装 react-intl 库:`npm install react-intl` 2. 创建一个包含中英文文本的语言文件,例如 `src/lang.json`: ``` { "hello": { "en": "Hello", "zh": "你好" }, "world": { "en": "World", "zh": "世界" } } ``` 3. 在组件中引入 `FormattedMessage` 组件,并使用 `id` 属性指定需要显示的文本对应的键值: ```jsx import { FormattedMessage } from 'react-intl'; import lang from './lang.json'; function Greeting() { return ( <div> <FormattedMessage id="hello" defaultMessage="Hello" />{' '} <FormattedMessage id="world" defaultMessage="World" /> </div> ); } ``` 4. 在顶层组件中使用 `IntlProvider` 组件,指定当前语言和语言文件: ```jsx import { IntlProvider } from 'react-intl'; import lang from './lang.json'; function App() { const locale = 'zh'; // or 'en' return ( <IntlProvider locale={locale} messages={lang[locale]}> <Greeting /> </IntlProvider> ); } ``` 5. 在需要切换语言的地方,更新 `locale` 和 `messages` 属性即可。 ```jsx function LanguageSwitcher() { const [locale, setLocale] = useState('zh'); function handleSwitch() { setLocale(locale === 'zh' ? 'en' : 'zh'); } return ( <div> <button onClick={handleSwitch}>Switch Language</button> <IntlProvider locale={locale} messages={lang[locale]}> <Greeting /> </IntlProvider> </div> ); } ``` 以上就是使用 react-intl 实现中英文切换的基本步骤。当然,具体实现还可以根据项目需求进行调整和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值