我们在使用vue构建单页应用的时候,能给我们的效率带来很大的提升,但是紧随而来的也有一些新框架的问题。
1,比如我们公司在移动端微信上面的项目,如果所有项目都合并成一个,首页加载速度就会非常慢,所以我就去改webpack配置,把它变成多页应用。点击微信菜单后,只加载当前菜单项的所有内容,也就是以菜单项分为分页的标准,在当前菜单项里面通过vue的路由控制。
2,单页应用路由切换,微信的title不会改变,经过查阅资料,发现网上有一个解决方案,就是利用vue-wechat-title这个插件可以解决;
3,还有就是Vue使用动态组件或者router时,当多个视图使用/指向同一个组件时,如何能够生成多个新实例?比如文章页里面有个相关文章阅读,点击之后会一直复用文章页的组件,这个问题困扰我很久,网上查阅资料也没有具体的方法,在作者官网上市这么说的:
响应路由参数的变化
提醒一下,当使用路由参数时,例如从
/user/foo
导航到user/bar
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch(监测变化)
$route
对象:const User = { template: '...', watch: { '$route' (to, from) { // 对路由变化作出响应... } } }
道理说的很明白了,只要是同一个组件,不管你切换多少次,它都会共用一个数据模型,虽然通过路由监听的方式可以检测到同组件的路由切换,但是并没有什么卵用,因为它并没有解决创建多个数据实例的问题,每当切换页面的时候,页面都会去拉取页面,造成了请求和带宽的浪费,用户体验十分不好。那到底有没有解决办法呢?答案是肯定的。经过我不断地尝试,找到了一个解决办法,而且可行。具体思路就是我们通过自己去创建管理删除组件的实例,然后用vuex状态管理区存储它(这个也不是必选项,因为相同组件之间切换,组件的数据实例并不会销毁,切换页面后之前页面的数据依然可以使用)。我们通过在路由上面传递不同的参数来区分在不同路由的情况下来显示哪个数据实例。
这里面还有一些细节:
a:创建新路由页面的时候,要创建新实例;
b:返回路由后需要把当前的实例删除掉,不然会一直占用内存;
c:文章组件返回到列表组件,然后再进入文章组件后,文章组件实例不会再创建
解决办法:贴出我的具体代码
index.js
import Vue from 'vue'; import router from './router'; import store from './store'; import index from './components/index'; import vueWechatTitle from 'vue-wechat-title'; Vue.use(vueWechatTitle); Vue.config.productionTip = false; //绑定vue new Vue({ el: '#contract', router, store, template: '<index/>', components: {index} });
router.js
import Vue from 'vue'; import Router from 'vue-router'; import contractList from './components/contract-list'; import contractDetail from './components/contract-detail'; Vue.use(Router); const router = new Router({ routes: [ { path: '/', name: 'contractList', meta: { title: '合同列表', keepAlive:true }, component: contractList }, { path: '/contractDetail/:timestamp', name: 'contractDetail', meta: { title: '合同详情', keepAlive:true }, component: contractDetail } ] }); export default router;
store.js
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const state = {}; const actions = {}; const getters = {}; const mutations = { addData: (state, res) => { if (!state.hasOwnProperty(res.key)) { Vue.set(state, res.key, res.value); } console.log('set', state); },//增 deleteData: (state, key) => { console.log(key); delete state[key]; console.log('del', state); },//删 updateData: (state, res) => { if (state.hasOwnProperty(res.key)) { for (let item in res.value) { state[res.key][item] = res.value[item]; } } console.log('updateData', state); },//改 getData: (state, key) => { if (state.hasOwnProperty(key)) { return state[key]; } else { return null; } console.log('get', state); }//查 }; export default new Vuex.Store({ state, actions, getters, mutations });
下面是模板页面
index.vue
<template> <div id="contract"> <keep-alive> <router-view v-if="$route.meta.keepAlive" v-wechat-title="$route.meta.title"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive" v-wechat-title="$route.meta.title"></router-view> </div> </template> <script> export default { data() { return {} }, computed: {}, created() { }, mounted() { }, methods: {}, watch: {} } </script>
contract-list.vue
<template> <div id="contract-list"> <input type="text" v-model="msg"> <router-link :to="'/contractDetail/'+new Date().getTime()">点击进详情页</router-link> </div> </template> <script> export default { data() { return { msg: '' } }, computed: {}, created() { }, mounted() { }, methods: {}, watch: {} } </script> <style scoped> #contract-list { color: red; } </style>
contract-detail.vue
<template> <div id="contract-detail"> <input type="text" v-if="currentData" v-model="currentData.msg"> 我是合同详情页 <router-link :to="'/contractDetail/'+new Date().getTime()">进如下页详情</router-link> {{currentData}} </div> </template> <script> import {mapState} from 'vuex'; export default { data() { return {} }, computed: { currentKey() { return this.$route.name + this.$route.params.timestamp; }, ...mapState({ currentData(state) { return state[this.currentKey]; } }) }, created() { console.log('created'); this.initModel(); }, mounted() { console.log('mounted'); }, methods: { createModel() { class model { constructor() { this.msg = ''; } } return new model(); }, initModel() { this.$nextTick(function () { if (!this.$store.state.hasOwnProperty(this.currentKey)) { console.log('initModel-set'); this.$store.commit('addData', { key: this.currentKey, value: this.createModel() }); } }); } }, watch: { currentData: { handler: function (value) { console.log('watch-set', this.currentKey); this.$nextTick(function () { this.$store.commit('updateData', { key: this.currentKey, value: value }); }); }, deep: true } }, beforeRouteUpdate(to, from, next) { console.log('beforeRouteUpdate1', to, from); //清除老对象 if (to.params.timestamp < from.params.timestamp) { this.$store.commit('deleteData', from.name + from.params.timestamp); } //创建新对象 console.log('beforeRouteUpdate2', to, from); if (to.name == from.name) { console.log(to.name, from.name, to.name == from.name); this.initModel(); } next(); }, beforeRouteLeave(to, from, next) { console.log('beforeRouteLeave', to, from); this.$store.commit('deleteData', from.name + from.params.timestamp); this.$destroy(); next(); } } </script> <style scoped> #contract-detail { color: green; } </style>