Vue 是一套用于构建用户界面的,基于数据驱动、组件化思想的 **渐进式框架**,基于 **MVVM** 架构模式。Vue最独特的特性之一,是其非侵入性的 **响应式系统**

Vue.js 使用了基于 _HTML_的模板语法,允许开发者声明式地将底层 _Vue_实例的数据渲染进 _DOM_的系统。也可以不用模板,直接写渲染 _(render)_ 函数,使用可选的 _JSX_ 语法。


🐌 安装

1.CDN

CDN 全称:Content Delivery Network,即内容分发网络。通过在网络各处的智能虚拟网络,实时地根据网络流量等综合信息,将用户的请求重新导向离用户最近的服务节点上。

  1. //通过CDN,引入外部资源vue.js
  2. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

2.NPM

安装最新稳定版:$ npm install vue

  1. //引包:
  2. <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script>

3.命令行工具 (CLI)

安装 Vue CLI 包:$ npm install -g @vue/cli
以图形化界面创建和管理项目:$ vue ui


🔚 Vue 实例

1.实例创建

Vue 是一个构造函数,通过 new Vue 创建的根 Vue 实例和可选的嵌套的可复用的组件树可组成 Vue 应用。

  1. //创建实例时,传入一个选项对象
  2. <script>
  3. new Vue({
  4. router,
  5. store,
  6. //渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode
  7. //h 作为 createElement 的别名:const h = this.$createElement
  8. render: h => h(App)
  9. }).$mount('#app')
  10. </script>

2.实例选项(常用)

属性方法 含义 说明
el 挂载:提供已存在的 DOM 元素作为 Vue 实例的挂载目标 存在则立即编译,否则 vm.$mount() 手动开启编译
data 数据:实例的数据对象或组件的函数(返回一个初始数据对象) vm.a 等价于访问 vm.$data.a
props 自定义属性:数组语法用于接收来自父组件的数据;对象语法提供类型验证 单向下行绑定:父级 prop 的更新会向下流动到子组件中
computed 计算属性:一些数据需随其它数据变动而变动时使用 缓存结果,依赖响应式属性变化时再计算
methods 方法:方法中的 this 自动绑定为 Vue 实例 不使用箭头函数来定义,其绑定了父级作用域的上下文
watch 侦听属性:键是需要观察的表达式,值是对应回调函数/方法名/含选项的对象 不应该使用箭头函数来定义 watcher 函数
filters 过滤器:文本格式化:如{{ a &#124; capitalize }}、<div :id="Id &#124; formatId"></div> 接收表达式的值:之前操作链结果作为第一个参数
directives 自定义指令:对普通 DOM 元素进行底层操作 钩子函数:bind首次绑定;inserted插入父节点;update VNode更新
components 组件:组件是可复用的 Vue 实例且带有一个名字,根实例中组件作为自定义元素 单个根元素、通过 Prop 向子组件传递数据、监听子组件事件、动态组件

3.添加实例属性

当组件里需求使用数据/工具且不污染全局作用域时,可根据约定的“ $ 是在 Vue 所有实例中都可用的属性(全局作用域)”在 Vue 的原型上添加相应属性。如引入 axios 实例以便全局调用:

  1. import http from './http.js'
  2. //在构造函数 Vue 的原型上赋值 axios 实例给 $http 属性,以便全局使用实例属性(约定 $ 开头)
  3. Vue.prototype.$http = http

4.生命周期

Vue 实例有一个完整的生命周期,即从开始创建、初始化数据、编译模版、挂载 Dom ->渲染、更新-> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

  • 各个生命周期作用 | 生命周期 | 描述 | | —- | —- | | beforeCreate | 组件实例被创建之初,组件的属性生效之前 | | created | 组件实例已创建,属性也绑定,真实DOM
    未生成,$el
    不可用 | | beforeMount | 在挂载开始之前被调用:相关的render
    函数首次被调用 | | mounted | el 被新创建的vm.$el
    替换,并挂载到实例上去后调用该钩子 | | beforeUpdate | 组件数据更新之前调用,发生在虚拟DOM
    打补丁之前 | | update | 组件数据更新之后 | | activited | keep-alive
    专属,组件被激活时调用 | | deadctivated | keep-alive
    专属,组件被移除时调用 | | beforeDestory | 组件销毁前调用,可以访问 this | | destoryed | 组件销毁后调用 |
  • 父组件和子组件生命周期钩子函数执行顺序
    • 加载渲染过程:父 beforeCreate ->created ->beforeMount ->子 beforeCreate ->created ->beforeMount ->mounted ->父 mounted
    • 更新过程:父 beforeUpdate->子 beforeUpdate->updated->父 updated
    • 销毁过程:父 beforeDestroy->子 beforeDestroy->destroyed->父 destroyed
  • 调用异步请求:在created钩子函数中调用异步请求
    • 能更快获取到服务端数据,减少页面 loading 时间
    • ssr 不支持 beforeMount 、mounted 钩子函数
  • 访问操作 DOM:mounted阶段,Vue 已经将编译好的模板挂载到页面上
  • 父组件监听子组件的生命周期:引用子组件时通过 @hook 来监听
  1. // Parent.vue
  2. <Child @hook:mounted="doSomething" ></Child>
  3. doSomething() {
  4. console.log('父组件监听到 mounted 钩子函数 ...');
  5. },
  6. // Child.vue
  7. mounted(){
  8. console.log('子组件触发 mounted 钩子函数 ...');
  9. },
  10. // 以上输出顺序为:
  11. // 子组件触发 mounted 钩子函数 ...
  12. // 父组件监听到 mounted 钩子函数 ...
  • 生命周期示意图
    学习 Vue(2.x) - 图1

📝 模板语法

1.插值

  • {{msg}}语法:
    • {{a}}:文本
    • {{ok?'YES':'NO'}}:表达式
  • Mustache 标签将会被替代为对应数据对象 data 上相应属性的值

2.指令

指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外)

  • v-bind
    • <a v-bind:href="url">...</a>:绑定参数
    • <a v-bind:[attributeName]="url"> ... </a>:绑定动态参数
    • <a :href="url">...</a>:缩写
    • 对象语法
      • 绑定 Class
        • HTML:<div :class="classObject"></div>
        • JS:data: {classObject: { active: true,'text-danger': false}}
      • 绑定内联样式
        • HTML:<div :style="styleObject"></div>
        • JS:data: {styleObject: {color: 'red',fontSize: '13px'}}
      • 对象语法常常结合返回对象的计算属性使用
    • 数组语法
      • 绑定 Class
        • HTML:<div :class='[classA, { classB: isB, classC: isC }]'></div>
        • JS:data: {classA: 'isclassA',isB: true,isC: false}
      • 绑定内联样式
        • HTML:<div :style="[baseStyles, overridingStyles]"></div>
        • JS:data: {baseStyles: {color: 'red'},overridingStyles:{fontSize: '13px'}}
  • v-on
    • <a v-on:click="doSomething">...</a>:监听事件,调用表达式/methods
    • <a @click="doSomething">...</a>:缩写
    • 事件修饰符
      • <a @click.stop="doThis"></a>:阻止冒泡
      • <form @submit.prevent="onSubmit">...</form>:阻止默认行为
      • <div @click.capture="doThis">...</div>:事件捕获
      • <div @click.self="doThat">...</div>:仅当 event.target 是当前元素自身时触发处理函数
    • 按键修饰符
      • <input v-on:keyup.enter="submit">:处理函数只会在$event.key等于 enter 时被调用
      • 常用按键码别名:.enter、.tab、.delete (捕获“删除”和“退格”键)、.esc、.space、.up、.down、.left、.right
    • 系统修饰键
      • <input @keyup.alt.67="clear"><!-- Alt + C -->:实现仅按下相应按键才触发鼠标或键盘事件监听器:.ctrl、.alt、.shift、.meta(windos 键)
      • .exact 修饰符:<button @click.ctrl.exact="onCtrlClick">A</button>控制精确输入
      • 鼠标按钮修饰符:.left、.right、.middle 这些修饰符会限制处理函数仅响应特定的鼠标按钮
  • v-if
    • <template v-if="ok"> <h1>Title</h1> <p>Paragraph</p></template>:条件渲染分组切换多个元素,此外可用 key 管理可复用的元素
    • <div v-if="Math.random() > 0.5">Now you see me</div><div v-else>Now you don't</div>:v-else/v-else-if 须紧跟在带 v-if 或 v-else-if 的元素之后
  • v-show
    • <h1 v-show="ok">Hello!</h1>:条件展示元素的选项
    • 与 v-if 区别:带有 v-show 的元素始终会被渲染并保留在 DOM 中,v-show 只是简单地切换元素的 CSS 属性 display,一般用于非常频繁地切换
  • v-for
    • <li v-for="(item, i) in items">{{ i }} - {{ item.msg }}</li>:渲染一数组元素
    • <div v-for="(val, name, i) in obj">{{ i }}. {{ name }}: {{ val }}</div>:遍历一个对象的属性,可把属性值、属性名、索引依次作为参数
    • <div v-for="item in items" :key="item.id"><!-- 内容 --></div>:尽量在使用 v-for 时提供 key attribute
    • <my-component v-for="(item, index) in items" :item="item" :index="index" :key="item.id"></my-component>:把迭代数据传递到组件里,使用 prop
  • v-model
    • 基础用法
      • 文本:<input v-model="msg" placeholder="e"><p>M is: {{ msg }}</p>
      • 多行文本:<p style="white-space: pre-line;">{{ msg }}</p><br><textarea v-model="msg" placeholder="add"></textarea>
      • 复选框:
        • HTML:<div id='e1'><input type="checkbox" id="jack" value="Jack" v-model="checkedNames"><label for="jack">Jack</label><input type="checkbox" id="john" value="John" v-model="checkedNames"><label for="john">John</label><span>Checked names: {{ checkedNames }}</span></div>
        • JS:new Vue({el: '#e1',data: {checkedNames: []}})
      • 单选按钮:
        • HTML:<div id="e2"><input type="radio" id="one" value="One" v-model="picked"><label for="one">One</label><br><input type="radio" id="two" value="Two" v-model="picked"><label for="two">Two</label><br><span>Picked: {{ picked }}</span></div>
        • JS:new Vue({el: '#e2',data: {picked: ''}})
      • 选择框:
        • HTML:<select v-model="selected"><option v-for="option in options" v-bind:value="option.value">{{ option.text }}</option></select><span>Selected: {{ selected }}</span>
        • JS:new Vue({el: '.',data: {selected: 'A',options: [{ text: 'One',value: 'A' },{ text: 'Two', value: 'B' },{ text: 'Three', value: 'C' }]}})
    • 值绑定:v-bind 实现值绑定到 Vue 实例的一个动态属性上,值可不是字符串
    • 修饰符
      • .lazy—<input v-model.lazy="msg" >转变为使用 change 事件进行同步
      • .number—<input v-model.number="age" type="number">输入值转为数值类型
      • .trim—<input v-model.trim="msg">过滤用户输入的首尾空白字符
    • 输入组件:一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件

💻 组件基础

1.基本概念

组件是可复用的 Vue 实例,其与 new Vue 接收相同的选项,如 data、computed、watch、methods 以及生命周期钩子等。例外:el 根实例特有的选项

定义组件名的方式:更推荐使用 kebab-case

2.组件的复用

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

组件是用来复用的,若组件中data是一个对象,作用域没有隔离,子组件中的data属性值会相互影响。

3.组件的组织

全局注册组件在各自内部都可相互使用,局部注册组件在其子组件中不可用。

  • 全局注册:
    Vue.component('my-component-name', {//..});new Vue({ el: '#app'})
  • 局部注册:
    var ComponentA = {/* template:..*/};new Vue({ el: '#app',components: {'component-a': ComponentA, 'component-b': ComponentB}})
  • 模块系统注册:
    • 在 ComponentB.vue 文件中局部注册:import ComponentA from './ComponentA'; import ComponentC from './ComponentC';export default {components:{ComponentA,ComponentC},// ...}
    • 基础组件的自动化全局注册:在应用入口文件 (比如 src/main.js) 中使用 require.context 全局导入基础组件
      1. import Vue from "vue";
      2. import upperFirst from "lodash/upperFirst";
      3. import camelCase from "lodash/camelCase";
      4. // https://webpack.js.org/guides/dependency-management/#require-context
      5. const requireComponent = require.context(
      6. // Look for files in the current directory
      7. ".",
      8. // Do not look in subdirectories
      9. false,
      10. // Only include "_base-" prefixed .vue files
      11. /_base-[\w-]+\.vue$/
      12. );
      13. // For each matching file name...
      14. requireComponent.keys().forEach(fileName => {
      15. // Get the component config
      16. const componentConfig = requireComponent(fileName);
      17. // Get the PascalCase version of the component name
      18. const componentName = upperFirst(
      19. camelCase(
      20. fileName
      21. // Remove the "./_" from the beginning
      22. .replace(/^\.\/_/, "")
      23. // Remove the file extension from the end
      24. .replace(/\.\w+$/, "")
      25. )
      26. );
      27. // Globally register the component
      28. Vue.component(componentName, componentConfig.default || componentConfig);
      29. });

4.通过 Prop 向子组件传递数据

Prop 是你可以在组件上注册的一些自定义特性

  • 父组件向子组件传 data 值:子组件通过 props 中自定义属性,获取其绑定的值
  • 传递静态:<blog-post title="My journey with Vue"></blog-post>
  • 传递动态:<blog-post v-bind:title="post.title"></blog-post>
  • 单向数据流:父级 prop 的更新会向下流动到子组件中,反过来则不行。防止从子组件意外改变父级组件的状态。

5.单个根元素——每个组件必须只有一个根元素

6.监听子组件事件

  • 父组件通过 v-on 监听子组件实例的任意事件:
    <blog-post v-on:enlarge-text="postFontSize += $event"></blog-post>
  • 父组件向子组件传方法:子组件通过调用内建的 $emit 方法,触发自定义事件,获取其绑定的方法(传参时可向父组件传递 data 值)
    <button @click="$emit('enlarge-text', 0.1)">Enlarge text</button>
  • 组件上使用 v-model:<custom-input v-model="searchText"></custom-input>
  1. // 将其 value 特性绑定到一个名叫 value 的 prop 上
  2. // 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
  3. Vue.component("custom-input", {
  4. props: ["value"],
  5. template: `<input :value="value" @input="$emit('input', $event.target.value)">`
  6. });

7.通过插槽分发内容

父级组件提供内容时将会被渲染,从而取代在<slot>标签内的后备内容

  • 具名插槽:<slot name="header"></slot><template v-slot="header"> 元素中的内容会被传入相应的插槽,v-slot 可替换为字符 #
  1. <!-- 模板 -->
  2. <div class="container">
  3. <header><slot name="header"></slot></header>
  4. <main><slot></slot></main>
  5. <!--不带 name 的 <slot> 带有隐含名“default”-->
  6. <footer><slot name="footer"></slot></footer>
  7. </div>
  8. <!-- 向具名插槽提供内容(缩写) -->
  9. <base-layout>
  10. <template #header
  11. ><h1>Here might be a page title</h1></template
  12. >
  13. <p>A paragraph for the main content.</p>
  14. <p>And another one.</p>
  15. <template #footer
  16. ><p>Here's some contact info</p></template
  17. >
  18. </base-layout>
  • 作用域插槽:绑定在<slot>元素上的特性被称为插槽 prop。父级作用域中,可给 v-slot 带一个值来定义我们提供的插槽 prop 的名字
  1. <!-- 模板 -->
  2. <span><slot v-bind:user="user"> {{ user.lastName }}</slot></span>
  3. <!-- 包含所有插槽 prop 的对象slotProps来定义我们提供的插槽 prop 的名字 -->
  4. <current-user>
  5. <template v-slot:default="slotProps">
  6. {{ slotProps.user.firstName }}
  7. </template>
  8. </current-user>
  9. <!-- 独占默认插槽的缩写 -->
  10. <current-user v-slot="slotProps">{{ slotProps.user.firstName }}</current-user>
  11. <!-- 解构传入具体的插槽 prop -->
  12. <current-user v-slot="{ user }">{{ user.firstName }}</current-user>

8.动态组件&异步组件

  • keep-alive:
    • 一般结合路由和动态组件一起使用,用于缓存组件
    • 提供 include、exclude 属性,支持字符串或正则表达式,exclude 优先级更高
    • 对应两个钩子函数 activated (被激活时触发)、deactivated (被移除时触发)
  1. <keep-alive>
  2. <component v-bind:is="flag"></component>
  3. </keep-alive>
  4. <!-- keep-alive包裹时会缓存不活动的组件实例,来保留组件状态或避免重新渲染 -->
  5. <!-- flag 可以包括已注册组件的名字,或一个组件的选项对象 -->
  • 异步组件:Vue 允许以一个工厂函数的方式异步解析组件定义。只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
  1. Vue.component(
  2. "async-webpack-example",
  3. // 这个 `import` 函数会返回一个 `Promise` 对象。
  4. () => import("./my-async-component")
  5. );
  6. // 局部注册
  7. new Vue({
  8. // ...
  9. components: {
  10. "my-component": () => import("./my-async-component")
  11. }
  12. });
  13. //处理加载状态:异步组件工厂函数也可以返回一个如下格式的对象
  14. const AsyncComponent = () => ({
  15. // 需要加载的组件 (应该是一个 `Promise` 对象)
  16. component: import("./MyComponent.vue"),
  17. // 异步组件加载时使用的组件
  18. loading: LoadingComponent,
  19. // 加载失败时使用的组件
  20. error: ErrorComponent,
  21. // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  22. delay: 200,
  23. // 如果提供了超时时间且组件加载也超时了,
  24. // 则使用加载失败时使用的组件。默认值是:`Infinity`
  25. timeout: 3000
  26. });

9.Vue 组件间通信

Vue 组件间通信主要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信

  • props / $emit 适用 父子组件通信
  • ref、$parent / $children、.sync 适用 父子组件通信
    • ref:用于DOM元素时引用指向DOM元素;用于子组件时引用指向组件实例
    • $parent / $children:访问父 / 子实例,适用父子、兄弟组件通信
    • .sync:语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器
  • $attrs / $listeners 适用于 隔代组件通信
    • $attrs:包含父作用域中不被prop所识别获取的特性绑定 (classstyle除外)。可由v-bind="$attrs"传入内部组件。常配合inheritAttrs:false使用
    • $listeners:包含父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可通过v-on="$listeners"传入内部组件
  • provide / inject 适用于 隔代组件通信:祖先组件中通过 provide 来提供变量,子孙组件通过 inject 注入。主要用于子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系
  • EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信:通过一个空的 Vue 实例作为中央事件总线(事件中心)来触发事件和监听事件
  • Vuex 适用于 父子、隔代、兄弟组件通信:每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )

    10. 函数式组件

  1. Vue.component('my-component', {
  2. functional: true,
  3. // Props 是可选的
  4. props: {
  5. // ...
  6. },
  7. // h 即 createElement 函数
  8. // 为了弥补缺少的实例,提供第二个参数作为上下文
  9. render: function (h, context) {
  10. // return vnode
  11. }
  12. })

组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:

  • props:提供所有 prop 的对象
  • children: VNode 子节点的数组
  • slots: 一个函数,返回了包含所有插槽的对象
  • scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
  • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
  • parent:对父组件的引用
  • listeners: (2.3.0+) 一个包含所有父组件为当前组件注册的事件监听器的对象。 data.on 的一个别名。
  • injections: (2.3.0+) 如果使用了 [inject](https://cn.vuejs.org/v2/api/#provide-inject) 选项,则该对象包含了应当被注入的属性。

在添加 functional: true 之后,需要更新我们的锚点标题组件的渲染函数,为其增加 context 参数,并将 this.$slots.default 更新为 context.children,然后将 this.level 更新为 context.props.level


🍂 过渡动画

1.单元素/组件的过渡

Vue 提供了 transition 的封装组件,下列情形可以给任何元素和组件添加进入/离开过渡:
条件渲染 (使用 v-if)、条件展示 (使用 v-show)、动态组件、组件根节点

  1. <!-- html -->
  2. <div id="demo">
  3. <button @click="show = !show">Toggle</button>
  4. <transition name="fade">
  5. <p v-if="show">hello</p>
  6. </transition>
  7. </div>
  1. /* css */
  2. .fade-enter-active,
  3. .fade-leave-active {
  4. transition: all 0.3s ease;
  5. }
  6. .fade-enter,
  7. .fade-leave-to {
  8. transform: translateX(10px);
  9. opacity: 0;
  10. }
  1. // javascript
  2. new Vue({ el: "#demo", data: { show: true } });
  • 过渡的类名
    • v-enter:进入过渡的开始状态。元素被插入前生效,被插入后下一帧移除
    • v-enter-active:进入过渡生效时的状态。在整个进入过渡的阶段中应用
    • v-enter-to:进入过渡的结束状态。元素被插入之后下一帧生效
    • v-leave: 离开过渡的开始状态。离开过渡被触发时生效,下一帧被移除
    • v-leave-active:离开过渡生效时的状态。在整个离开过渡的阶段中应用
    • v-leave-to: 离开过渡的结束状态。离开过渡被触发之后下一帧生效
  • CSS 动画
    • 可以设置不同的进入和离开动画
    • 设置持续时间和动画函数
  1. .bounce-enter-active {
  2. animation: bounce-in 0.5s;
  3. }
  4. .bounce-leave-active {
  5. animation: bounce-in 0.5s reverse;
  6. }
  7. @keyframes bounce-in {
  8. 0% {
  9. transform: scale(0);
  10. }
  11. 50% {
  12. transform: scale(1.5);
  13. }
  14. 100% {
  15. transform: scale(1);
  16. }
  17. }
  • 自定义过渡类名:优先级高于普通类名,可与其他第三方 CSS 动画库结合使用
  1. <link
  2. href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1"
  3. rel="stylesheet"
  4. type="text/css"
  5. />
  6. <div id="example-3">
  7. <button @click="show = !show">Toggle render</button>
  8. <transition
  9. name="custom-classes-transition"
  10. enter-active-class="animated tada"
  11. leave-active-class="animated bounceOutRight"
  12. >
  13. <p v-if="show">hello</p>
  14. </transition>
  15. </div>
  • 显性的过渡持续时间:
    <transition :duration="{ enter: 500, leave: 800 }">...</transition>
  • javascript 钩子
  1. <!-- 钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。
  2. 当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调,同时
  3. 推荐元素添加 v-bind:css="false",可以避免过渡过程中 CSS 的影响。 -->
  4. <transition
  5. @before-enter="beforeEnter"
  6. @enter="enter"
  7. @after-enter="afterEnter"
  8. v-bind:css="false"
  9. >
  10. </transition>
  1. // ...
  2. methods: {
  3. beforeEnter(el) {
  4. el.style.transform = 'translate(0,0)'
  5. },
  6. enter(el, done) {
  7. el.offsetWidth;
  8. el.style.transform = 'translate(300px,450px)';
  9. el.style.transition = 'all .5s ease';
  10. done();
  11. },
  12. afterEnter(el) {
  13. this.flag = false;
  14. }
  15. }

2.初始渲染的过渡:<transition appear><!-- .. --></transition>

3.多个元素的过渡(过渡模式)

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入
  1. <transition name="fade" mode="out-in">
  2. <button v-bind:key="docState">{{ buttonMessage }}</button>
  3. </transition>
  1. // ...
  2. computed: {
  3. buttonMessage: function () {
  4. switch (this.docState) {
  5. case 'saved': return 'Edit'
  6. case 'edited': return 'Save'
  7. case 'editing': return 'Cancel'
  8. }
  9. }
  10. }

4.多个组件的过渡

使用动态组件:
<transition name="component-fade" mode="out-in"><keep-alive><component :is='flag'></component></keep-alive></transition>

5.列表过渡

  • 使用<transition-group>组件特点
    • 以一个真实元素呈现:默认为一个<span>。可通过tag特性更换
    • 过渡模式不可用,因为我们不再相互切换特有的元素
    • 内部元素总是需要提供唯一的 key 属性值
    • CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身
  • 列表的进入/离开过渡
  1. <transition-group appear tag="ul">
  2. <li v-for="(item,i) in dataList" :key="item.id" @click="del(i)">
  3. {{item.id}}---{{item.task}}
  4. </li>
  5. </transition-group>
  6. <!-- 但当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,
  7. 而不是平滑过渡。使用v-move与v-leave-active可以解决:
  8. .v-move{transition:all .7s ease;},.v-leave-active{position:absolute;} -->
  • 列表的排序过渡
    • v-move 特性,在元素改变定位时应用,设置过渡的切换时机和过渡曲线
      • 结合 FLIP 简单的动画队列,将元素从之前的位置平滑过渡新的位置
  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
  2. <style>
  3. .flip-list-move {
  4. transition: transform 1s;
  5. }
  6. </style>
  7. <div id="flip-list-demo" class="demo">
  8. <button v-on:click="shuffle">Shuffle</button>
  9. <transition-group name="flip-list" tag="ul">
  10. <li v-for="item in items" :key="item">{{ item }}</li>
  11. </transition-group>
  12. </div>
  1. new Vue({
  2. el: "#flip-list-demo",
  3. data: { items: [1, 2, 3, 4, 5, 6, 7, 8, 9] },
  4. methods: {
  5. shuffle: function() {
  6. this.items = _.shuffle(this.items);
  7. }
  8. }
  9. });
  • 列表的交错过渡

6.可复用的过渡:<transition>/<transition-group>作为根组件

7.动态过渡

动态过渡最基本的例子是通过 name 特性来绑定动态值。

最终方案是组件通过接受 props 来动态修改之前的过渡。

  1. <transition v-bind:name="transitionName">
  2. <!-- ... -->
  3. </transition>

8.状态过渡

  • 状态动画与侦听器:通过侦听器我们能监听到任何数值属性的数值更新
    • 数字值:可用 GreenSock 动画平台中的补间动画工具 TweenMax 实现
    • color 值:Tween.js 和 Color.js 实现
  • 动态状态过渡
    • SVG 节点的位置
  • 把过渡放到组件里

🖖 Vue Router

1.起步

  • 路由导航 router-link ;路由匹配组件渲染 router-view

    1. <!-- 导航:通过传入 `to` 属性指定链接. <router-link> 默认渲染成`<a>` -->
    2. <!-- 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active -->
    3. <router-link to="/foo">Go to Foo</router-link>
    4. <router-link to="/bar">Go to Bar</router-link>
    5. <!-- 路由出口——路由匹配到的组件将渲染在这里 -->
    6. <router-view></router-view>
  • 将组件 (components) 映射到路由 (routes),告诉 Vue Router 在哪里渲染它们

    1. // 1.定义组件,可以从其他文件 import 进来
    2. const Foo = { template: "<div>foo</div>" };
    3. const Bar = { template: "<div>bar</div>" };
    4. // 2.定义路由(每个路由应该映射一个组件)
    5. const routes = [
    6. { path: "/foo", component: Foo },
    7. { path: "/bar", component: Bar }
    8. ];
    9. // 3. 创建 router 实例,然后传 `routes` 配置
    10. const router = new VueRouter({
    11. routes // (缩写) 相当于 routes: routes
    12. });
    13. // 4. 创建和挂载根实例
    14. const app = new Vue({
    15. router
    16. }).$mount("#app");
  • 注入路由器后在任何组件内通过 **this.$router** 访问路由器,通过 **this.$route** 访问当前路由

  1. export default {
  2. computed: {
  3. username() {
  4. // 我们很快就会看到 `params` 是什么
  5. return this.$route.params.username
  6. }
  7. },
  8. methods: {
  9. goBack() {
  10. window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
  11. }
  12. }
  13. }

2.动态路由匹配

  1. //使用场景:把某种模式匹配到的所有路由,全都映射到同个组件
  2. //当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用
  3. const User = {
  4. template: "<div>User {{ $route.params.id }}</div>"
  5. };
  6. const router = new VueRouter({
  7. routes: [
  8. // 动态路径参数以冒号 : 开头
  9. //现在像 /user/foo 和 /user/bar 都将映射到相同的路由
  10. { path: "/user/:id", component: User }
  11. ]
  12. });

3.嵌套路由

  1. // 一个被渲染组件同样可以包含自己的嵌套 <router-view>
  2. const User = {
  3. template: `
  4. <div class="user">
  5. <h2>User {{ $route.params.id }}</h2>
  6. <router-view></router-view>
  7. </div>
  8. `
  9. };
  10. //嵌套出口渲染组件,需在 VueRouter 的参数中使用 children 配置(path不用加/):
  11. const router = new VueRouter({
  12. routes: [
  13. {
  14. path: "/user/:id",
  15. component: User,
  16. children: [
  17. {
  18. // 当 /user/:id/profile 匹配成功,
  19. // UserProfile 会被渲染在 User 的 <router-view> 中
  20. path: "profile",
  21. component: UserProfile
  22. },
  23. { path: "posts", component: UserPosts },
  24. { path: "", component: UserHome }
  25. ]
  26. }
  27. ]
  28. });

4.编程式导航

声明式 编程式
<router-link :to="..."> router.push(...)
如果提供了 path,params 会被忽略,query 并不属于这种情况
<router-link :to="..." replace> router.replace(...)
替换掉当前的 history 记录
router.go(-1)
router.go(n)
在 history 记录中向前或者后退多少步

5.命名路由

  1. //通过一个名称来标识一个路由显得更方便
  2. const router = new VueRouter({
  3. routes: [
  4. {
  5. path: "/user/:userId",
  6. name: "user",
  7. component: User
  8. }
  9. ]
  10. });
  1. <!-- 要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象 -->
  2. <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

6.命名视图

  1. <!-- 界面中拥有多个单独命名的视图。若 router-view 未设置名字,默认为 default -->
  2. <router-view class="view one"></router-view>
  3. <router-view class="view two" name="a"></router-view>
  4. <router-view class="view three" name="b"></router-view>
  1. //同个路由,多个视图就需要多个组件
  2. const router = new VueRouter({
  3. routes: [
  4. {
  5. path: "/",
  6. components: {
  7. default: Foo,
  8. a: Bar,
  9. b: Baz
  10. }
  11. }
  12. ]
  13. });

7.重定向和别名

  • 重定向:/a 重定向到 /b,访问 /a 时,URL 将会被替换成 /b,匹配路由为 /b。
    routes: [{ path: '/a', redirect: '/b' }]
  • 别名:/a 的别名是 /b,访问 /b 时,URL 会保持为 /b,但路由匹配则为 /a。
    routes: [{ path: '/a', component: A, alias: '/b' }]

8.路由组件传参

组件中的 $route 会使其与对应路由形成高度耦合,使用 props 将组件和路由解耦:

  • 布尔模式:如果 props 被设置为 true,route.params 将会被设置为组件属性
  1. const User = {
  2. props: ["id"],
  3. template: "<div>User {{ id }}</div>"
  4. };
  5. const router = new VueRouter({
  6. routes: [
  7. { path: "/user/:id", component: User, props: true },
  8. // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
  9. {
  10. path: "/user/:id",
  11. components: { default: User, sidebar: Sidebar },
  12. props: { default: true, sidebar: false }
  13. }
  14. ]
  15. });
  • 对象模式:props 是一个对象时被按原样设置为组件属性。props 静态时有用
  1. const router = new VueRouter({
  2. routes: [
  3. {
  4. path: "/promotion/from-newsletter",
  5. component: Promotion,
  6. props: { newsletterPopup: false }
  7. }
  8. ]
  9. });
  • 函数模式:创建一个函数返回 props。将参数转换成另一种类型,静态值与基于路由的值结合等
  1. //URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件
  2. const router = new VueRouter({
  3. routes: [
  4. {
  5. path: "/search",
  6. component: SearchUser,
  7. props: route => ({ query: route.query.q })
  8. }
  9. ]
  10. });
  11. //请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。
  12. //如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。

9.路由模式 (mode)

vue-router 默认 hash 模式。不想要很丑的 hash,可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

  • hash:使用 URL hash 值来作路由。支持所有浏览器,含不支持 history 的浏览器
    • 作为客户端的一种状态,当向服务器端发出请求时,hash 部分不会被发送;
    • hash 值的改变会在浏览器的访问历史中增加一个记录,可控制 hash 的切换;
  • history:依赖 HTML5 History API 和服务器配置
    • window.history.pushState(null, null, path)新增一个历史记录
    • window.history.replaceState(null, null, path)替换当前的历史记录
  • abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。若发现无浏览器的 API,路由会自动强制进入该模式
  1. const router = new VueRouter({
  2. mode: 'history',
  3. routes: [...]
  4. })
  5. //需要后台配置支持,要在服务端增加一个覆盖所有情况的候选资源:
  6. //如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面

10.导航守卫

导航:表示路由正在发生改变。可通过路由来进行一些操作如登录权限验证,当用户满足条件时进入导航,否则就取消跳转,到登录页面让其登录
导航守卫:在路由发生变化时作出相应的判断,判断用户可否去到其想去往的页面的路由

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。参数或查询的改变并不会触发进入/离开的导航守卫。

  • 全局守卫:用来监测所有的路由,代码写在路由页面(router.js)

    1. //to和from分别是将要进入和离开,通过this.$route获取到的路由对象
    2. //next: Function 该参数是函数,且必须调用,否则不能进入路由(页面空白)
    3. //next() 进入该路由; next(false) 取消进入路由;
    4. //next('/') 或 next({ path: '/' }): 跳转到一个不同的地址
    5. const router = new VueRouter({ ... })
    6. router.beforeEach((to, from, next) => {next();});
    7. router.beforeResolve((to, from, next) => {next();});
    8. router.afterEach((to, from) => {console.log('afterEach 全局后置钩子');});
    • 全局前置守卫:router.beforeEach注册,进入路由之前被调用
    • 全局解析守卫:router.beforeResolve注册,beforeRouteEnter 调用后调用
    • 全局后置钩子:router.afterEach注册,进入路由之后被调用
  • 路由独享守卫:路由配置上直接定义beforeEnter守卫

    1. const router = new VueRouter({
    2. routes: [
    3. {
    4. path: "/foo",
    5. component: Foo,
    6. beforeEnter: (to, from, next) => {
    7. // 参数用法一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
    8. }
    9. }
    10. ]
    11. });
  • 组件内的守卫

    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave
      1. const Foo = {
      2. template: `...`,
      3. beforeRouteEnter(to, from, next) {
      4. // 不!能!获取组件实例 `this`,因为守卫执行前,组件实例还没被创建
      5. // 在渲染该组件的对应路由被 confirm 前调用
      6. },
      7. beforeRouteUpdate(to, from, next) {
      8. // 在当前路由改变,但是该组件被复用时调用,可以访问组件实例 `this`
      9. // 举例来说,带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 间跳转时
      10. // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。钩子此时被调用。
      11. },
      12. beforeRouteLeave(to, from, next) {
      13. // 导航离开该组件的对应路由时调用,可以访问组件实例 `this`
      14. // 常用来禁止用户在还未保存修改前突然离开
      15. const answer = window.confirm("Do you really want to leave?");
      16. if (answer) {
      17. next();
      18. } else {
      19. next(false);
      20. }
      21. }
      22. };
  • 完整的导航解析流程

    • 触发进入其他路由
    • beforeRouteLeave:在失活的组件里调用离开守卫,可用来做保存拦截
    • beforeEach:调用全局前置守卫,可用于登录验证、全局路由 loading 等
    • beforeRouteUpdate:在重用的组件里调用 beforeRouteUpdate 守卫
    • beforeEnter:在路由配置里调用路由独享守卫
    • 解析异步路由组件
    • beforeRouteEnter:在被激活的组件里调用 beforeRouteEnter
    • beforeResolve:调用全局解析守卫
    • 导航被确认
    • afterEach:调用全局后置钩子 afterEach
    • 触发 DOM 更新(mounted)
    • 执行 beforeRouteEnter 守卫中传给 next 的回调函数

11.路由元信息(meta)

路由记录,即 routes 配置中的每个路由对象。

路由对象属性 描述
$route.path string,对应当前路由的路径,总是解析为绝对路径
$route.params Object,包含了动态片段和全匹配片段,无路由参数时为空
$route.query Object,表示 URL 查询参数,无查询参数时是个空对象
$route.hash string,当前路由的 hash 值 (带 #) ,无 hash 值则为空
$route.fullPath string,完成解析后的 URL,含查询参数和 hash 完整路径
$route.matched Array,包含当前路由的所有嵌套路径片段的路由记录
  1. //配置 meta 字段
  2. const router = new VueRouter({
  3. routes: [
  4. // 下面的对象就是路由记录
  5. {
  6. path: "/foo",
  7. component: Foo,
  8. children: [
  9. // 这也是个路由记录
  10. {
  11. path: "bar",
  12. component: Bar,
  13. // a meta field
  14. meta: { requiresAuth: true }
  15. }
  16. ]
  17. }
  18. ]
  19. });
  20. //全局导航守卫中检查元字段:
  21. router.beforeEach((to, from, next) => {
  22. if (to.matched.some(record => record.meta.requiresAuth)) {
  23. // this route requires auth, check if logged in
  24. // if not, redirect to login page.
  25. if (!auth.loggedIn()) {
  26. next({
  27. path: "/login",
  28. query: { redirect: to.fullPath }
  29. });
  30. } else {
  31. next();
  32. }
  33. } else {
  34. next(); // 确保一定要调用 next()
  35. }
  36. });