前言
本篇主要介绍模块中的常见的报错,希望列举不正确的以及不完善的大家帮忙提出。
npm run dev报错找不到localhost:
报错 如下 :原因是因为localhost解析不到对应的ip
Error: getaddrinfo ENOTFOUND localhost
at errnoException (dns.js:50:10)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:92:26)
解决方式 :找到根目录 /etc/hosts,它是计算机进行域名解析服务的一个本地映射,写入如下的解析,就可以完美的解决这个问题。127.0.0.1 localhost
提示:如果你需要定制很多host,可以下载一个switchHosts的软件,可以切换不同测试环境下添加不同的域名解析机制。如果你是shell面板,可以cd 切换到/etc,然后mac利用 vi或者win利用vm修改这个文本文件(需要管理员权限进行修改)。
关于异步获取数据时渲染报错
问题场景:一般情况下,页面需要用的数据项是data中会定义一份,然后大部分数据肯定是接口异步获取的,在组件模板编码中,一般都习惯于直接基于已有数据正确的情况下去实现,在初始数据为空渲染空对象属性的属性下就会报错(直接空属性不会报错哦)。
Vue.component("row",{
template:"<li>{{item.account.name}}</li>",
props:{
item:{
type:Object,
default:[],
required:true
}
},
})
// 父组件中初始状态
<row :item="item"></row>
data:{
item :{}
},
created(){
// 模拟异步接口
setTimeout(()=>{
this.dataReady = true;
this.item =
{ account:{
name:8576
}}
},2000)
}
// vue warn 报错
VM1873 console_runner-ce3034e6bde3912cc25f83cccb7caa2b0f976196f2f2d52303a462c826d54a73.js:1 [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
found in
---> <Row>
解决方案 :这样的报错主要是因为数据在渲染时没有保证其是在数据准备好的情况下,所以需要在组件渲染的做一些必要的准备工作。比如我们可以加下数据加载好的标志位。dataReady:false
<li is="row" :item="item" v-if="dataReady"></li>
// 父组件中
data:{
item:{},
dataReady:true,
}
created(){
// 模拟异步接口
setTimeout(()=>{
this.dataReady = true;
this.item =
{ account:{
name:8576
}}
},2000)
}
- 拓展认知1:如果你的数据是Array类型,也许不会有这样问题,因为Array类型默认用的[],所以没有内容不会渲染,内容有的时候才会进行渲染并进行循环,不过为了安全,element等框架还是进行了安全校验,v-if=”data&&data.length>0”,数据是对象的,加v-if=”item” 以此来保证数据是有的情况。
- 拓展认识2 :即使是数据在正常得到的情况下,如果你的属性值也可能是空的,那么需要你在可能有属性报错或者没有的情况下,追加属性存在的判断,比如:
if(item.account&&item.account.name){//codes here}
- codepen案例地址:dataRenderError
watch使用特性:更好的支持异步,可以支持深度监测
问题场景:这点主要是讲watch以及computed的区别,在watch使用场景中,我们可以针对某属性的变化,进行一些额外的异步或者同步逻辑或者事件,而计算属性本身只关注其最后的结果。而且计算属性时建立在watch基础上的,在进行计算的时候实际等同于对每个可影响的属性增加了watch监听。
那么具体的问题就是指当我们改变对象的非第一层属性或者值时,虽然值改变了,但是并未触发其watch方法监听。如此,我们就需要修改为第三种写法。
三种基本写法 :
watch: {
//第一种写法 适用于普通变量(简单类型的值的观测写法)
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 第二种写法:方法名
b: 'someMethod',
// 第三种写法:深度 watcher(能观测对象c下多重属性变化)(复杂类型的值的观测写法)
c: {
//当c变化后会回调handler函数
handler: function (val, oldVal) { /* ... */ },
deep: true
}
}
解决方案 :使用deep,并且改变数值的方式要使用this.$set(this.obj,key,value)
data:{
pserson:''
},
watch:{
person:{
handler(){
console.log(111)
},
deep:true,
immediate:true
}
},
created(){
this.tip='变更数据'
setTimeout(()=>{
this.name = this.name2='zhang san'
this.tip='变化数据完成'
// this.$set(this.person,'tip',45);
this.person= Object.assign({},this.person,{tip:45});
this.person.account.name=['li定位si'];
},2000)
}
- vueWatchDemo—codepen地址
- 拓展:Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。不能用箭头函数,因为箭头函数会绑定父级的作用域,导致功能失败。
- 如果希望是第一次data的时候就监听,需要增加immediate:true (如果你是在created生命周期里就加了执行事件,是根据某监听属性来的,那么可以改到这个位置)
- 如果是新增属性,原来对象中没有的,需要用到前面的知识,用this.$set(),或者Object.assiagn()实现。如果是原来就有这个属性,直接用赋值方式就可以。
- 不要混淆watch监听与对数据的改变,直接赋值是任何时候都可以改变值的,只不过没触发监听事件
- watch默认就会返回其最新的val,不需要人为多一行代码去处理,但是如果你想返回其他值就需要加中间逻辑,并显性的返回需要的值。
Vue路由参数变化不重载问题(watch)
vue通过ID(参数)修改URL复用同一个页面(组件)不重新加载的问题
项目中经常会用到同一个页面,结构是相同的,我只是在vue-router中通过添加参数的方式来区分状态,参数可以在页面跳转时带上params,或者query,但是有一个问题,即使我们修改了参数,URL也显示已经改变,但页面并不会刷新,因为路由是相同的,vue就会认为你是同一个页面,从而复用已加载的页面,而不会重新加载,所以如果在created钩子中来区分状态明显是行不通的,可以通过watch监听事件来监听路由的变化:
watch: {
'$route' (to, from) {
if (to.name === 'projectAdd') {
console.log(to.query) // 在此调用函数
}
}
}
通过watch监听即可实现,这里顺带说一下params和query的区别:
相同:
使用方法相同,都是在路由跳转的时候带过去:
manageProject (toseId) {
this.$router.push({
name: 'projectAdd',
query: {toseId: toseId} // params: {toseId: toseId}
})
}
不同:
params需要在路由设置index.js中添加参数(
path: ‘/projectAdd/:id’),而query不需要;
跳转后在URL的显示不同,params显示的是(http://localhost:8082/#/projectAdd/6),query显示的是(http://localhost:8082/#/projectAdd?toseId=6);
接收方式不同,params为this.$route.params,query为this.$route.query
移动端使用rem
一般情况下可能会自己做适配,也可以使用阿里的模块。amfe-flexible
//main.js 引入依赖
import 'amfe-flexible'
//_base.scss 设计图宽度除以10,假如设计图宽度是750px那么,基础宽度就是75
$baseWidthSize: 75 !default;
@function to($px) {
@return $px / $baseWidthSize * 1rem;
}
//组件和页面使用; to()里面的数值是photoshop里测量的值
<style lang="scss">
@import "../scss/_base.scss";
.box{
width: to(750);
height: to(100);
}
</style>
移动端使用vw布局
使用语言包 i18n
场景 :希望在项目中支持国际化 。
解决方案 :详细的见文章,描述较为详细。其中语言包可以根据路由的需要决定是否懒加载。
watch 6位数字
场景 :我们一般都需要对数字做验证,在没有其他外界环境的情况下,一般我们是通过正则进行验证的,但是现在产品对需求交互进行了细化,要求如下:在没有数字键盘,选择控件的情况下,还是比较麻烦的。
1 不能输入非数字,也就是只能输入数字
2 不能输入超过6位
解决方案 :主要通过正则以及Number的相关方法实现。
number(newval){
let reg = /^[0-9]+$/ ;
let val = parseInt(newval) ;
if(reg.test(newval)){
this.number = newval.toString().substring(0,6);
}else if(isNaN(val)){
this.number = ''
}else{
this.number = val
}
}
兼容问题
在进行项目开发的时候,在ie9或者其他浏览器可能会遇到兼容问题,那么需要看下以下的几种可能,es6不支持 ,promise不支持 ,babel的相关设置不对 。
es6 不支持:
npm install babel-polyfill --save-dev
entry: {
'babel-polyfill': 'babel-polyfill',
app: './src/main.js'
},
.babelrc 文件的设置
"presets": [
"es2015",
"stage-2"
],
"plugins": ["transform-runtime"],
promise不支持 :
import promise from 'es6-promise';
promise.polyfill();
使用eventBus跨页面传参错误
场景:假设我们没有使用vuex,而需要在a页面跳转到b页面的时候进行大量的参数传递,可以使用eventBus事件进行,于是在a页面mounted 周期里提交事件,在b页面mounted周期接收事件。于是发现两个bug :
- 第一次出发并没有执行接收
- 后续的触发都会积累之前的
对于第一点是因为生命周期的问题,在进行页面跳转的时候,会先进行a页面的beforeDestroy周期,然后进行b页面的creadted,,在beforeMount之后才唤醒a页面的beforeDestroy,destroyed的周期,然后把提交事件放到beforeDestroy周期就可以了。
对于第二点是因为全局的eventBus定义之后,没有在destroyed周期里销毁对应的事件,所以针对这种情况需要.$off销毁对应的事件。那么在销毁的时候需要拿到其当前页面的路径,this.$route.path作为参数传递,this.bus.$off(this.$route.path).
页面刷新每次都要执行一个方法或者代码
场景:如上描述 ,如果这段代码写在常规的mounted的周期里不会每次刷新页面都执行,也有试过去执行某些方法无效。
解决方案 :
this.$nextTick(function(){
//codes here
})
备注:关于this.$nextTick后续会深入研究源码以及其解决的实际问题。
按需加载组件库
一般按需加载都是为了节省带宽,避免网络资源浪费,提高页面性能。下面以vant为例:
- 安装: cnpm i vant -S
- 安装babel-plugin-import插件使其按需加载: cnpm i babel-plugin-import -D
- 在 .babelrc文件中中添加插件配置 :
libraryDirectory {
"plugins": [
// 这里是原来的代码部分
// …………
// 这里是要我们配置的代码
["import",
{
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}
]
]
}
- 在main.js中按需加载你需要的插件:
// 按需引入vant组件
import {
DatetimePicker,
Button,
List
} from 'vant';
- 使用组件:
// 使用vant组件
Vue.use(DatetimePicker)
.use(Button)
.use(List);
- 页面中使用
<van-button type="primary">按钮</van-button>
备注 :出来vant库外,像antiUi、elementUi等,很多ui库都支持按需加载,可以去看文档,上面都会有提到。基本都是通过安装babel-plugin-import插件来支持按需加载的,使用方式与vant的如出一辙,可以去用一下。
优雅的只在当前页面中修改ui库样式
很多情况下我们引入了组件,需要修改样式为我们需求的,但是因为如果加scoped的关键字,就会找不到对应的库样式,一般我们是采用非scoped的样式去覆盖,但这样会造成样式污染,而且其他人是不知情的。
所以建议可能会有一种是:
在样式公共目录下统一的修改组件样式,然后大家共用,同时与产品、设计约定好这样的规则 。
还有一种就是本文推荐的,深度选择器,我们可以在当前页面修改库样式:.van-tabs /deep/ .van-ellipsis { color: blue};
官方文档:deep选择器
轮播的技术选型
如果你只是想使用轮播的组件,这里推荐Vue-Awesome-Swiper。
定位分析大文件
在进行项目优化的时候,我们需要针对性的分析出哪些文件大,以及如何优化 。
如果你是vue-cli初始化的项目,会默认安装webpack-bundle-analyzer插件,该插件可以帮助我们查看项目的体积结构对比和项目中用到的所有依赖。也可以直观看到各个模块体积在整个项目中的占比。
npm run build —report // 直接运行,然后在浏览器打开http://127.0.0.1:8888/即可查看(先把run dev关掉)
开启gzip压缩代码
spa这种单页应用,首屏由于一次性加载所有资源,所有首屏加载速度很慢。解决这个问题非常有效的手段之一就是前后端开启gizp(其他还有缓存、路由懒加载等等)。gizp其实就是帮我们减少文件体积,能压缩到30%左右,即100k的文件gizp后大约只有30k。
vue-cli初始化的项目中,是默认有此配置的,只需要开启即可。但是需要先安装插件:cnpm i compression-webpack-plugin
然后在config/index.js中开启即可:
build: {
// 其他代码
…………
productionGzip: true, // false不开启gizp,true开启
// 其他代码
}
现在打包的时候,除了会生成之前的文件,还是生成.gz结束的gzip过后的文件。具体实现就是如果客户端支持gzip,那么后台后返回gzip后的文件,如果不支持就返回正常没有gzip的文件。
注意:这里前端进行的打包时的gzip,但是还需要后台服务器的配置。配置是比较简单的,配置几行代码就可以了,可以让运维操作下。(待完善具体的配置)
浏览器前进后退刷新数据的问题
keep-alive 不能完全解决你的问题的话,参考这里的文章实践:浏览器刷新时的数据与位置