需要掌握的Vue技巧、常用知识及问题汇总(持续更新)

开发技巧

1.插槽简写

Vue2.6推出具名插槽缩小, 跟v-on v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容(v-slot:)替换为字符 #。例如v-slot:header可以被重写为 #header

<base-layout>
  <template #header="{user}">
    <h1>{{ user.firstName }}</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

2.Prop验证

一般通过Prop传递的数据格式的常见验证方式如下:

props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    }
  }

还有自定义验证器:

props: {
    // 自定义验证函数
    propF: {
      validator: (value)=> {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }

3.动态指令参数

Vue 2.6 的最酷功能之一是可以将指令参数动态传递给组件。 假设有一个按钮组件,它的事件名是动态设置或者通过prop传递获得,那么动态指令就能实现:

<template>
  <button @[dynamicEvent]="handleSomeEvent">按钮</button>
</template>

<script>
  export default {
    name: 'demo',
    props: {
      dynamicEvent: {
        type: String,
        default: 'click'
      }
    },
    methods: {
      handleSomeEvent() {
        alert(this.dynamicEvent);
      }
    }
  }
</script>

4.重用同一路由组件

有这样一种情况,比如我在同一路由页面来回切换,只是参数不一样,比如路由是这样配置的:

{
  path: '/details/:id',
  component: () => import('@/views/details/index'),
  name: 'details',
  meta: {
    title: '详情'
  }
}

比如我在这个页面来回切换,只是传递的参数id不一样,但是默认情况,这样的共享组件页面是不会被重新渲染的,因为Vue出于性能考虑会重用该组件。

如果我们还是想要重新渲染该组件,那么我们可以在路由器视图组件中提供:key属性来实现重新渲染:

<template>
  <router-view :key="$route.fullPath"/>
</template>

5.$createElement

默认情况下,每个Vue实例都可以访问$createElement方法来创建和返回虚拟节点。例如,可以利用它在可以通过v-html指令传递的方法中使用标记。在函数组件中,可以将此方法作为渲染函数中的第一个参数访问。

6.使用 JSX

由于Vue CLI 3默认支持使用JSX,因此现在(如果愿意)我们可以使用JSX编写代码(例如,可以方便地编写函数组件)。 如果尚未使用Vue CLI 3,则可以使用babel-plugin-transform-vue-jsx获得JSX支持。

7.自定义 v-model

默认情况下,v-model是@input事件监听器和:value props上的语法糖。但是,我们可以在Vue组件中指定一个模型属性,以定义使用什么事件和值

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

8.$on(‘hook:’)

如果要在created或mounted方法中定义自定义事件侦听器或第三方插件,并且需要在beforeDestroy方法中将其删除以免引起任何内存泄漏,则可以使用此功能。 使用$on(‘hook:’)方法,我们可以仅使用一种生命周期方法(而不是两种)来定义/删除事件。

mounted() {
  const thirdPartyPlugin = thirdPartyPlugin();
  this.$on('hook:beforeDestroy',() => {
    thirdPartyPlugin.destroy()
  })
}

9.\(props、\)attrs和$listeners使用技巧

  • (1)$props:当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象属性的访问。
  • (2)$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。
  • (3)$listeners:包含了父作用域中(不含 .native 修饰器的)v-on事件监听器。他可以通过 v-on="listeners"传入内部组件

$props

这是一个非常酷的功能,可将所有prop从父组件传递到子组件。 如果我们有另一个组件的包装器组件,这将特别方便。 因为,我们不必一个一个将prop传递给子组件,而是一次传递所有prop:

<child-component v-bind="$props"></child-component>

原使用方式:

<child-component :prop1="prop1" :prop2="prop2" :prop3="prop3" ...></child-component>

那么child-component传递的参数通过props正常接收就行:

export default {
    name: 'child-component',
    props: {
      prop1: {
        type: String,
        default: ''
      },
      prop2: {
        type: Array,
        default: function() {
          return []
        }
      },
      prop3: {
        type: Number,
        default: 0
      }
    }
  }

$attrs

跟$props使用方式类似:

<child-component v-bind="$attrs"></child-component>

如果子组件(孙子组件)不在父组件的根目录下,则可以将所有事件侦听器从父组件传递到子组件,如下所示:

<child-component v-on="$listeners"></child-component>

参考示例:《Vue中\(props、\)attrs和$listeners的使用详解》

常见知识

使用cross-env设置node环境变量

安装:

npm install --save-dev cross-env

package.json设置:

{
  "scripts": {
    "dev1": "export WEBPACK_ENV=production && npx webpack -p",  ## mac
    "dev1": "set WEBPACK_ENV=production && npx webpack -p", ## windows
    "dev2": "cross-env CURRENT_ENV=development webpack-dev-server --inline --progress", ## 兼容所有平台
  }
}

在把当前环境的变量通过webpack内置的插件DefinePlugin,把当前这个node环境中的变量的值注入到浏览器中,如上面添加的这个变量,我们一般这样操作:

const env = {
 WEBPACK_ENV: process.env.WEBPACK_ENV,
 CURRENT_ENV: process.env.CURRENT_ENV
}
 new webpack.DefinePlugin({
     'process.env': env
})

然后我们就能在浏览器可识别的JS文件中这样访问:

process.env.WEBPACK_ENV

扩展参考:《阮一峰:npm scripts 使用指南》

package.json中的dependencies与devDependencies之间的区别

我们在使用npm install 安装模块或插件的时候,有两种命令把他们写入到 package.json 文件里面去,比如:

  • –save-dev 安装的 插件,被写入到 devDependencies 对象里面去
  • –save 安装的 插件 ,被写入到 dependencies 对象里面去

devDependencies 是只会在开发环境下依赖的模块,生产环境不会被打入包内。

dependencies 是不仅在开发环境使用,在生成环境也需要。

webpack中alias配置中的“@”的作用

如下配置代码:

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src')
    }
  }

其中的@的意思是:只是一个别名而已。这里设置别名是为了让后续引用的地方减少路径的复杂度。

//例如
src
- components
  - a.vue
- router
  - home
    - index.vue

index.vue 里,正常引用 A 组件:
import A from '../../components/a.vue'
如果设置了 alias 后。
alias: {
 'vue$': 'vue/dist/vue.esm.js',
 '@': resolve('src')
}

引用的地方路径就可以这样了
import A from '@/components/a.vue'
这里的 @ 就起到了【resolve('src')】路径的作用。

给每个路由页面设置title

路由实例:

[{
    path:'/login',
    meta: {
      title: '登录页面'
    },
    component:'login'
}]

全局前置守卫拦截代码示例:

router.beforeEach((to, from, next) => {
  if(to.meta && to.meta.title){
    window.document.title = to.meta.title;
  }
  next()
})

vue中使用axios上传图片文件

通过form表单提交,html代码如下:

<input name="file" type="file" accept="image/png,image/gif,image/jpeg" @change="update"/>

JS代码:

import axios from 'axios'

update(e) {
    let file = e.target.files[0]
    let param = new FormData(); // 创建form对象
    param.append('file', file); // 通过append向form对象添加数据
    param.append('chunk', '0'); // 添加form表单中其他数据
    let config = { // 添加请求头
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    };
    axios.post('http://172.19.26.60:8080/user/headurl', param, config)
    .then(response => {
        if (response.data.code === 200) {
            this.ImgUrl = response.data.data;
        }
    })
}

vue双向数据绑定vuex中的state

在vue中, 不允许直接绑定vuex的值到组件中, 若是直接使用, 则会报错 have no setter

方法一: 使用get和set

// 在从组件的computed中
computed: {
    user: {
        get() {
          return this.$store.state.user
        },
        set(v) {
          // 使用vuex中的mutations中定义好的方法来改变
          this.$store.commit('USER', v)
        }
    }
}
// 在组件中就可以使用
<input v-modle="user" />

方法二: 使用watch

// 在组件中绑定
<input v-modle="user" />

// 在computed中获取vuex的值
computed: {
  ...mapState( { user: state => state.user } )
}

// 在组件的watch中观测
watch: {
  'user': {
      deep: true,
      handler(value) {
        // 使用vuex中的mutations中定义好的方法来改变
          this.$store.commit('USER', value)
      }
  }
}

vue原型上实现的setCookie、getCookie、delCookie

//设置cookie,增加到vue实例方便全局调用
Vue.prototype.setCookie = (c_name, value, expiredays) => {
  var exdate = new Date();    
  exdate.setDate(exdate.getDate() + expiredays);    
  document.cookie = c_name + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate.toGMTString());
}

//获取cookie
Vue.prototype.getCookie = (name) => {
    var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
    if (arr = document.cookie.match(reg)){
        return (arr[2]);
    }   
    return null;   
}

//删除cookie
Vue.prototype.delCookie =(name) => {
    var exp = new Date();
    exp.setTime(exp.getTime() - 1);
    var cval = this.getCookie(name);
    if (cval != null){
        document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
    }     
}

webpack proxyTable 代理跨域

proxyTable: {
    '/api': {
        target: 'http://api.douban.com/v2',
        changeOrigin: true,
        pathRewrite: {
            '^/api': ''
        }
    }
}

这样当我们访问localhost:8080/api/movie的时候 其实我们访问的是http://api.douban.com/v2/movie这样便达到了一种请求转发的方案。

当然我们也可以根据具体的接口的后缀来匹配代理,如后缀为.shtml,代码如下:

proxyTable: {
    '**/*.shtml': {
        target: 'http://192.168.198.111:8080/abc',
        changeOrigin: true
    }
}

可参考地址:

vue非父子组件通信

如果2个组件不是父子组件那么如何通信呢?这时可以通过eventHub来实现通信。所谓eventHub就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。

方式1:
把中转站eventHub放到两个组件能全局访问的,示例代码:

var Hub = new Vue()

组件1触发:

<div @click="eve"></div>
methods: {
    eve() {
        Hub.$emit('change','hehe'); //Hub触发事件
    }
}

组件2接收:

created() {
    Hub.$on('change', (msg) => { //Hub接收事件
        this.msg = msg;
    });
}

可参考:《vue非父子组件怎么进行通信》

方式2:

把中转站数据存放到根实例下面,如下:

// 根组件(this.$root)
new Vue({
 el: '#app',
 router,
 render: h => h(App),
 data: {
  // 空的实例放到根组件下,所有的子组件都能调用
  Bus: new Vue()
 }
})

组件1触发:

<div @click="eve"></div>
methods: {
    eve() {
        this.$root.Bus.$emit('change','hehe'); 
    }
}

组件2接收:

created() {
    this.$root.Bus.$on('change', (msg) => { //接收事件
        this.msg = msg;
    });
}

常见问题

IE9报vuex requires a Promise polyfill in this browser问题解决

因为使用了 ES6 中用来传递异步消息的的Promise,而IE低版本的浏览器不支持。

解决方法
**第一步: 安装 babel-polyfill **

babel-polyfill可以模拟ES6使用的环境,可以使用ES6的所有新方法

npm install --save-dev babel-polyfill

第二步: 在 Webpack/Browserify/Node中使用

在webpack.config.js文件中把

module.exports = {
    entry: {
        app: './src/main.js'
    }
}

替换为:

module.exports = {
    entry: {
        app: ["babel-polyfill", "./src/main.js"]
    }
};

当然还有其它两种引入方式:

  • require("babel-polyfill");
  • import "babel-polyfill";

启动Vue项目时提示: [BABEL] ... max of "500KB".

在项目的根目录下找到 .babelrc 文件,增加 "compact": false ,如:

{
  "compact": false,
  "presets": ["env", "react", "stage-0"],
  "plugins": [
    "transform-runtime"
  ]
}

如果不存在则手动创建该文件,并填写内容如:

{
  "compact": false
}

pdf.js默认不显示电子签章(数字签名)问题解决

1. pdfjs 旧版本

pdf.worker.js 找到

if(this.data.fieldType === 'Sig') {
    warn('unimplemented annotation type: Widget signature');
    return false;
}

注解上面代码.

2. pdfjs 新 版本v1.10.88

pdf.worker.js 找到

if(data.fieldType === 'Sig') {
    _this2.setFlags(_util.AnnotationFlag.HIDDEN);
} 

pdf.js预览,中文显示乱码解决方法

有可能是有pdf不支持的字体格式,引入pdf.js的字体试试

const CMAP_URL = 'https://unpkg.com/pdfjs-dist@2.0.489/cmaps/';
pdfjsLib.getDocument({
    data: pdfData,
    cMapUrl: CMAP_URL,
    cMapPacked: true,
})

解决Vue引入百度地图JSSDK:BMap is undefined 问题

export default {
  init: function (){
    //console.log("初始化百度地图脚本...");
    const AK = "AK密钥";
    const BMap_URL = "https://api.map.baidu.com/api?v=2.0&ak="+ AK +"&s=1&callback=onBMapCallback";
    return new Promise((resolve, reject) => {
      // 如果已加载直接返回
      if(typeof BMap !== "undefined") {
        resolve(BMap);
        return true;
      }
      // 百度地图异步加载回调处理
      window.onBMapCallback = function () {
        console.log("百度地图脚本初始化成功...");
        resolve(BMap);
      };

      // 插入script脚本
      let scriptNode = document.createElement("script");
      scriptNode.setAttribute("type", "text/javascript");
      scriptNode.setAttribute("src", BMap_URL);
      document.body.appendChild(scriptNode);
    });
  }
} 

说明:

本地启动vue项目,host配置域名访问出现Invalid Host header 服务器域名访问出现的问题

在webpack.dev.config.js中找到 devServer下的hot,再下面添加 disableHostCheck: true,来解决127.0.0.1指向其他域名时出现"Invalid Host header"问题

如图所示:

image

参考原文:12 Tips and Tricks to Improve Your Vue Projects

posted @ 2017-12-10 17:05  风雨后见彩虹  阅读(5657)  评论(0编辑  收藏  举报