keep-alive 在详情页(表单)上的尝试

926 阅读1分钟

需求

列表页上每个Row上都有【编辑】功能,点击进去就是详情页(有表单编辑的需求)

 <el-table-column label="查看详情">
    <template v-slot="{ row }">
      <el-link
        @click="$router.push({name: 'info', query: { id: row.id }})"
        type="primary">查看详情</el-link>
    </template>
  </el-table-column>

keep-alive实现

  • vuex保存顶部路由消息
const keepRouter = {
  state: {
    router: [],
  },
  mutations: {
    addRouter(state, router) {
      // 可同时存在: 仅校验path(router-link :key=item.path)
      const index = state.router.findIndex((item) => item.path === router.path);
      if (index === -1) {
        state.router.push(router);
      }
    },
    removeRouter(state, index) {
      const { router } = state;
      router.splice(index, 1);
      state.router = router;
    },
  },
}
  • router使用beforeEnter,将路由信息保存到vuex里
import store from './store';

const routes = [
  ...,
  {
    path: '/info',
    name: 'info',
    component: infoCom,
    meta: { title: 'info' },
    beforeEnter: function (to, from, next) {
      store.commit('addRouter', {
        title: to.meta.title,
        name: to.name,
        path: to.fullPath,
      });
      next();
    },
  }
]
  • keep-alive的父级组件上展示vuex的信息
computed: {
    list() {
      return this.$store.state.keepRouter.router;
    },
  },

 <router-link
    v-for="(item, index) in list"
    :key="item.path"
    :to="{path: item.path}"
    :active-class="$style.action"
    exact>
    <span>{{ item.title }}</span>
    <i
      v-if="list.length > 1"
      @click.prevent="remove(index, item)"
      class="el-icon-close"></i>
  </router-link>
  • keep-alive上设置exclude参数(router-view必须具有key)
data() {
    return {
      exclude: [],
    };
},

<keep-alive :exclude="exclude">
  <router-view :key="$route.fullPath"></router-view>
</keep-alive>
  • 关闭的页面设置exclue,半秒后还原
methods: {
    ...,
    remove(index, route) {
      if (route.path === this.$route.fullPath) {
        // 加载上一路由或下一个路由
        const toPath = this.list[index === 0 ? 1 : index - 1].path;
        this.$router.push({ path: toPath });
      }
    
      this.setExclude([route.name]);
      this.$store.commit('removeRouter', index);
    },
    setExclude(name) {
      // 可消除对应组件的缓存
      this.exclude = name;
      setTimeout(() => {
        this.exclude = [];
      }, 500);
    },
},

问题


总结:匹配规则是组件的name

这时在上面需求中就会出现问题,因为即便2个url不一样的详情页面name="info",用到的详情组件是一样的。只是url不同,获取到的详情页数据也不同。这时设置的exclude属性会将这个组件销毁,即便它url不一样。

另辟蹊径

通过exclude了解到,它会将name="info"的组件都销毁,那么我们在销毁时保存不需要销毁页面的数据呢

  • vuex增加缓存功能
const keepRouter = {
  state: {
    router: [],
    destroyPath: '',   // 关闭的页面
    cache: {},         // 缓存数据
  },
  mutations: {
    addRouter(state, router) { ... },
    removeRouter(state, index) { ... },
    // 新增
    addCache(state, { key, cache }) {
        state.cache[key] = cache;
    },
    removeCache(state, key) { 
        const { cache } = state;
        delete cache[key];
        state.cache = cache;
    },
    setDestroyPath(state, fullPath) {
        state.destroyPath = fullPath;
        // 一次性关闭多个同组件时,删除缓存
        state.cache[fullPath] && this.commit('removeCache', fullPath);
    },
}
  • 保存需要关闭页面的路径
remove(index, route) {
  this.$store.commit('setDestroyPath', route.path); // 保存关闭的路径

  if (route.path === this.$route.fullPath) {
    const toPath = this.list[index === 0 ? 1 : index - 1].path;
    this.$router.push({ path: toPath });
  }

  this.setExclude([route.name]);
  this.$store.commit('removeRouter', index);
},
  • 详情页的处理(这里可以做成mixin,引入到需要的组件上)
info组件


export default {
  name: 'info',
  data() {
    return {
      currentPath: '',
      form: {
          xxx: '',
          yyy: '',
          zzz: '',
      },
    };
  },
  created() {
    const { fullPath } = this.$route;
    this.currentPath = fullPath;

    const cache = this.$store.state.keepRouter.cache[fullPath];
    // 是否有缓存
    if (cache) {
      this.$store.commit('removeCache', fullPath);
      Object.keys(cache).forEach((key) => {
        this[key] = cache[key];
      });
    } else {
        // 请求详情数据
        ...
    }
  },
  beforeDestroy() {
    const { currentPath } = this;
    if (currentPath !== this.$store.state.keepRouter.destroyPath) {
      this.$store.commit('addCache', {
        key: currentPath,
        cache: JSON.parse(JSON.stringify(this.$data)),
      });
    }
  },
};
</script>

注:因为关闭页面时fullPath是未知了,可能会在list页面上就关闭info页面,所以必须保存fullpath

注意事项

  • keepRouter保存的路由信息是router里的name,exclude是组件的name,所以这里必须一致

结语

这只是我的一个解题思路,希望有更好的解决方案