学习Vue.js

安装Vue

安装Node.js后,使用npm安装Vue:

npm install -g vue-cli

创建Vue项目

通过Vue模板创建Vue项目,一般使用webpackpwa模板:

vue-init webpack

数据绑定

Vue支持双向数据绑定,有内联(interpolation)及指令(directive)两种表现方式。

内联的使用方式:

<div id="app">{{title}}</div>

指令的使用方式,以v-model为例:

<input type="text" v-model="message">

Vue指令除用于绑定数据外,还可以完成逻辑处理、方法调用等功能。下文会列举一些常用的指令

数据响应性

数据响应性(reactivity)在这里指数据更新时,界面显示是否会同步更新。

只有初始化时已存在的属性才具有响应性。

const vm = new Vue({
    data: {
        mydata: {fullname: ""}
    }
});

所以,对于上面定义的Vue实例,vm.mydata.name 不具有响应性,下面的数据更新后界面不会相应更新。

vm.mydata.name="shortname"

为了让属性具有响应性,除了在初始化时在data对象中声明,还有两种解决方式,以为vm.mydata添加name属性为例:

  • Object.assign方式:
    vm.mydata=Object.assign({}, vm.mydata, {name: "shortname"});
    

Object.assign的作用是合并多个对象的属性,用法为Object.assign(target, source_1, source_2, ...)

  • Vue.set方式,
    Vue.set(vm.mydata, "name", "shortname");
    

在组件中与Vue.set等效的方式是this.$set

常见模板指令(Directive)

v-if, v-else-if 条件判断

根据条件是否为真来显示或隐藏元素。

v-for: 用于迭代数组、对象元素,及计数

使用v-for时必须要使用:key="..."设置key,防止更新时重新输出所有DOM

<div v-for="t in tagsByAccount(accountId)" >
    <div>{{t.tag}}</div>
</div>

v-for支持数据解构。

  • 如对于对象数据,获取属性及属性值:
    <div v-for="(k, v) in myMap" > ... </div>
    
  • 如对于数组,取出元素时还可获得相应的索引:
    <div v-for="(dog, idx) in dogs" > ... </div>
    
  • 计数:
    <div v-for="idx in 6" >
      <img :src="'//mydomain.com/static/img/mimg-'+idx + '.png'"/>
    </div>
    
    

v-bind 绑定事件/HTML元素的属性

<button v-bind:disabled="btnDisabled">...</button>

等同于

<button :disabled="btnDisabled">...</button>

若需要拼接字符串:

<div class="progress" :class="'progress--' + progress.percent"></div>

v-model

v-model用于数据绑定与更新,它相当于v-bind与更新事件的组合。如

<input v-model="myname"/>

等同于使用v-bind版本的:

<input :value="myname" @input="value=>myname=value" />

使用v-bind的方式更灵活,比如可以自定义用户输入后数据更新的方法:

// 注意handleInput的参数为事件e
<input :value="myname" @input="handleInput" />

v-html 原始HTML显示

将原始HTML解析为DOM元素,与ClojureScript的dangerouslySetInnerHTML相似。

⚠ 将未处理的用户输入作为v-html输出,可引起XSS攻击

v-on 绑定事件

v-on:click 可简写为 @click

事件定义支持修饰符,多个修饰符可组合使用。组合使用时表示逻辑与的关系,例如在下例中,仅忽略第一次点击:

<div @click.stop.capture.once>...</div>

@keyup 按键事件

Vue支持所有按键的按键事件绑定。在下例中,按下Enter键后,将调用handleKeyUpEnter方法:

@keyup.enter="handleKeyUpEnter"

在此例中,按下EnterShift+Enter组合键后均会调用handleKeyUpEnter方法。若想只在单独按下Enter键后调用handleKeyUpEnter,则应使用exact修饰符(如,@keyup.enter.exact=...)。

@click 点击事件

点击事件支持以下几个修饰符:

  • prevent
  • stop
  • once
  • capture
  • self

示例(来源):

<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>

<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- modifiers can be chained -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- just the modifier -->
<form v-on:submit.prevent></form>

<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div v-on:click.capture="doThis">...</div>

<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div v-on:click.self="doThat">...</div>

使用emit触发父元素事件

在父元素中定义

<child ref="home" @myclick='handleChildEvent'/>

在子元素中定义

<button @click="$emit('myclick')"></button>

自定义指令

自定义指令的方法与定义Filter的方式类似。如

Vue.directive('blink', {
  bind(el) {
    let isVisible = true;
    setInterval(() => {
      isVisible = !isVisible;
      el.style.visibility = isVisible ? 'visible' : 'hidden';
    }, 1000);
  }
});

使用自定义指令与使用Vue自带指令相同:

<p v-blink>This content will blink</p>

组件的生命周期类似,自定义指令也有5个钩子函数,分别对应5个时间节点:

  1. bind 该指令与组件绑定时调用
  2. inserted 绑定的元素加入父元素时调用
  3. update 父元素更新时调用
  4. componentUpdated
  5. unbind

对于更复杂的自定义指令,就需要指定bind函数的第2个参数,如bind(el, binding) {...}

例如,对于

<div v-blink:long.twice="someExpression()">blink</div>

binding对象存在以几个属性:

  1. name 对应指令名称,值为blink
  2. value 值为someExpression()的值
  3. oldValue 更新前的值,仅在更新时存在
  4. expression 即‘someExpression()’
  5. arg 值为long
  6. modifiers 为对象{twice: true}

Methods

可通过methods为组件定义方法。在方法体中,可通过this访问当前组件的属性或其它方法。

Computed

可通过computed为Vue实例或组件Computed属性,可通过this访问当前组件的属性或其它方法。

Methods 与 Computed属性的差异

在组件中,方法(methods)与Computed属性都能以函数定义的方式声明。它们的差异是

  1. 方法可以带参数,但Computed属性不可以
  2. 方法无缓存,但Computed属性可缓存上次计算结果
  3. Computed属性也可以通过set/get方法定义,例如
    // ...
    data: {
        numbers: [5, 8, 3]
    },
    computed: {
        numberTotal: {
            get() {
                return numbers.reduce((sum, val) => sum + val);
            },
            set(newValue) {
                const oldValue = this.numberTotal;
                const difference = newValue - oldValue;
                this.numbers.push(difference).
            }
        }
    }
    // ...
    

Watcher 观察者

watcher方法,用于监视组件属性的变化。如

watch: {
    'formData.user': {
        handler(new_val, old_val){
            //
        },
        deep: true
    }
}

注意:只有当deep设为true时,才会监视目标属性内部值的变化。

Filter 数据格式化/后处理

在组件中通过以下方式定义filter:

filters: {
    formatCost(val, sym) {
        return val+' ' + sym;
    }
}

或定义全局filter:

Vue.filter('formatCost', function(val, sym){...});

注意:filter必须为纯函数,且在filter方法体里,this不可用。

使用上面定义的filter

<input type="text" v-bind:value="cost | formatCost('$')">

使用ref绑定DOM

与第三方库交互时,有可能需要访问DOM,可以通过ref实现:

<canvas ref="myCanvas"></canvas>

在组件中,通过this.$ref.myCanvas获取DOM。

网页特效

CSS特效

Vue自带CSS特效支持。使用时需要将目标DOM元素用transition组件包裹,并定义相应的CSS类:

<transition name="fade">
  <div v-if="someCondition"> ... </div>
</transition>

// 相应的CSS定义:
.fade-enter-active, .fade-leave-active{...}
.fade-enter, .fade-leave-to{...}

transition会使用下面这些CSS:

  • {name}-enter
  • {name}-enter-active
  • {name}-enter-to
  • {name}-leave
  • {name}-leave-active
  • {name}-leave-to

JavaScript特效

通过v-on指令,可实现JavaScript特效。用这种方式可方便地接入第三方特效库,如velocity。

<transition v-on:before-enter="..."
            v-on:enter="..."
            v-on:leave="..."
            >
  <div v-if="someCondition"> ... </div>
</transition>

组件

组件是Vue中可重复使用的最小单元。使用组件即可以减少重复的代码,还可将复杂的业务逻辑分解为简单的模块。

组件可以全局定义:

Vue.component('myButton',
              {
                  template: '<button>..</button>',
                  // ...
              })

定义后可以在所有模板中通过<myButton>的形式使用。

也可以在当前组件中定义局部组件:

const MyButton = {
    template: '...',
    // ...
}

Vue组件与Vue实例的比较

Vue实例(Instance)通过new Vue(...)的方式定义,由于实例创建后一般不会复用,其data属性定义为JavaScript对象。

与之相反,Vue组件(Component)通常会在一个页面多次调用,所以其data属性必须定义为函数。

组件的props属性

props vs data

props 与 data的异同:

  • props表示从父元素传入的属性,而data表示当前组件自带的属性。
  • props的初始值由调用方通过Vue模板设置

props的声明与使用

下面的perDis就是组件的一个props属性,

<mydiv perDis="20"></pidis>

由于Vue支持kekab-casecamelCase转换,上面的代码也可以写成

<mydiv per-dis="20"></pidis>

在组件声明时,通过props字段声明组件属性。可以使用数组方式与对象方式声明。

使用数组方式时,属性值只能当做String类型处理。

使用对象方式时,可以为属性指定type(属性的类型), required(是否必须提供), default(默认值)及validator(验证函数)。

// 数组方式
props: ['perDis']
// 对象方式
props: {
    perDis: {
        type: Number,
        required: true,
        default: 3,
        validator(val){
            return val>0;
        }
    }
}

注意 指定了类型后,在调用时还须使用v-bind

// perDis仍然是String类型
<mydiv perDis="20"></pidis>
// perDis自动转换为Number类型
<mydiv v-bind:perDis="20"></pidis>

props值的更新

父元素更新属性值时,会传播到它创建的组件中,但默认情况下,组件不能修改父元素传入的属性。

以下面的代码为例,numberFromParent为父元素的属性,若需要在mydiv组件中更新numberFromParent的值,应当使用sync修饰符:

<mydiv :perDis.sync="numberFromParent"/>

等同于:

<mydiv :perDis="numberFromParent" @update:number="val => numberFromParent = val" />

需要注意的是若两个组件同时使用并修改一个可变属性,有可能造成无限循环

Vue组件生命周期

Vue组件的生命周期可以分为创建(create)、加载(mount)、更新(update)、销毁(destroy)4个阶段,每个阶段又可分为执行前与执行后两部分。

我们可以定义下面8个钩子函数,用于在组件的不同生命周期执行:

  1. beforeCreate
  2. created 此时Vue已从模板编译为HTML代码
  3. beforeMount
  4. mounted 注意此时并不能保证组件的DOM已加载。要想确认DOM已加载,应在mounted函数体中使用this.$nextTick(callback_fn)回调来实现
  5. beforeUpdate
  6. updated
  7. beforeDestroy
  8. destroyed

VueRouter:Vue路由

参考vue-router

与后台协同开发时的CORS错误

通常前端与后台协同开发时,后台接口与Vue前端通常不在一个域名下,这样调用时就会存在跨域访问错误。有个办法是后台接口在开发环境允许跨域访问,但这样不仅需要修改后台代码,而且万一在发布时忘记禁用跨域,会带来安全问题。

其实Webpack已经考虑到这个问题了,你只需要在config/index.js里,找到dev参数,并在里面添加proxyTable配置指定后台接口服务的域名或IP与端口:

dev: {
    // ...
    proxyTable: {'/api':
                 {
                     target: 'http://127.0.0.1:4499',
                     changeOrigin: true
                 },
                },
}

Comment