🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 指令 ## 内置指令 | 指令 | 描述 | | --- | --- | | v-text |主要用来更新 textContent,可以等同于 JS 的 text 属性。| | v-html |双大括号的方式会将数据解释为纯文本,而非 HTML。为了输出真正的HTML,可以用 v-html 指令。它等同于 JS 的 innerHtml 属性。| | v-pre |想显示 {{ }} 标签而不进行替换时使用,跳过该元素和它的子元素的编译过程| | v-cloak | 这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。| | v-once |v-once 关联的实例,只会渲染一次。之后的重新渲染,实例及其所有的子节点将被视为静态内容跳过,这可以用于优化更新性能。| | v-if| 实现条件渲染,Vue 会根据表达式的值的真假条件来渲染元素。| | v-else| v-else 是搭配 v-if 使用的,它必须紧跟在 v-if 或者 v-else-if 后面,否则不起作用。| | v-else-if| v-else-if 充当 v-if 的 else-if 块,可以链式的使用多次。可以更加方便的实现 switch 语句。| | v-show |也是用于根据条件展示元素。和 v-if 不同的是,如果 v-if 的值是 false,则这个元素被销毁,不在 dom 中。但是 v-show 的元素会始终被渲染并保存在 dom 中| | v-for |遍历数组 / 对象来进行渲染| | v-bind| 用来动态的绑定一个或者多个特性。没有参数时,可以绑定到一个包含键值对的对象。常用于动态绑定 class 和 style。简写为:【 :】| | v-model |用于在表单上创建双向数据绑定。 v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值。因为它选择 Vue 实例数据做为具体的值。| | v-on| 主要用来监听 dom 事件(或父子组件通信),以便执行一些代码块。表达式可以是一个方法名。简写为:【 @ 】| ## Class 与 Style 的绑定 ⑴ 传递一个对象,实现动态切换 class ```html <div v-bind:class="{ 'active': isActive }"></div> ``` ⑵ 与普通的 class 属性共存 ```html <div class="static" v-bind:class="{ 'active': isActive, 'text-danger': hasError }" ></div> ``` ⑶ 用在组件上时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。 例如,如果你声明了这个组件: ```js Vue.component('my-component', { template: '<p class="foo bar">Hi</p>' }) ``` 然后在使用它的时候添加一些 class: ```js <my-component class="baz boo"></my-component> ``` HTML 将被渲染为: ```html <p class="foo bar baz boo">Hi</p> ``` ⑷ 绑定内联样式 ```html <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> ``` 或者绑定到一个样式对象: ```html <div v-bind:style="styleObject"></div> ``` ```js data: { styleObject: { color: 'red', fontSize: '13px' } } ``` ## 条件渲染 v-if 与 v-show `v-if`是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 `v-if`也是**惰性的**:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,`v-show`就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 一般来说,`v-if`有更高的切换开销,而`v-show`有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用`v-show`较好;如果在运行时条件很少改变,则使用`v-if`较好。 ## 列表渲染 ⑴ 只使用 1 个参数 ```html <ul id="example-1"> <li v-for="item in items"> {{ item.message }} </li> </ul> ``` ```js var example1 = new Vue({ el: '#example-1', data: { items: [ { message: 'Foo' }, { message: 'Bar' } ] } }) ``` ⑵ 使用 2 个参数,第二个参数表示当前项的索引 ```html <ul id="example-2"> <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul> ``` ⑶ 遍历一个对象的属性,第二个参数(可选)为键名,第三个参数(可选)为索引;遍历对象时,是按 Object.keys() 的结果进行遍历的。 ```html <div v-for="(value, name, index) in object"> {{ index }}. {{ name }}: {{ value }} </div> ``` ```js new Vue({ el: '#v-for-object', data: { object: { title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt: '2016-04-10' } } }) ``` ⑷ 需要为每项提供一个唯一的 key 属性,数组下标不行(不合理) ```html <div v-for="item in items" v-bind:key="item.id"> <!-- 内容 --> </div> ``` ⑸ `v-for` 也可以接受整数。在这种情况下,它会把模板重复对应次数 ```html <div> <span v-for="n in 10">{{ n }} </span> </div> ``` ⑹ 数组更新检测 Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括: * `push()` * `pop()` * `shift()` * `unshift()` * `splice()` * `sort()` * `reverse()` Vue**不能**检测以下数组的变动: 1. 当你利用索引直接设置一个数组项时,例如:`vm.items[indexOfItem] = newValue` 2. 当你修改数组的长度时,例如:`vm.items.length = newLength` ```js var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // 不是响应性的 vm.items.length = 2 // 不是响应性的 ``` 为了解决第一类问题,以下两种方式都可以实现和`vm.items[indexOfItem] = newValue`相同的效果,同时也将在响应式系统内触发状态更新: ```js // Vue.set Vue.set(vm.items, indexOfItem, newValue) ``` ```js // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue) ``` 你也可以使用 `vm.$set` 实例方法,该方法是全局方法 `Vue.set` 的一个别名: ```js vm.$set(vm.items, indexOfItem, newValue) ``` 为了解决第二类问题,你可以使用 `splice`: ```js vm.items.splice(newLength) ``` ## 事件处理 事件修饰符 * .stop - 调用 event.stopPropagation()。 * .prevent - 调用 event.preventDefault()。 * .capture - 添加事件侦听器时使用 capture 模式。 * .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。 * .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。 * .native - 监听组件根元素的原生事件。 * .once - 只触发一次回调。 * .left - (2.2.0) 只当点击鼠标左键时触发。 * .right - (2.2.0) 只当点击鼠标右键时触发。 * .middle - (2.2.0) 只当点击鼠标中键时触发。 * .passive - (2.3.0) 对应于 addEventListener 中的 passive 选项 例如: `@click.stop.prevent`:禁止事件冒泡和预设行为,移动端下拉事件经常使用 `@click.prevent`:不处理任何的点击事件,可以给 Toast 的蒙版添加该行为 `@keyup.enter.exact` 或者 `@keyup.13.exact`:keyup 是按下按键抬起后触发 exact 精确要求回车, 不加的话按 shift+enter 也会触发 ```html <!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即元素自身触发的事件先在此处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div> ``` `v-on`用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的 **自定义事件**。 ## 表单输入绑定 `v-model`其实是一种简写: ```html <input v-model="searchText"> 等价于: <input v-bind:value="searchText" v-on:input="searchText = $event.target.value" > ``` `v-model`在内部为不同的输入元素使用不同的属性并抛出不同的事件: * text 和 textarea 元素使用`value`属性和`input`事件; * checkbox 和 radio 使用`checked`属性和`change`事件; * select 字段将`value`作为 prop 并将`change`作为事件。 >[danger]v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。 ***** 当用在组件上时,`v-model`则会这样: ``` html <custom-input v-bind:value="searchText" v-on:input="searchText = $event" ></custom-input> ``` 为了让它正常工作,这个组件内的 \<input\> 必须: • 将其 value 特性绑定到一个名叫 value 的 prop 上 • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出 ```html Vue.component('custom-input', { props: ['value'], template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > ` }) ``` 现在 v-model 就应该可以在这个组件上完美地工作起来了: ```html <custom-input v-model="searchText"></custom-input> ``` ## 自定义指令 ```js // 注册一个全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() } }) ``` 如果想注册局部指令,组件中也接受一个`directives`的选项: ```js directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } } } ``` 然后你可以在模板中任何元素上使用新的`v-focus`属性,如下: ```html <input v-focus> ``` # 计算属性与侦听器 ## computed 对于模板中的复杂逻辑,应该将其设置为计算属性 ```js <div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div> ``` ```js var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split('').reverse().join('') } } }) ``` 当 vm.message 发生改变时,vm.reversedMessage 会自动更新。 调用方法也可以实现同样的效果: ```html <p>Reversed message: "{{ reversedMessage() }}"</p> ``` ```js // 在组件中 methods: { reversedMessage: function () { return this.message.split('').reverse().join('') } } ``` 区别是:**计算属性是基于它们的响应式依赖进行缓存的**。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要`message`还没有发生改变,多次访问`reversedMessage`计算属性会立即返回之前的计算结果,而不必再次执行函数。 ## watch 侦听器 watch 是一种通用的用于观察和响应 Vue 实例上的数据变动的方式,使用方式如下: ```html <div id="demo">{{ fullName }}</div> ``` ```js var vm = new Vue({ el: '#demo', data: { firstName: 'Foo', }, watch: { // 监听的属性和其发生变化后触发的回调 firstName: function (val) { this.fullName = val + ' ' + this.lastName } } }) ``` # 组件 ## 模板 .vue 文件的模板大致是这样的:(将由 vue-loader 进行解析) ```html <template> <div class="component-class"></div> </template> <script> import { } from ... export default { mixins: [], // 混入(组合) components: {}, // 子组件(模板内使用的资源) props: { title: { type: Boolean, default: true} }, // 接口 data () {}, // 本地状态-实例的数据对象 computed: {}, // 本地状态-计算属性 watch: {}, // 侦听属性 methods: { plus: function () { this.a++ } }, // 方法 // 生命周期钩子 created () {}, mounted () {} } </script> <style lang="scss" scoped> @import "../../mixin.scss" </style> ``` ## 组件通信 组件间的通信可分为父子组件通信、兄弟组件通信、跨级组件通信。 **1.父子组件通信** - props 选项:子组件可以通过 props 选项来接收父组件的数据,数据流的传输是单向的。 - $emit 与 v-on:子组件通过调用内置的 $emit 方法并传入事件名称来触发一个事件,父组件使用 v-on 监听子组件实例的任何事件。 - $children 与 $parent(不推荐使用):在子组件中,使用 `this.$parent` 可以直接访问该组件的父实例或组件,父组件也可以通过 `this.$children` 访问它所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。 - ref:父组件可以用特殊的属性 ref 来为子组件(或者DOM)指定一个索引名称。通过`this.$refs.xxx`可以访问到这个子组件。 **2.兄弟组件通信与跨级组件通信** - 中央事件总线 bus:创建一个名为 bus 的空 Vue 实例。其他组件都通过 bus 来发送事件并且监听来自 bus 的事件。 - vuex:如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,就需要使用 vuex,vuex 的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 ## 插槽 1.作用域:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。 ```html <navigation-link url="/profile"> Clicking here will send you to: {{ url }} <!-- 这里的 `url` 会是 undefined,因为 "/profile" 是 传递给 <navigation-link> 的而不是 在 <navigation-link> 组件*内部*定义的。 --> </navigation-link> ``` 2.后备内容:我们可能希望在一个`<button>`内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为后备内容,我们可以将它放在`<slot>`标签内: ```html <button type="submit"> <slot>Submit</slot> </button> ``` 现在当我在一个父级组件中使用 `<submit-button>` 并且不提供任何插槽内容时: ```html <submit-button></submit-button> ``` 后备内容“Submit”将会被渲染: ```html <button type="submit"> Submit </button> ``` ## 内置组件 1.\<component> ```html <!-- 动态组件由 vm 实例的属性值 `componentId` 控制 --> <component :is="componentId"></component> ``` 我们还需要引入使用到的组件,注意是放在 data 选项中: ```js import StaffInfo from '../components/StaffInfo.vue' import LeaveTable from '../components/LeaveTable.vue' import SalaryTable from '../components/SalaryTable.vue' import GateTable from '../components/GateTable.vue' export default { data: function () { return { StaffInfo, LeaveTable, SalaryTable, GateTable } }, ``` 2.\<transition> * 使用 v-show / v-if 动态显示或隐藏元素时,会触发过渡动画 * transition 需要指定 name,并包裹一个含 v-show / v-if 的 div * vue 会为 transition 包裹的 div 动态添加 class,共6种 ![](https://box.kancloud.cn/8c5c8213b9a936271d04562e3b1adae8_1200x600.png) v-enter: 显示之前&emsp; v-enter-to: 显示之后&emsp; v-enter-active: 显示的过程 v-leave: 隐藏之前&emsp; v-leave-to: 隐藏之后&emsp; v-leave-active: 隐藏的过程 注意 transition 的样式必须和包裹的 div 同级(scss) 例如: ```html <transition name="fade"> <div class="popup-bg" @click.stop.prevent="hide()" v-if="popupVisible"></div> <!--灰色的背景图层, 点击后弹窗隐藏. 禁止事件冒泡和预设行为--> </transition> ``` 这是一个淡入淡出的效果: ```css .fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-to, .fade-leave { opacity: 1; } .fade-enter-active, .fade-leave-active { transition: all .3s linear; } ``` 3.\<transition-group> * 不同于`<transition>` ,它会渲染一个真实的 DOM 元素,默认为`span`。你也可以通过 tag 特性更换为其他元素。 * 过渡模式不可用,因为我们不再相互切换特有的元素。 * 内部元素总是需要提供唯一的 key 属性值。 不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用。 像之前的类名一样,可以通过 name 属性来自定义前缀,也可以通过 move-class 属性手动设置。 ```html <transition-group name="list" tag="div" id="item-list"> <div class="item" v-for="(item, index) in items" :key="item.id"></div> </transition-group> ``` ```css .list-move { transition: transform 1s; } .list-leave-active { display: none; } ``` 还需要注意以下事项: - name:用于自动生成 CSS 过渡类名 - tag:这个\<transition-group>组件在DOM中实际以什么样的形式存在,默认渲染为 \<span> - 每个 \<transition-group> 的子节点必须有独立的 key ,动画才能正常工作 可以看下这个 [demo](https://github.com/ChenMingK/epub-Proj/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/%E4%B9%A6%E6%9E%B6%E5%BC%80%E5%8F%91.md) 4.\<keep-alive> `<keep-alive>` 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。`<keep-alive>`是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。 当组件在 `<keep-alive>` 内被切换,它的 `activated` 和 `deactivated` 这两个生命周期钩子函数将会被对应执行。 - Props: - `include`\- 字符串或正则表达式。只有名称匹配的组件会被缓存。 - `exclude`\- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。 - `max`\- 数字。最多可以缓存多少组件实例。 应用场景:避免组件的反复重建和渲染,保存用户状态等。 ```html <!-- 基本 --> <keep-alive> <component :is="view"></component> </keep-alive> <!-- 多个条件判断的子组件 --> <keep-alive> <comp-a v-if="a > 1"></comp-a> <comp-b v-else></comp-b> </keep-alive> <!-- 和 `<transition>` 一起使用 --> <transition> <keep-alive> <component :is="view"></component> </keep-alive> </transition> ``` # 生命周期 生命周期函数就是组件在初始化或者数据更新时会触发的钩子函数。 ![](https://box.kancloud.cn/c7bfd289342941961506e8a3063bf621_1200x3039.png) 下面配合源码来看下生命周期的钩子函数是如何执行的,参考:[Vue 技术揭秘](https://ustbhuangyi.github.io/vue-analysis/components/lifecycle.html#beforeupdate-updated) 源码中最终执行生命周期的函数都是调用`callHook`方法,它的定义在`src/core/instance/lifecycle`中: ```js export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() } ``` `callHook`函数的逻辑很简单,根据传入的字符串`hook`,去拿到`vm.$options[hook]`对应的回调函数数组,然后遍历执行,执行的时候把`vm`作为函数执行的上下文。 各个阶段的生命周期的函数会被合并到`vm.$options`里,并且是一个数组。因此`callhook`函数的功能就是调用某个生命周期钩子注册的所有回调函数。 ## beforeCreate & created `beforeCreate`和`created`函数都是在实例化`Vue`的阶段,在`_init`方法中执行的,它的定义在`src/core/instance/init.js`中: ```js Vue.prototype._init = function (options?: Object) { // ... initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) // 初始化props、data、methods、watch、computed 等属性 initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // ... } ``` 在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能够访问 DOM,一般来说,如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以,如果是需要访问`props`、`data`等数据的话,就需要使用`created`钩子函数。vue-router 和 vuex 的就是利用`beforeCreatd`钩子将自己注入到组件中的。 ## beforeMount & mounted ```js export function mountComponent { callHook(vm, 'beforeMount') // ... if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } } ``` `beforeMount`就是在挂载前执行的,然后开始创建 VNode并替换成真实 DOM,最后执行`mounted`钩子。`vm.$vnode`如果为`null`,则表明这不是一次组件的初始化过程,而是我们通过外部`new Vue`初始化过程,所以直接执行`mounted`钩子了。如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。 ## beforeUpdate & updated `beforeUpdate`和`updated`的钩子函数执行时机都应该是在数据更新的时候 `beforeUpdate`的执行时机是在渲染 Watcher 的`before`函数中: ```js export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { // ... // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) // ... } ``` 注意这里有个判断,也就是在组件已经`mounted`之后,才会去调用这个钩子函数。 `update`的执行时机是在`flushSchedulerQueue`函数调用的时候,它的定义在`src/core/observer/scheduler.js`中: ```js function flushSchedulerQueue () { // ... // 获取到 updatedQueue callUpdatedHooks(updatedQueue) } function callUpdatedHooks (queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm if (vm._watcher === watcher && vm._isMounted) { callHook(vm, 'updated') } } } ``` `updatedQueue`是更新了的`wathcer`数组,那么在`callUpdatedHooks`函数中,它对这些数组做遍历,只有满足当前`watcher`为`vm._watcher`以及组件已经`mounted`这两个条件,才会执行`updated`钩子函数。 ## beforeDestroy & destroyed `beforeDestroy`和`destroyed`钩子函数的执行时机在组件销毁的阶段 最终会调用`$destroy`方法,它的定义在`src/core/instance/lifecycle.js`中: ```js Vue.prototype.$destroy = function() { // ... callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // remove self from parent const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // call the last hook... vm._isDestroyed = true // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null) // fire destroyed hook callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } // release circular reference (##6759) if (vm.$vnode) { vm.$vnode.parent = null } } ``` `beforeDestroy`钩子函数的执行时机是在`$destroy`函数执行最开始的地方,接着执行了一系列的销毁动作,包括从`parent`的`$children`中删掉自身,删除`watcher`,当前渲染的 VNode 执行销毁钩子函数等,执行完毕后再调用`destroy`钩子函数。 在`$destroy`的执行过程中,它又会执行`vm.__patch__(vm._vnode, null)`触发它子组件的销毁钩子函数,这样一层层的递归调用,所以`destroy`钩子函数执行顺序是先子后父,和`mounted`过程一样。 ## activated & deactivated `activated`和`deactivated`钩子函数是专门为`keep-alive`组件定制的钩子 当组件在`<keep-alive>`内被切换,它的`activated`和`deactivated`这两个生命周期钩子函数将会被对应执行。