vue.js开发总结(持续更新中~)
1.VUE 路由变化页面数据不刷新问题问题描述:(知乎项目)新闻板块有推荐、精华、最新等几个Tab,设想通过切换Tab,改变路由参数(get/news/:tab)去获取对应数据,然后渲染到页面(用的是同一套组件),问题来了:当切换Tab时,数据并没有更新原因:提醒一下,当使用路由参数时,例如从/user/foo导航到user/bar,原来的组件实例会被复用。因为两个路由都渲染同...
1.VUE 路由变化页面数据不刷新问题
问题描述:(知乎项目)新闻板块有推荐、精华、最新等几个Tab,设想通过切换Tab,改变路由参数(get/news/:tab)去获取对应数据,然后渲染到页面(用的是同一套组件),问题来了:当切换Tab时,数据并没有更新
原因:提醒一下,当使用路由参数时,例如从 /user/foo
导航到 user/bar
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。意思很明显了:虽然路由参数发生了变化,但组件还是那个组件,Vue生命周期也还没结束,此时并不会刷新数据。
解决:1.复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch(监测变化) $route
对象:
watch: {
// 方法1
'$route' (to, from) { //监听路由是否变化
if(this.$route.params.articleId){// 判断条件1 判断传递值的变化
//获取文章数据
}
}
//方法2
'$route'(to, from) {
if (to.path == "/page") { /// 判断条件2 监听路由名 监听你从什么路由跳转过来的
this.message = this.$route.query.msg
}
}
}
2.给<router-view></router-view>添加一个具有唯一值的 key
属性,声明“这两个元素是完全独立的——不要复用它们”。
关于key的作用,可以看看知乎的一篇文章
<router-view :key="key"></router-view>
computed: {
key() {
return this.$route.name !== undefined? this.$route.name +new Date(): this.$route +new Date()
}
}
使用computed属性和Date()可以保证每一次的key都是不同的,这样就可以如愿刷新数据了。
2.vue 滚动行为
问题描述:在页面拉动滚动条,然后刷新浏览器会发现滚动条依然在原来的位置,这是浏览器的默认行为,会记录浏览器滚动条默认位置。但是点击浏览器“前进/后退”按钮,会发现当初那个页面的滚动条从0开始了,没有记录上一次滚动条的位置。现在要求点击浏览器“前进/后退”按钮,页面滚动条要记录上一次的位置,这时需要设置它的的滚动行为。
解决:在路由配置中设置 scrollBehavior(to,from,savePosition)函数,函数有三个参数。scrollBehavior() 函数在点击浏览器的“前进/后退”,或者切换导航的时候触发。
注意:这个功能只在支持 history.pushState
的浏览器中可用。
const router = new VueRouter({
mode: 'history',
scrollBehavior (to, from, savedPosition) {
if (savedPosition) { //如果savedPosition存在,滚动条会自动跳到记录的值的地方
return savedPosition
} else{
return { x: 0, y: 0}//savedPosition也是一个记录x轴和y轴位置的对象
}
},
routes: [...]
})
我们还可以设hash来控制滚动行为,定位到某一位置(模拟『滚动到锚点』的行为):
let router = new VueRouter({
mode:'history',//默认是hash模式
linkActiveClass:'menvscode-active',
scrollBehavior(to,from,savePosition){ // 在点击浏览器的“前进/后退”,或者切换导航的时候触发。
console.log(to) // to:要进入的目标路由对象,到哪里去
console.log(from) // from:离开的路由对象,哪里来
console.log(savePosition) // savePosition:会记录滚动条的坐标,点击前进/后退的时候记录值{x:?,y:?}
/*if(savePosition) {
return savePosition;
}else{
return {x:0,y:0}
}*/
if(to.hash){ //先判断目标路由有没有hash值
return {selector:to.hash}
}
},
routes:[]
})
export default router
3.路由跳转后setInterval继续运行并没有及时进行销毁
问题描述:(myShop移动端)比如倒计时,这类需要定时调用的,路由跳转之后,因为组件已经销毁了,但是setInterval还没有销毁,还在继续后台调用,控制台会不断报错,如果运算量大的话,无法及时清除,会导致严重的页面卡顿。
解决:在组件生命周期beforeDestroy停止setInterval
beforeDestory() {
clearInterval(this.timer);
MessageBox.close()
}
4.切换路由的时候取消上一页所有请求
问题描述:在真实项目中,当路由已经跳转,而上一页的请求还在pending状态,如果数据量小还好,数据量大时,跳到新页面,旧的请求依旧没有停止,这将会十分损耗性能,这时我们应该先取消掉之前还没有获得相应的请求,再跳转页面。
解决:axios给我们提供了一个方法:cancelToken
让我们来看看cancelToken的使用方法:
//官网方法一:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else { // 处理错误
}
}); // 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
如果我要跳转页面的话,我调用source.cance()方法就可以干掉之前这个没有请求完的请求了。
但是这个方法有个弊端,就是比较麻烦,每次都要手动去调用source.cance()方法。怎么做到全局统一管理呢?
官网给了以下方法:
还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) { // executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
}); // 取消请求cancel();
根据这个方法:我们一步一步来实现它:
在main.js里写一个全局httpRequestList的空数组,用来装我们的cancel函数:
// main.js
Vue.$httpRequestList = []
再在我们封装好的post,get请求里面,将每一个请求里面都做一个将cancel函数推入的httpRequestList数组的动作:
POST(url, data, errMsg) {
const CancelToken = axios.CancelToken
return axios.post(url, data, {
timeout: 30000,
cancelToken: new CancelToken(function executor(c) {
Vue.$httpRequestList.push(c)
})
}).then(checkStatus).then(res => checkCode(res, errMsg))
},
GET(url, params, errMsg) {
const CancelToken = axios.CancelToken
return axios.get(url, {
params: {
_t: +(new Date()),
...params
},
timeout: 30000,
cancelToken: new CancelToken(function executor(c) {
Vue.$httpRequestList.push(c)
})
}).then(checkStatus).then(res => checkCode(res, errMsg))
}
这样我们的每一个请求里面,都包含了一个cancelToken对象。
在这之后我们要写一个执行cancel方法的 方法:
import Vue from 'vue'
export const clearHttpRequestingList = () => {
if (Vue.$httpRequestList.length > 0) {
Vue.$httpRequestList.forEach((item) => {
item()
})
Vue.$httpRequestList = []
}
}
tem就是之前每一个请求装进httpRequestList数组的cancel方法,item()执行后,如果该请求是pending状态,那么可以直接取消掉。执行完后记得清空httpRequestList数组。
最后我们回到main.js
在每次跳转之前执行clearHttpRequestingList()函数。
router.beforeEach((to, from, next) => {
clearHttpRequestingList()
..........这下面是你的路由验证代码..........
})
这样就实现了每次路由跳转之前,就清空之前出于pending状态的请求,优化了性能。
5.解决 4 问题的方法的另外一种
问题描述:如果上面的方法有用就不用看这里了,但如果像我一样,把向后台请求数据的request方法封装起来,并且把业务逻辑封装成类继承了有request方法的base类,那么我们可以
1)建一个vue-bus.js,
const VueBus = {
$httpRequestList: [],
clearHttpRequestingList: () => {
if (VueBus.$httpRequestList.length > 0) {
VueBus.$httpRequestList.forEach((item) => {
item();
})
VueBus.$httpRequestList = []
}
}
}
export default VueBus;
2)然后在base.js中request方法中将每个请求都存储到VueBus的$httpRequestList数组中
class Base {
constructor() {
this.baseRestUrl = Config.restUrl;
}
request(params, noRefetch) {
const CancelToken = axios.CancelToken
var that = this;
var url = this.baseRestUrl + params.url;
if (!params.type) {
params.type = 'get';
}
return axios({
url: url,
data: params.data,
header: {
'content-type': 'application/json',
},
method: params.type,
// 在这里加上每个请求都存储到VueBus的$httpRequestList数组中
timeout: 30000,
cancelToken: new CancelToken(function executor(c) {
VueBus.$httpRequestList.push(c);
})
}).then(res => {
var code = res.status.toString();
var startChar = code.charAt(0);
if (startChar == '2') {
params.sCallback && params.sCallback(res.data);
} else {
that._processError(res);
params.eCallback && params.eCallback(res.data);
}
}).catch(err => {
that._processError(err);
})
}
3)最后在main.js中添加一个全局前置导航钩子,在进入另外一个页面前执行VueBus.clearHttpRequestingList();方法,先将上一页的请求都撤销掉
router.beforeEach((to, from, next) => {
VueBus.clearHttpRequestingList();
.....
next();
})
6.跨域
问题描述:vue项目中:localhost:8010,但我们需要的数据是我们自己搭建的服务器node项目中:localhost:8011 提供给我们的。因为浏览器的“同源政策”,我们不能直接进行跨域访问。
解决:解决方案也有很多。CORS,jsonp,...。而在我们的项目中,webpack-dev-server 给我们提供的proxy模块,可以很快解决这个问题。配置如下:
** config/index.js **
dev: {
env: require('./dev.env'),
port: 8525,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:8011
changeOrigin: true
}
},
7.页面跳转样式错位但是刷新又好了的情况
问题描述:同样的页面,由别的页面跳转过来,样式错位,但是刷新样式又好了
原因:要是同样的样式,前面已经加载过了,浏览器在跳到下个页面相同的样式就不会再加载了。在起名的时候可以自行区分,也可以根据官网的提示
解决办法:在page的style中加上scoped,这个可选 scoped 属性会自动添加一个唯一的属性 (比如 data-v-21e5b78) 为组件内 CSS 指定作用域(只在该组件内有效),也就是给你定义的名字前加一串哈希值,编译的时候 .list-container:hover 会被编译成类似 .list-container[data-v-21e5b78]:hover。
如下:
<style scoped>
.note{
position: absolute;
width:400px;
height:400px;
margin-left:20%;
}
</style>
8.VUE如何实现切换页面时的过渡动画?
问题描述:在切换页面时根据页面的层级判断做前进还是后退动画
解决:这个问题的难点就是如何来确定路由是前进还是后退的问题,下面是解决的方案
1)我们需要给各个页面定义层级,在切换路由时判断用户是进入哪一层页面,如果用户进入更高层级那么做前进动画,如果用户退到低层级那么做后退动画.
router/index.js
import VueRouter from 'vue-router'
import Home from '../components/home/home'
import User from '../components/user/user'
var router = new VueRouter({
routes: [{
name: 'test',
path: '/',
meta: {
index: 0
}, // meta对象的index用来定义当前路由的层级,由小到大,由低到高
component: {
template: '<div>test</div>'
}
},
{
name: 'home',
path: '/home',
meta: {
index: 1
},
component: Home
},
{
name: 'user',
path: '/user/:id',
meta: {
index: 2
},
component: User
}
]
})
2)监控路由跳转,判断切换页面之间的层级关系,并以此来判断路由前进或者后退.
3)编写slide-left 和 slide-right 类的动画
<template>
<div id="app">
<transition :name="transitionName">
<router-view></router-view>
</transition>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
transitionName: ""
};
},
watch: {
// 使用watch 监听$router的变化
$route(to, from) {
// 如果to索引大于from索引,判断为前进状态,反之则为后退状态
if (to.meta.index > from.meta.index) {
// 设置动画名称
this.transitionName = "slide-left";
} else {
this.transitionName = "slide-right";
}
}
}
};
</script>
<style>
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
will-change: transform;
transition: all 500ms;
position: absolute;
}
.slide-right-enter {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
.slide-right-leave-active {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
.slide-left-enter {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
.slide-left-leave-active {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
</style>
持续更新中~
更多推荐
所有评论(0)