简介

Vue - 介绍

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

后起之秀,生态完善,已然称为前端工程师必备技能。

Vue - 特点

  1. 声明式渲染
  2. 组件化模式
  3. 使用 虚拟 DOM + 优秀的 Diff 算法 ,高复用 DOM 节点

Vue - 文档

官方网站

中文文档

Vue - MVVM模型

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

  1. M - 模型(Model):对应 data 中的数据
  2. V - 视图(View):对应模板
  3. VM - 视图模型(ViewModel):对应 Vue 实例对象

Vue - 引入

  1. 通过 <script> 标签引入远程 Vue.js

    • 对于制作原型或学习,你可以这样使用最新版本:
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    • 对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
    • 如果你使用原生 ES Modules,这里也有一个兼容 ES Module 的构建文件:
    <script type="module">
      import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js'
    </script>
  2. 通过 <script> 标签引入本地 Vue.js

    开发版本包含完整的警告和调试模式

    生产版本删除了警告,33.46KB min+gzip

  3. 使用 NPM 安装

    # 最新稳定版
    $ npm install vue

实例

实例 - 创建一个 Vue 实例

每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:

注意:Vue 实例所管理的函数不能用箭头函数,否则 this 指向的不是当前 vm 而是 window

var vm = new Vue({
  el:'#root',
  data:{
      name:'nayst'
  },
  methods:{
      method1(){
          
      }
  },
  ...
})

el 还可以利用$mount挂载到 vm 上:

var vm = new Vue({
	
})
vm.$mount('#root')

data除了上述的对象式写法,还有一种常用的函数式写法:

var vm = new Vue({
	data() {
        return {
            name:'test'
        }
    }
})

在组件中,data必须使用_函数式_写法!

实例 - 数据与方法

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

// 我们的数据对象
var data = { a: 1 }

// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
  data: data
})

// 获得这个实例上的 property
// 返回源数据中对应的字段
vm.a == data.a // => true

// 设置 property 也会影响到原始数据
vm.a = 2
data.a // => 2

// ……反之亦然
data.a = 3
vm.a // => 3

实例 - 生命周期钩子

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

例如 created钩子可以用来在一个实例被创建之后执行代码:

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a)
  }
})
// => "a is: 1"

实例 - 生命周期图示

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

语法

模板语法 - 插值

插值语法用于解析标签体的内容,在双大括号之间加入 js 表达式。这种方式可以直接读取到data中的所有属性。

<span>{{ msg }}</span>

对于所有的数据绑定,Vue.js 都提供了完全的 js 表达式支持。

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

模板语法 - 指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

例如,给标签绑定v-if指令,通过改变seen值的真假来 插入/移出 标签:

<p v-if="seen">现在你看到我了</p>

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute:

<a v-bind:href="url">...</a>
<!-- 可以简写成下面的形式 -->
<a :href="url">...</a>

另一个例子是 v-on 指令,它用于监听 DOM 事件:

<a v-on:click="doSomething">...</a>
<!-- 可以简写成下面的形式 -->
<a @click="doSomething">...</a>

数据

数据 - 单向绑定

通过v-bind完成对数据的单向绑定,数据只能从data流向页面。

<div id="root">
    你原来的名字:<input type="text" v-bind:value="name">
</div>

数据 - 双向绑定

通过v-model完成对数据的双向绑定,数据可以从data流向页面,也可以从页面流向data

<div id="root">
    你要修改的名字:<input type="text" v-model:value="name">
</div>

注意:(1)双向绑定一般都应用在表单类元素上。

(2)v-model:value 可以简写为 v-model ,因为 v-model 默认接收 value

数据 - 表单输入数据

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。

文本框、多行文本、下拉框中,在 data 中绑定 value 即可:

<form @submit.prevent="demo">
    <input type="text" v-model="info.text">
</form>

单选框中,需要绑定值,还要给标签配置 value :

<form @submit.prevent="demo">
    <input type="radio" name="sex" v-model="info.sex" value="male">man
    <input type="radio" name="sex" v-model="info.sex" value="famale">woman
</form>

复选框中,需要绑定值,还要给标签配置 value ,绑定的值要为一个_数组_:

<form @submit.prevent="demo">
    <input type="checkbox" v-model="info.hobby" value="sing">sing
    <input type="checkbox" v-model="info.hobby" value="dance">dance
    <input type="checkbox" v-model="info.hobby" value="study">study
</form>

需要配置的 data :

data:{
    info:{
    	text:'',
        sex:'famale',
        hobby:[]
    }
}

数据 - 数据代理

利用Object.defineProperty(obj, prop, desc) 操作数据,可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。

  • obj:需要定义属性的当前对象

  • prop:当前需要定义的属性名

  • desc:属性描述符

基本用法:

let Person = {
    name:'',
}
Object.defineProperty(Person, 'name', {
    value: 'jack',
    enumberable:true,//是否可以枚举,默认为false
    writable: true,// 是否可以改变,默认为false
    configurable: true,//是否可以被删除,默认为false
})

进阶用法:

let Person = {
    name:'',
    age:'',
}
let number = 19
Object.defineProperty(Person, 'age', {
    get(){
        return number
    },
    set(value){
        number = value
    }
}

Vue 中的数据代理,是通过 vm 对象来代理 data 对象中的属性的操作(读/写)

Vue 中的数据代理的好处:更加方便的操作 data 中的数据

基本原理:通过 Object.defineProperty 把对象中的所有属性加到 vm 上,每一个属性都添加了对应的 getter/setter ,用于操作 data 中对应的属性

事件

事件 - 事件处理

可以用 v-on 指令监听 DOM 事件,并在触发时运行对应的代码。

<div>
    <buttom @click="greet">Greet</buttom>
</div>
methods:{
    greet(){
        alert('Hello')
    }
}
  • 事件的回调需要配置在 methods 中,最终在 vm 上

  • methods 中配置的函数,不能用箭头函数,否则 this 就不是 vm 了

  • methods 中配置的函数,都是被 Vue 所管理的函数,this 指向的是 vm 或 组件实例对象

事件 - 事件修饰符

Vue.js 为 v-on 提供如下了事件修饰符,用于在处理事件时进行一些限制。

  1. prevent:阻止默认事件(常用)
  2. stop:阻止事件冒泡(常用)
  3. once:事件只触发一次(常用)
  4. capture:使用事件的捕获模式,即内部元素触发的事件优先在此处理,然后才交由内部元素进行处理
  5. self:只有 event.target 是当前操作的元素时才触发事件,即事件不是从内部元素触发的
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕
<div>
    <a href="http://www.baidu.com" @click.prevent="showInfo">点我提示</a>
    <!-- 修饰符可以串联 -->
	<a @click.stop.prevent="doThat"></a>
</div>

事件 - 键盘事件

Vue.js 中的键盘事件有 keyup(按下并抬起时触发)和 keydown (按下时触发),并且可以在触发时添加修饰符:_enter_(回车)、_delete_(删除)、_esc_(退出)、_space_(空格)、_tab_(换行)、_up_(上)、_down_(下)、_left_(左)、_down_(右)。

其余未提供别名的按键,可以使用按键原始的 key 值去绑定,利用 event.keyCode 获取按键编码。

系统修饰键 Ctrl、Alt、Shift、Meta 用法特殊,当搭配 keydown 使用的时候,正常按下即触发;当搭配 keyup 的时候,需要按下修饰键时同时按下任意其他键,释放后触发。

<div>
    <input type="text" placeholder="按下回车提示输入" @keydown.ctrl="showInfo">
</div>

属性

属性 - 计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。 Vue.js 中提供了_计算属性_,用于得到不存在的属性。例如:

<div id="root">
    你的全名:<span>{{fullName}}</span>
</div>
data:{
    firstName:'nayst',
    lastName:'yang'
}
computed:{
    fullName:{
        //当 fullName 被 读取 时调用 get
        get(){
            console.log('get被调用了',this)
            return this.firstName + '-' + this.lastName
		},
        //当 fullName 被 修改 时调用 set
        set(value){
            console.log('set',value)
        }
    }
}

当计算属性不需要setter修改时,可以简写成如下形式:

//计算属性的简写
computed:{
    fullName(){
        console.log('get被调用了',this)
        return this.firstName + '-' + this.lastName
    }
}

原理:底层借助了Object.defineProperty方法提供的 setter 和 getter

属性 - 监视属性

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:监视属性。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。例如:

<div>
    <input type="text" placeholder="请输入您的问题" v-model="question">
</div>
data:{
	question:''
},
watch:{
    answer:{
        immediate:true,//在初始化时调用一次 handler
        // handler 在 answer 改变时调用
		handler(newValue,oldValue){
            console.log('question被修改了,原来是',oldValue,',现在是',newValue)
        }
    }
}

还有另一种命令式的 API 写法:

vm.$watch('answer',{
    handler(newValue,oldValue){
        console.log('question被修改了,原来是',oldValue,',现在是',newValue)
    }
})

当需要监视对_多级结构_中某个属性变化时,需要开启深度监视

data:{
	numbers:{
        a:1,
        b:2
    }
},
watch:{
    numbers:{
        deep:true//是否开启深度监视
		handler(Value){
            console.log('question被修改了',Value)
        }
    }
}

当_监视属性_中只需要 handler 时,可以采用如下简写形式:

watch:{
    answer(newValue,oldValue){
        console.log('question被修改了,原来是',oldValue,',现在是',newValue)
    }
}

当然 API 写法也是可以简写的:

vm.$watch('answer',function(newValue,oldValue){
	console.log('question被修改了,原来是',oldValue,',现在是',newValue)
})

绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组

绑定 - Class样式

我们可以传给 v-bind:class 一个字符串,以动态地切换 class:

<div>
    <div class="basic" :class="class">
        <span>{{ msg }}</span>
    </div>
</div>
data:{
    msg:'This is a test.',
    class:'className'
}

同样的,我们也可以传_数组_和_对象_:

<div>
    <div class="basic" :class="classArr">
        <span>{{ msg1 }}</span>
    </div>
    <div class="basic" :class="classObj">
        <span>{{ msg2 }}</span>
    </div>
</div>
data:{
    msg1:'This is an array.',
    msg2:'This is an object',
    classArr:['className1','className2','className3'],
    classObj:{
        className1:true,
        className2:false,
        className3:true
    }
}

绑定 - Style样式

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象

<div>
    <div class="basic" :style="styleObj">
        <span>{{ msg }}</span>
    </div>
</div>
data:{
    msg:'This is an object.',
    styleObj:{
        fontsize: '40px',
        color: 'red'
    }
}

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div>
    <div class="basic" :style="styleArr">
        <span>{{ msg }}</span>
    </div>
</div>
data:{
    msg:'This is an array.',
    styleObj:[
        {
            fontsize: '40px',
        	color: 'red'
        },
        {
            backgroundColor: 'gray'
        }
    ]
}

渲染

渲染 - 条件渲染

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 值的时候被渲染。v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用。v-else 指令用于表示 v-if 的“else 块”

<div>
    <div v-if="n===1">Angular</div>
    <div v-else-if="n===2">Vue</div>
    <div v-else>Wrong choice!</div>
</div>

另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:

<div>
    <div v-show="false">You can't see me</div>
    <div v-show="true">You can see me</div>
</div>

渲染 - 列表渲染

我们可以用 v-for 指令基于一个_数组_来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名

<ul>
    <li v-for="(person,index) in persons" :key="index">
    	{{person.name}}-{{person.age}}
    </li>
</ul>
data:{
    persons:[
        {id:'01',name:'Akko',age:'18'},
        {id:'02',name:'Ashe',age:'19'},
        {id:'03',name:'Luxa',age:'20'}
    ]
}

你也可以用 v-for 来遍历一个_对象_的 property

<ul>
    <li v-for="(value,key) in cars" :key="key">
    	{{key}}-{{value}}
    </li>
</ul>
data:{
    cars:{
        name:'奥迪A8',
        price:'800k',
        color:'black'
    }
}

注意:key 在循环中作为_唯一标识_,不写默认为_循环的索引_

生命周期钩子

所有生命周期钩子的 this 上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。因为箭头函数绑定了父级上下文,所以 this 不会指向预期的组件实例,并且this.fetchTodos 将会是 undefined。

生命周期钩子 - beforeCreate

在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用

生命周期钩子 - created

在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用

生命周期钩子 - beforeMount

在挂载开始之前被调用:相关的 render 函数首次被调用

该钩子在服务器端渲染期间不被调用

生命周期钩子 - mounted

实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内

注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick

mounted(){
  this.$nextTick(function () {
    // 仅在整个视图都被渲染之后才会运行的代码
  })
}

该钩子在服务器端渲染期间不被调用

生命周期钩子 - beforeUpdate

在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器

该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行

生命周期钩子 - updated

在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性watcher 取而代之。

注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick

updated(){
  this.$nextTick(function () {
    //  仅在整个视图都被重新渲染之后才会运行的代码     
  })
}

该钩子在服务器端渲染期间不被调用

生命周期钩子 - beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用

该钩子在服务器端渲染期间不被调用

生命周期钩子 - destroyed

实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁

该钩子在服务器端渲染期间不被调用

脚手架

脚手架 - 简介

Vue CLI (_C_ommand _L_ine _I_nterface)是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 实现的交互式的项目脚手架
  • 通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发
  • 一个运行时依赖 (@vue/cli-service),该依赖:
    • 可升级;
    • 基于 webpack 构建,并带有合理的默认配置;
    • 可以通过项目内的配置文件进行配置;
    • 可以通过插件进行扩展
  • 一个丰富的官方插件集合,集成了前端生态中最好的工具
  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面

Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject

脚手架 - 安装

  1. 全局安装 @vue/cli (仅首次)

    npm install -g @vue/cli
  2. 切换到你要创建项目的目录,创建项目

    vue create xxxx
  3. 启动项目

    npm run serve

备注:切换淘宝镜像

npm config set registry https://registry.npm.taobao.org

脚手架 - 目录结构

  1. 文件

    .gitnore: Git 的忽略文件,在此描述哪些文件(夹)不接受 Git 的管理

    babel.config.js:babel 的控制文件

    package.json:包的说明书,存在于 npm 创建的工程中

    package-lock.json:包版本控制文件,版本仓库

    README.md:项目的描述文件

  2. 文件夹

    src:存放以下内容

    ——main.js:整个项目的入口文件

    ——App.vue:页面的主要内容

    ——asserts:存放项目里的静态资源,如图片、页签

    ——components:存放组件

    public:存放公共资源

组件

组件 - 基础

组件是可复用的 Vue 实例,且带有一个名字

注意一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝

通常一个应用会以一棵嵌套的组件树的形式来组织:

组件 - 非单文件组件

使用组件的步骤:

  1. 定义(创建)组件

    使用 Vue.extend(options) 创建,其中的 options 和创建 Vue 实例时的 options 几乎一样

  2. 注册组件

    • 局部注册:在创建 Vue 实例的时候传入 components 选项
    • 全局注册:直接 Vue.component(‘组件名’,组件)
  3. 使用组件

    编写组件标签:<组件名/>

<!-- 准备一个容器 -->
<div id="root">
    <school></school>
</div>
const school = Vue.extend({
    template:`<div><h2>学校名称:{{name}}</h2></div>`,
    data(){
        return{
            name:'CCSU'
        }
    }
})

new Vue({
    el:'#root',
    components:{school}
})

注意

  1. 组件名是多单词时,中间用-连接,如果在脚手架中,首字母大写即可
  2. 组件标签的自闭合只能在脚手架中用

组件 - 单文件组件

将非单文件组件的内容写作一个独立的.vue文件,在脚手架中使用

<template>
	<div>
        
    </div>
</template>

<script>
	export default {
        name:'filename',
        data(){
            return{
                
            }
        },
        methods:{
            
        }
    }
</script>

<style></style>

注意:这里的filename必须与组件名保持一直,首字母大写

组件 - 组件嵌套

Vue 支持在组件中注册子组件

<!-- 准备一个容器 -->
<div id="root">
    <school></school>
</div>
const student = Vue.extend({
    template:`<div><h2>学生名称:{{name}}</h2></div>`,
    data(){
        return{
            name:'Nayst'
        }
    }
});

const school = Vue.extend({
    template:`
    	<div><h2>学校名称:{{name}}</h2></div>
    	<student></student>
    `,
    data(){
        return{
            name:'CCSU'
        }
    }
});

new Vue({
    el:'#root',
    components:{school}
});

组件 - ref

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。

$refs是一个对象,存放注册过 ref attribute 的所有 DOM 元素和组件实例

如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素

如果用在子组件上,引用就指向组件实例(vc)

组件 - props

功能:让组件接受外部传过来的数据

传递数据:直接在标签内部写

<Student name="nayst" age="21"/>

接收数据:在组件内的 props 配置项内配置

//第一种:只接收数据
props:['name','age']
//第二种:限制类型
props:{
    name:String,
    age:Number
}
//第三种:限制类型、限制必要性、指定默认值,完整写法
props:{
    name:{
        type:String,
        required:true,
        default:'nayst'
    },
    age:{
        type:Number,
        required:false,
        default:'18'
    }
}

注意:props 是只读的,Vue 底层会监测你对 props 的修改,并且在修改后发出警告

组件 - mixin

功能:可以把多个文件共用的配置提取成一个混入对象

在 src 中创建 mixins.js (名字随意),用于定义混入:

export const test = {
    data(){
        
    },
    methods:{
        
    }
}

局部引入:在组件中引入并使用:

import {test} from '../mixin'
export default {
    name:'Student',
    mixins:[test]
}

全局引入:在main.js中引入:

import {test} from './mixin'
Vue.mixin(test)

组件 - plugin

功能:用于增强 Vue,本质是包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据

在 src 中创建 plugins.js (名字随意),用于定义插件:

export default {
    install(Vue){
        Vue.filter()//定义全局过滤器
        Vue.directive()//定义全局指令
        Vue.mixin()//定义混入
        Vue.prototype.func = ()=>{}//在 Vue 原型上添加一个方法
    }
}

main.js中引入并使用插件:

import plugins from './plugins'
Vue.use(plugins)

组件 - slot

功能:让父组件可以向子组件指定位置插入 html 结构,也是一种组件间的通信方式,适用于父组件给子组件传。

  1. 默认插槽

    父组件:

    <component>
    	<div>
            ...
        </div>
    </component>

    子组件:

    <template>
    	<div>
        	<!-- 定义插槽 -->
        	<slot>默认内容</slot>
        </div>
    </template>
  2. 具名插槽

    父组件:

    <component>
        <template slot="header">
        	<div>Header</div>
        </template>
        <template slot="footer">
            <div>Footer</div>
        </template>
    </component>

    子组件:

    <template>
    	<div>
            <slot name="footer">Default footer</slot>
            <slot name="header">Default header</slot>
        </div>
    </template>
  3. 作用域插槽:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。

通信

通信 - 自定义事件

自定义事件,适用于子组件给父组件传递数据

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称

第一步:定义一个自定义事件

methods:{
	demo(value){
		console.log('自定义事件被调用了,收到了',value)
	}
}

第二步:给子组件绑定自定义事件

<Student @myEvent="demo"/>

第三步:在子组件内触发自定义事件

<button @click="$emit('myEvent',value)"></button>

通信 - 全局事件总线

全局事件总线(Global Event Bus),适用于任意组件间通信

第一步,安装全局事件总线

new Vue({
    beforeCreate() {
        Vue.prototype.$bus = this //$bus 就是当前的应用
    }
})

第二步,利用事件总线发送数据

<button @click="$bus.$emit('test',studentName)"></button>

第三步,利用事件总线接收数据

mounted() {
    this.$bus.$on('test',(data)=>{
        
    })
},
beforeDestroy() {
    this.$bus.$off('test')
} 

注意:最好在 beforeDestroy 钩子中,用$off去解绑当前组件用到的事件

通信 - 消息订阅与发布

消息订阅与发布(pubsub),适用于任意组件间通信

第一步:安装pubsub

npm i pubsub-js

第二步:在需要的组件中引入pubsub

import pubsub from 'pubsub-js'

第三步:利用pubsub发布(发送)数据

<button @click="sendMsg"></button>
methods: {
    sendMsg(){
        pubsub.publish('test',data)
    }
}

第四步:利用pubsub订阅(接收)数据

mounted(){
    this.pubId = pubsub.subscribe('test',(msgName,data)=>{
        console.log('有人发布了test消息',msgName,data)//这里 msgName 就是 test
    })
},
beforeDestroy() {
    pubsub.unsubscribe(this.pubId)
}

过渡动画

Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。

过渡动画 - 单组件

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

第一步:在目标元素外包裹<transition name="xxx">

<transition name="fade">
    <p v-if="show">hello</p>
</transition>

第二步:定义 class 样式

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

过渡动画 - 类名

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

过度动画 - JS 钩子

可以在 attribute 中声明 JavaScript 钩子,通过 vue 提供给的动画钩子函数来绑定事件,然后在事件函数中处理对应的动画

入场动画:

before-enter 动画入场运动前一刻执行

enter 动画运动时执行

after-enter 在动画 enter 函数中运行完毕并调用回调 done 时执行

离场动画:(用法同上)

before-leave

leave

after-leave

例子:图片自跳动

<transition appear name="fade" @after-enter="show=false" @after-leave="show=true">
    <img src="../assets/logo.png" v-if="show">
</transition>
.fade-enter-active, .fade-leave-active {
  transition:opacity 1s;
}
.fade-enter, .fade-leave-to{
  opacity:0;
}

添加 appear 属性后,网页才会一打开就有入场动画,否则需要触发

过渡动画 - 多组件

多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件

<button @click="show = !show">test</button>
<transition-group name="fade">
    <h1 v-show="show" key="1">TEST1</h1>
    <h1 v-show="!show" key="2">TEST2</h1>
</transition-group>

过渡动画 - 第三方动画

Animate.css

<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

<div id="example-3">
  <button @click="show = !show">切换</button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>

数据请求

如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器,这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置

数据请求 - Axios

数据请求 - 代理

vue.config.js 中添加如下配置

module.exports = {
  devServer: {
    proxy: 'http://localhost:4000'
  }
}

Vuex

Vuex - 简介

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,也是一种组件间通信的方式

Vuex - 使用

  1. 安装Vuex

    npm install vuex
  2. 创建文件src/store/index.js

//引入
import Vue from 'vue'
import Vuex from 'vuex'
//应用
Vue.use(Vuex)
//响应组件中的动作
const actions = {}
//操作数据
const state = {}
//存储数据
const mutations = {}
//创建并暴露store
export default new Vuex.Store({
  actions,
  mutations,
  state
})
  1. main.js中配置
//引入store
import store from './store'
//使用store
new Vue({
    store
})

Vuex - 原理图

Vuex - state

Vue 管理的状态对象,存放数据的地方

注意:state 应该是唯一的

Vuex - actions

  1. 值为一个对象,包含多个响应用户动作的回调函数

  2. 通过 commit( )来触发 mutation 中函数的调用,间接更新 state

  3. 在组件中使用: $store.dispatch(‘对应的 action 回调名’) 触发回调

  4. 可以包含异步代码(定时器, ajax 等等)

Vuex - mutations

  1. 值是一个对象,包含多个直接更新 state 的方法
  2. 在 action 中使用:commit(‘对应的 mutations 方法名’) 触发
  3. mutations 中方法的特点:不能写异步代码、只能单纯的操作 state

Vuex - getters

  1. 值为一个对象,包含多个用于返回数据的函数,类似于计算属性
  2. 在组件中调用 $store.getters.xxx

路由

路由 - 简介

路由就是对应的映射关系,一组路由就是一组key <-> value键值对

key 就是路径

value 在前端中,是一个组件,当 key 改变时,会在页面上展示相应的 value 组件

value 在后端中,是一个函数,当 key 改变时,会处理客户端提交的请求

路由 - 使用

  1. 安装 vue-router :npm i vue-router
  2. 创建文件:src/router/index.js
  3. main.js引入:import VueRouter from 'vue-router'
  4. main.js使用:Vue.use(VueRouter)

路由 - 配置

import VueRouter from 'vue-router'
//引入组件
import Home from '../views/Home'
//创建并暴露一个路由
export default new VueRouter({
	routes: [
		{
			name:'Home',
			path:'/',
			component:Home
		},
	],
	mode: 'history'
})

路由 - 嵌套

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id',
      component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

路由 - 编程式导航

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现

  1. router.push(location, onComplete?, onAbort?)

在 Vue 实例内部,你可以通过 $router 访问路由实例,因此你可以调用 this.$router.push(),可以跳转到指定的页面。当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意:如果提供了 pathparams 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
  1. router.replace(location, onComplete?, onAbort?)

    router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录

  2. router.go(n)

    这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

    例子

    // 在浏览器记录中前进一步,等同于 history.forward()
    router.go(1)
    
    // 后退一步记录,等同于 history.back()
    router.go(-1)
    
    // 前进 3 步记录
    router.go(3)
    
    // 如果 history 记录不够用,那就默默地失败呗
    router.go(-100)
    router.go(100)

路由 - 命名

有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

要链接到一个命名路由,可以给 router-linkto 属性传一个对象:

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

这跟代码调用 router.push() 是一回事:

router.push({ name: 'user', params: { userId: 123 } })

这两种方式都会把路由导航到 /user/123 路径

路由 - 重定向

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

路由 - 别名

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,那么“别名”又是什么呢

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样

上面对应的路由配置为:

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构

路由 - 传参

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性

使用 props 将组件和路由解耦,如果 props 被设置为 trueroute.params 将会被设置为组件属性:

取代与 $route 的耦合

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [{ path: '/user/:id', component: User }]
})

通过 props 解耦

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})

这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试

如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用

const router = new VueRouter({
  routes: [
    {
      path: '/promotion/from-newsletter',
      component: Promotion,
      props: { newsletterPopup: false }
    }
  ]
})

你可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等

const router = new VueRouter({
  routes: [
    {
      path: '/search',
      component: SearchUser,
      props: route => ({ query: route.query.q })
    }
  ]
})

URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件

路由 - history

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面