zoukankan      html  css  js  c++  java
  • 单页应用后退不刷新方案(vue & react)试一下有惊喜

    引言

    前进刷新,后退不刷新,是一个类似app页面的特点,要在单页web应用中做后退不刷新,却并非一件易事。

    为什么麻烦

    spa的渲染原理(以vue为例):url的更改触发onHashChange/pushState/popState/replaceState,通过url中的pathName去匹配路由中定义的组件,加载进来并实例化渲染在项目的出口router-view中。

    换言之,一个实例的解析渲染意味着另外一个实例的销毁,因为渲染出口只有一个。

    keep-alive为什么不行?因为keep-alive的原理是将实例化后的组件存储起来,当下次url匹配到了改组件时,优先从存储里面取。

    但是vue只提供了入存储的方式,没提供删存储的方式,所以没法实现“前进刷新”。

    有一种方案是手动根据to和from去做前进后退判断,这种判断不能应对复杂的跳转逻辑可维护性也很差

    有坑的社区方案(以vue为例)

    vue-page-stackvue-navigation

    这两个方案都有明显缺点:前者不支持嵌套路由,在一些场景下会出现url变化,页面完全无反应的情况,后者存在类似的bug。并且这两种方案侵入性都很强,因为他们都是基于vue-router的魔改。并且会在url中增加无意义的多余字段(stackID)

    目前不错的方案

    现在有一个可行且简单的方案:嵌套子路由 + 叠页面

    叠页面的灵感:原生应用中的webview in webview,多页应用中的window in window

    要在spa中实现后退不刷新,本质是要实现多实例共存

    这个方案的核心在于:通过嵌套子路由实现多实例共存,通过css的absolute实现视觉上的页面堆叠

    上效果图

     

    vue中的实现

    在routes配置文件中:

    import Home from "../views/Home.vue";

    const routes = [
      {
        path"/home",
        name"Home",
        component: Home,
        children: [
          {
            path"sub",
            component() =>
              import(/* webpackChunkName: "sub" */ "../views/Sub.vue"),
          },
        ],
      },
    ];

    export default routes;

    主页:

    <template>
      <div class="home">
        <input v-model="inputValue" />
        <h3>{{ inputValue }}</h3>
        <button @click="handleToSub">to sub</button>
        <router-view @reload="handleReload" />
      </div>
    </template>

    <script>
    export default {
      name"Home",
      data() {
        return {
          inputValue"",
        };
      },
      methods: {
        handleToSub() {
          // 注意路由格式,是基于上一个路由/home下面的sub,不是独立的/sub
          this.$router.push("/home/sub");
        },

        handleReload(val) {
          // 这里可以做一些重新获取数据的操作,比如在详情页修改数据,返回后重新拉取列表
          console.log("reload", val);
        },
      },
      mounted() {
        // 子页面返回,不会重新跑生命周期
        console.log("mounted");
      },
    };
    </script>

    <style scoped>
    .home {
      position: relative;
    }
    </style>

    子页面:

    <template>
      <div class="sub">
        <h1>This is Sub page</h1>
      </div>

    </template>

    <script>
    export default {
      beforeDestroy() {
        /
    / 可以传自定义参数,如果没需要,也可以不做
        this.$emit("reload", 123);
      },
    };
    </
    script>

    <style scoped>
    .sub {
      position: absolute;
      left0;
      top0;
      width100%;
      height100%;
      background-color#fff;
    }
    </style>

    react中的实现

    在routes中:

    import { Route } from "react-router-dom";

    const Routes = () => {
      return (
        <>
          {/* 这里不能加exact,因为要先匹配父页面再匹配子页面 */}
          <Route path="/home" component={lazy(() => import("../views/Home"))} />
        </>
      );
    };

    export default Routes;

    主页:

    import React, { useEffect, useState } from "react";
    import { Route, useHistory } from "react-router-dom";
    import styled from "styled-components";
    import Sub from "./Sub";

    const HomeContainer = styled.div`
      position: relative;
    `
    ;

    const Home: React.FC = () => {
      const [inputValue, setInputValue] = useState("");
      const history = useHistory();

      const handleToSub = () => {
        history.push("/home/sub");
      };

      const handleReload = (val: number) => {
        console.log("reload", val);
      };

      useEffect(() => {
        console.log("mounted");
      }, []);

      return (
        <HomeContainer>
          <input
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
          />
          <h3>{inputValue}</h3>
          <button onClick={handleToSub}>to sub</button>
          <Route
            path="/home/sub"
            component={() => <Sub handleReload={handleReload} />}
          />
        </HomeContainer>
      );
    };

    export default Home;

    子页面:

    import React from "react";
    import styled from "styled-components";

    const SubContainer = styled.div`
      position: absolute;
      left: 0;
      top: 0;
       100%;
      height: 100%;
      background-color: #fff;
    `
    ;

    type SubProps = {
      handleReload(val: number) => void;
    };

    const Sub: React.FC<SubProps> = ({ handleReload }) => {
      useEffect(() => {
          return () => handleReload(123);
      }, []);

      return (
        <SubContainer>
          <h1>This is Sub page</h1>
        </SubContainer>

      );
    };

    export default Sub;

    题外

    在前司的核心项目“平安好车主”中,我就在部分h5新项目用了该方案,在线上经受住了170w+访问量的考验。目前在Shopee也在推行这种h5方案,由于逻辑简单,得到了不少同事的认可和使用。比如常见的:列表页存在搜索条件,进入详情页再返回。 大家可以试用一下,会有惊喜的。

    该方案的优点

    • 实现简单,无侵入式修改,几乎0逻辑;
    • 子页面可以单独提供出去,供三方接入;
    • 完全的多实例共存,后退不刷新;
    • 可以像父子组件一样通信,监听子页面离开;

    缺点

    路由格式需要做改造,必须做成嵌套关系,对url有一定要求。

    github地址

    https://github.com/zhangnan24/no-refresh-back-vue

  • 相关阅读:
    MondoDB 之 使用python对MongoDB排序 MondoDB -9
    MondoDB 之 使用python对MongoDB进行文档修改 MondoDB -8
    MondoDB 之 使用python对MongoDB进行查询文档 MondoDB -7
    MondoDB 之 使用python对MongoDB进行插入文档 MondoDB -6
    MondoDB 之 使用python操作MongoDB MondoDB -5
    MondoDB 之 $关键字 及 $修改器 $set $inc $push $pull $pop MondoDB -4
    MondoDB 之 数据类型 MondoDB -3
    MongoDB 之 操作增删改查 MongoDB
    简述常见数据库高可用方案
    关于MySql数据库误操作数据找回的办法
  • 原文地址:https://www.cnblogs.com/zhangnan35/p/15344231.html
Copyright © 2011-2022 走看看