vue 知识点整理

854 阅读12分钟

跨域常见处理

一、CORS(跨域资源共享),后台需要在响应头中设置 Access-Control-Allow-Origin 可接受的域和是否允许携带 cookie,如果后台需要使用 cookie 验证,前端 axios 的withCredentials 的值需要设置为 true,默认为 false,如果后台不允许携带 cookie,withCredentials 为 true 会报跨域问题

二、nginx 反向代理,利用 nginx 反向代理则是在同一域请求,则不会出现跨域问题,后台需要在 nginx 进行额外配置,vue 在开发环境测试时配置 proxyTable 可以实现代理解决跨域

三、JSONP,以前 jquery 等解决跨域方案,利用 script 标签(script 的 src 不受同源策略约束来跨域获取数据),添加回调函数 jsonpCallback:"jsonp_handler(回调函数名称)"信息返回给后台,后台通过这个函数把数据信息返回,不过只能发起 get 请求,返回的数据有时因为不是严格的 json 格式,无法使用 JSON.parse 转换,就只能使用 eval("(" + res.data + ")") 来转换

axios

axios 的 post 和 put 请求需对数据 qs.stringify 将对象序列化,否则后台接收数据为空,由于 axios 默认发送数据时,数据格式是 application/json,而并非我们常用的 x-www-form-urlencoded 表单格式,后端未必能正常获取到,所以在发送之前,如果后台接收格式是 x-www-form-urlencoded 则需要使用qs模块对其进行处理,可以在 axios 请求拦截统一处理:

// 请求拦截器
axios.interceptors.request.use( config => {
    if((config.method === 'post' ||  config.method === 'put') && 
    config.headers['Content-Type'] == "application/x-www-form-urlencoded;charset=UTF-8"){
        config.data=qs.stringify(config.data);    
    }      
    if(sessionStorage.getItem('token')){      
        config.headers['Authorization'] = sessionStorage.getItem('token')   
    }      
    return config;    
    },    
    error => {      // 对请求错误做些什么     
     return Promise.reject(error);    
    }
)

axios 使用 application/x-www-form-urlencoded 格式传递数组,qs 对数组进行字符串序列化默认格式如下:
qs.stringify({ a: ['b', 'c', 'd'] });// 'a[0]=b&a[1]=c&a[2]=d'
qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });// 'a=b&a=c&a=d'
qs.stringify({ id: ['b', 'c'] }, { arrayFormat: 'indices' })// 'id[0]=b&id[1]=c'
qs.stringify({ id: ['b', 'c'] }, { arrayFormat: 'brackets' })// 'id[]=b&id[]=c'
qs.stringify({ id: ['b', 'c'] }, { arrayFormat: 'repeat' })// 'id=b&id=c'

axios 传递字段数据时,当数据为 null 时不会传递该字段

axios的onUploadProgress上传文件事件会在浏览器接收新数据期间周期性地触发,我们就可以为用户创建一个进度条或者加载器:

uploadFile (param,callback){  
    return axios.post('/file/upload',param,{    
        headers : {        
            "Content-Type": "multipart/form-data;charset=UTF-8"    
        },  
        onUploadProgress: progressEvent => {      
            callback(progressEvent);    
        }  
    })
}


scss 使用

先安装有关 scss 的包 npm install sass-loader node-sass sass-resources-loader --save-dev,然后在 build->utils 中设置 scss 的默认导入文件( vue 修改了配置文件需要重启服务):

scss: generateLoaders('sass').concat(  {    
    loader: 'sass-resources-loader',    
    options: {      resources: path.resolve(__dirname, '../src/assets/css/base.scss')    }  
})

也可以在 app.vue 直接导入,安装 node-sass 依赖时,会从 github.com 上下载 .node 文件。由于国内网络环境的问题,这个下载时间可能会很长,甚至导致超时失败,推荐使用淘宝镜像源 set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ && npm install node-sass下载

scss 的深度作用选择器 /deep/ 可以在组件样式的设置、覆盖(自己定义的组件和第三方的组件如 elementui 等)和 v-html 等环境中使用

vue 中 js 调用 scss 的变量,在 scss 文件中用 :export 导出你要使用的变量对象,然后在你要导入的文件中引入这个文件

图片路径

图片在 assets 文件夹,需要使用 require()进行引入,图片在static文件夹,可以直接使用绝对路径,因为使用 assets 下面的资源,在js中使用的话,路径要经过 webpack 中的 file-loader 编译,路径不能直接写,而 static 文件夹中是不会被 wabpack 处理的,它们会被直接复制到最终的打包目录下面(默认是 dist/static ),且必须使用绝对路径来引用这些文件

Promise

Promise 传递异步操作的消息 ,可以将异步操作队列化,按照期望的顺序执行:

function asyncFunction() {  
    return new Promise(function (resolve, reject) {    
        setTimeout(function () {      
            resolve('Async Hello world');    
        }, 1000)  
    })}
asyncFunction().then(function (data) {  
    console.log(data)  //通过返回的data数据进行下一步操作
}).catch(function (error) {  console.log(error);})

new Promise(() => {}).then() , new Promise() 这一部分是一个构造函数,这是一个同步任务,后面的 .then() 是一个异步微任务即先 new 一个实例,再调用方法

当遇到发送多个请求并根据请求顺序获取和使用数据的场景,可以使用 Promise.all,例如一个 Promise 失败会影响所有 Promise 回调执行以及 promise.all 会有一些阻塞的情况,即一个失败后续的 .then 都会停止,几个异步操作是强相关的,后续步骤必须依赖这几个步骤全部成功才能进行

async/await 本质上还是基于 Promise 的一些封装,await 等待的是一个表达式,这个表达式的返回值可以是一个 promise 对象也可以是其他值,await 会暂停当前 async 函数的执行,等待后面的 Promise 的计算结果返回以后再继续执行当前的 async 函数,await 等待的不是所有的异步操作,等待的只是 Promise,你如果没有给他返回个 Promise,那它还是会继续向下执行,达不到想象的结果,async/await 就是要完善 Promise 还不够完美的地方,如:

async function testAsync() {  
    return new Promise(function (resolve, reject) { 
       // 异步操作    
    setTimeout(()=>{      
        resolve('hello')    
    }, 1000)  })}
async function test() {  
    const hello = await testAsync()  
    const world = 'world'  //1s之后hello world  
    console.log(hello)  
    console.log(world)
}
test()

vue 打包配置

样式和路径不显示:

一、build->utils.js,fallback: 'vue-style-loader' 后添加 publicPath: '../../'

二、config->index.js->build 中的 assetsPublicPath: '/' 修改为 assetsPublicPath: './',如果使用 history 模式就直接 assetsPublicPath: '/

productionSourceMap 显示:config->index.js->build 中的 productionSourceMap 设置为 false,不然在 source 的 webpack 中可以看到未压缩的源代码,并且增加了打包之后的体积

去掉 console.log:build->webpack.prod.conf.js中:

new UglifyJsPlugin({  uglifyOptions: {    compress: {
      warnings: false,      
      drop_debugger: true,      
      drop_console: true    }  }
,  sourceMap: config.build.productionSourceMap,  parallel: true})


uuid 唯一标识生成

function uuid{  
    return 'xxxxxx4xxxyxxxxxx'.replace(/[xy]/g, function (c) {    
        let r = Math.random() * 16 | 0,      v = c == 'x' ? r : (r & 0x3 | 0x8)    
    return v.toString(16)  })
}

echarts

echarts tab 切换 出现宽度变成100px 问题,而设置的宽是百分比,原因是初始创建 echarts 实例时获取不到隐藏tab的宽,所以建议在切换 tab 时在创建 echarts 实例,如果 echarts 是固定的宽,不需要响应,也可以设置具体的值而不是百分比

echarts 移动端字体模糊,通过 dpr 来设置不同大小或直接设置100%

echarts 双纵轴需要设置 min 和 max 才会显示右边纵轴,不然会默认重合

echarts 图表数据切换会出现不明竖线,,可先清空 clear 再重新绘制或者 setOption 设置 true,即不跟之前设置的 option 进行合并

echarts 使用地图需要注册地图文件,注册中国地图方式:

import chinaJson from 'echarts/map/json/china.json'
echarts.registerMap('china', chinaJson)

数组去重

单元素数组去重:

let arr = [1,3,3,5,4,85,4,2,2,1]
let newArr = [...new Set(arr)]
console.log(newArr) //[1, 3, 5, 4, 85, 2]

对象数组去重:

//reduce去重
let person = [  
    {id: 0, name: "小明"},  {id: 1, name: "小张"},  
    {id: 2, name: "小李"},  {id: 3, name: "小孙"},  
    {id: 1, name: "小张"},  {id: 2, name: "小李"}   
]
let obj = {}
let peon = person.reduce((cur,next) => {   
    obj[next.id] ? "" : obj[next.id] = true && cur.push(next);   return cur},[]) 
//设置cur默认类型为数组,并且初始值为空的数组console.log(peon)

//filter去重
let person = [  
    {id: 0, name: "小明"},  
    {id: 1, name: "小张"},  
    {id: 2, name: "小李"},  
    {id: 3, name: "小孙"},  
    {id: 1, name: "小张"},  
    {id: 2, name: "小李"}   
]
let peon = person.filter((x, index)=>{ 
    let arrids = [] 
    person.forEach((item,i) => {   
        arrids.push(item.id) 
    }) 
    return arrids.indexOf(x.id) === index
}) 
console.log(peon)

嵌套路由 path

嵌套路由子路由的 path 添加/会默认从根目录开始匹配,没有添加则会加上父组件path

路由组件复用

beforeRouteUpdate 路由钩子使用于当前路由改变,但是该组件被复用时调用,如动态路由等,当路由种嵌套子路由时,在父路由里面添加此回调,当子路由发生任何变化时,都会触发该回调或使用 watch 监听路由

data 数据之间的调用

vue 中 data 数据之间的调用,直接在 data 中赋值是无法获取,需要在 mounted() 里赋值才可以取到或使用 computer

vue data render 函数写成箭头函数 this 会指向 vue 实例,不然会无法获取实例的数据和方法

computed 与 watch

computed 计算属性会依赖于使用它的data属性,只要是依赖的 data 属性值有变动,则自定义重新调用计算属性执行一次,watch 则是在监控的 data 属性值发生变动时,其会自动调用watch 回调函数

computed 直接从缓存中获取,而 watch 需要重新编译,性能偏低一点

computed 计算属性可以直接使用,不需要在 data 数据中定义,而 watch 必须是 data 数据域中所存在的,否则无法使用(watch 的 handler 方法,immediate 属性和 deep 属性一般监听对象的某个属性)

Computed property "isReceive" was assigned to but it has no setter:一个计算属性,如果没有设置 setter 或者 getter,当你尝试直接修改这个计算属性的值时,都会报这个错误,添加上 get、set 就可以了

导入和导出

CommonJS 的规范:exports 导出的东西需要 require 引入,如 module.exports 和 exports,输出是一个值的拷贝,运行时加载

ES6规范:export 导出的东西需要 import 引入,如 export default 和 export,输出是一个值的引用,编译时输出接口

provide / inject

在父组件中通过 provider 来提供变量,然后在子组件中通过 inject 来注入变量 ,不论子组件有多深,只要调用了 inject 那么就可以注入 provider 中的数据 ,provide 和 inject 绑定并不是可响应的,然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的

组件 props

props 默认值的类型为数组或者对象,一定要在函数中返回这个默认值,而不是直接赋默认值

props: {  
    columns: {    
        type: Array,    
        default() {      
            return []    
        }  
    },  
    parameter: {    
        type: Object,    
        default() {      
            return {}    
        }  
    }
}

iview

iview 组件 tab 切换存在 table 时,如果只有部分列设置了宽度,在不是当前tab改变浏览器窗口时会出现没有设置宽度的列 width 变成0,可以给没设置的列设置个 minWidth 属性

elementui

elementui 时间范围选择清空时,值并不是空数组而是null,所以在数据列表通过时间查找时不单单判断是否为空数组,还要判断是否为null

elementui 组件添加自定义参数,如 element-ui 的 change 事件默认参数是选中的 value 值 ,但是实际项目中我们要传的一般不止这一个参数,如果直接追加两个参数可能获取不到,这时需要把参数中的默认参数名换成 $event 即可,@change='selectChange($event, extraValue)' 或 @change='(val)=>selectChange(value, extraValue )'

elementui table render 中无法使用 v-model,只能通过 value 来切换变化

elementui 使用 :before-upload 时会触发 :on-remove,所以需通过 :on-remove 中 file 的 file.status === 'success' 来判断

使用 el-image 赋值 src,在 created 中赋值或者给 el-image,v-if 判断当 url 存在时在显示,不然会出现直接渲染图片错误插槽,因为 e-image 的 src 路径一旦初始化加载失败的话就没有异步刷新,created 可以给 data 赋值,但是无法获取 dom 节点

el-tree 的 default-checked-keys 默认勾选只会赋值响应一次,所以使用 setCheckedKeys 方法来每次设置所勾选,但当弹框没有渲染的时候,由于 tree 控件 dom 没有加载,setCheckedKeys 是不存在的,会报错,所以我们需要使用 this.$nextTick(callback) 方法,该方法会在 dom 加载完毕之后,执行回调函数

el-dialog modal-append-to-body 控制遮罩是否在 body 中,对控制显示不同层级 z-index 有可能用到
el-tooltip 的文字信息必须嵌套在标签中在放入 el-tooltip 中

el-table 使用 reserve-selection 和 row-key 来实现记住勾选框问题,当分页点击全选时,当在点击其它页时数据存在但勾选框没勾选,如判断勾选全选每次分页查询手动赋值会出现数据重复添加问题,因为每次点击分页时使用 toggleRowSelection 会触发 selectchang 事件

el-table 分页时,通过 tag 标签调用 toggleRowSelection 方法关闭已勾选项,反复点击分页再关闭时会出现失效问题

el-form resetFields() 是重置表单到初始值,不是清空表单,所以如果在 modal 弹框中先打开编辑表单在打开新增表单数据不会清空,当表单第一次在页面中渲染时所用的数据就是初始数据,如果修改对象的表单赋值没有放在 nextTick 中,就会在表单渲染时就会将这个修改对象作为初始值,可以使用 $nextTick:

//弹框打开
this.dialogBox.boxShow = true;
this.$nextTick(() => {  
    // 初始化再赋值  
    this.$refs['projectRef'].resetFields();  
    this.projectForm.formModel = {    
        name: row.name,    
        linkUrl: row.linkUrl,    
        startTime: row.startTime,    
        endTime: row.endTime,    
        status: row.status,    
        description: row.description  }
    }
)


主题切换

一、通过正则匹配和替换,将基本颜色变量替换成你需要的,之后动态添加 style 标签来覆盖原有的 css 样式
优点
无需准备多套主题,可以自由动态换肤
缺点
自定义不够,只支持基础颜色的切换

二、本地存放多套主题,两者有不同的命名空间,我们动态的在 body 上 添加 class
过程:
a、安装主题工具 element-theme 和 element-theme-chalk ,et -i 初始化变量生成 element-variables.scss,et 修改编译 element-variables.scss 文件生成 theme 文件夹
b、通过 gulp-css-wrap 批量给 css 文件的所有选择器添加命名空间
c、引入所有主题 css 文件,通过不同 class 来显示不同主题,主题过多可按需引入,动态的加载 css element-admin 通过给 body 设置 class 的 overflow ,hidden 来隐藏滚动条,主题添加 class 应该添加到 htm l中而不是 body 中,不然会出现 body 同级的 class 样式无法应用

三、直接通过 scss @mixin 来覆盖和设置不同的样式,如设置亮暗2个背景色:

@mixin bg-color($lightColor: transparent, $darkColor: transparent){  
    background-color: $lightColor;  
    [data-theme='custom-light'] & {    
        background-color: $lightColor;  
    }  
    [data-theme='custom-dark'] & {     
        background-color: $darkColor;  
    }
}


图片懒加载

v-lazy图片懒加载 <img v-lazy="图片地址" :key="图片地址">,需要添加 :key,不然会出现内容更新图片不更新,图片会存在缓存情况

vue修改对象数组属性,数据变化视图不变化

因为修改对象或数组嵌套层次比较深,修改时不能检测到数据的变动,从而没有响应式,可以通过 Vue.set(this.data, index, this.data[index]) 或 this.$set(this.data, index, this.data[index]) 来手动触发更新,如:对象- this.$set(this.student,"age", 24),数组- this.$set(this.Arr, index, this.Arr[index])

使用 this.$forceUpdate() 强制更新,视图和数据重新渲染,它仅仅影响实例本身和插入插槽内容的子组件

预渲染 prerender-spa-plugin

prerender-spa-plugin 打包生成静态页的时候我们发现在 console 控制台出现会出现 webpackJsonp is not defined 错误提示,这是因为 js 文件加载顺序导致,可以在 build --> webpack.prod.conf.js 的配置中引入 chunks: ['manifest', 'vendor', 'app'](还是出现错误)或在所有 index.html 中把 async 异步的属性我们把他换成了 defer,使用预渲染 prerender-spa-plugin 模式必须是 history 并且 config 的 assetsPublicPath 为'/'而不是'./'

在安装 prerender-spa-plugin 时 会发现 进度一直停在 build puppeteer 上,那是因为 puppeteer 默认的源 下载太慢了,我们可以使用淘宝源安装 npm config set puppeteer_download_host=https://npm.taobao.org/mirrors npm i puppeteer或$ npx cross-env PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors/ npm install apify --save

用户体验

一、表单提交可以绑定 enter 事件来提高用户体验

二、骨架屏注入,代替部分页面的

浅拷贝和深拷贝

浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用 ,方式:Object.assign() 方法,解构赋值,直接用=赋值和 for in,如:

let obj1 = {a: 1, b: 2}
let obj2 = Object.assign({}, obj1)
obj2.a = 4
console.log(obj1)  //{a: 1, b: 2}
console.log(obj2)  //{a: 4, b: 2}
let obj3 = {a: 1, b: {c: 3}}
let obj4 = Object.assign({}, obj3)
obj4.b = {c: 99}  //这里因为更换了b对象的地址,所以obj3没有跟着变化
console.log(obj3)  //{a: 1, b: {c: 3}}
console.log(obj4)  //{a: 1, b: {c: 99}}
let obj5 = {a: 1, b: {c: 3}}
let obj6 = Object.assign({}, obj5)
obj6.b.c = 99    //这里因为还是使用b对象的指向地址,所以obj5也跟着变化
console.log(obj5)  //{a: 1, b: {c: 99}}
console.log(obj6)  //{a: 1, b: {c: 99}}

深拷贝方式,JSON.parse(JSON.stringify(obj)) 和递归拷贝,JSON.parse(JSON.stringify(obj)) 这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构,如对象中值为 undefined 或者 function 的时候会丢失,递归拷贝实现:

// 数组对象递归深拷贝
function deepCopy(arr) {  
    let copyArr = (arr.constructor === Array) ? [] : {} // 判断是数组还是对象  
    for(let i in arr) {      
        if(typeof arr[i] === 'object') {   
            // 判断是值类型还是引用类型          
            copyArr[i] = deepCopy(arr[i])  
            // 引用类型的话进行递归操作      
        } else {          
            copyArr[i] = arr[i]  
            // 值类型直接赋值      
        }  
    }  
    return copyArr
}