前端追梦人Vue教程

1. Vue概述及入门

1.1 框架的渐进式

声明式渲染->组件系统->客户端路由->集中状态管理->项目构建

1.2 Vue的基本使用

// Vue也是把数据填充到页面的标签里
var vm = new Vue({
  el: '#app',   // 元素的挂载位置(CSS选择器或者DOM元素)
  data: {       // 模型数据(值是一个对象)
    msg: 'hello vue'
  }
});
<!-- 插值表达式, 支持简单的表达式(只要符合JS的表达式规则就可以支持),计算逻辑 -->
<div id="app">
	{{msg}}
  {{1+2}}
</div>

Vue语法->原生语法由Vue框架来实现

1.3 模板语法

模板+数据->前端渲染->静态的HTML内容

前端渲染方式:

  1. 原生js拼接字符串
  2. 使用前端模板引擎
  3. 使用Vue的模板语法

1.3.1 插值表达式

使用用双大括号在HTML中嵌入数据表达式

1.3.2 指令

什么是指令? 以v-开头的自定义属性

什么是自定义属性? data+[属性名]

指令的格式? 以v-开始(比如v-cloak)

  1. v-cloak

解决插值表达式的闪动问题 原理:先通过样式隐藏内容,然后在内存中进行值的替换,替换好后再显示最终的结果

提供样式

[v-cloak] {
  display: none;
}

使用

<div v-cloak>{{msg}}</div>
  1. 数据绑定指令
    v-text 没有闪动问题, 效果同插值表达式,比插值表达式更加简洁

v-html填充HTML片段,容易导致跨站脚本攻击(XSS),原则: 本网站内部数据可以使用,来自第三方的数据不推荐使用
v-pre填充原始信息,跳过编译过程

<div v-pre>
  {{Hello}}
</div>
  1. 数据响应式
    数据的变化导致页面内容的变化
    v-once只编译一次,显示一次之后不再具有响应式的功能,如果显示的信息后续不需要修改,使用v-once可以提高性能(不需要vue监听这个属性)

  2. 双向数据绑定

    v-model

    <input v-model="username" />
    

    修改页面影响数据,数据变化会导致页面内容的变化

    MVVM(本质思想:分而治之)
    ① M(model) 数据

    ②V(View) 页面展示
    ③VM(View-Model) 实现控制逻辑,关联数据和页面(从模型导视图使用事件监听(DOM Listeners), 从模型到视图使用数据绑定(Data Bindings))

image-20200706112253398

1.3.3 事件绑定

  1. v-on:事件名
  2. @click(简写形式)

函数调用方式两种

  1. v-on:click='say'
  2. v-on:click='say("hi", $event)'
    这种方式可以传递参数,但是需要使用$event来传递事件对象

事件修饰符

v-on:click.stop='handle' 组织冒泡 event.stopPropgation()

v-on:click:prevent='handle' 组织默认行为 event.preventDefault()

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止单击事件继续传播 -->
<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:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

按键修饰符(用于键盘事件中进行事件过滤)

Vue 允许为 v-on 在监听键盘事件时添加按键修饰符

<!-- 只有在 `key``Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

// 有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,这些内置的别名应该是首选

// 还可以通过全局 config.keyCodes 对象自定义按键修饰符别名

// 也可以自定义按键修饰符
Vue.config.keyCodes.f1 = 112(来自于事件对象的keycode)

也可以直接使用keycode
v-on:keyup.13 = "onKeyup"

系统修饰符

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>

鼠标按钮修饰符

  • .left
  • .right
  • .middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

1.3.4 属性绑定

使用v-bind:属性名='属性值'指令来进行属性绑定,可以简写为:bind

v-model原理

使用v-bind和v-on, 事件触发时候通过事件对象拿到最新值并对底层数据进行更新

<input type="text" v-bind:value="msg" v-on:input="handle"/>
<!-- v-on:input="msg = $event.target.value" -->
<script>
	...
  handle(){
    this.msg = event.target.value
  }
  ...
</script>

1.3.5 样式绑定

class样式绑定

<!--1. 对象语法 -->
<div :class="{'active': isActive, 'error': isError}"></div>  <!-- isActive控制active class的有无-->
<!-- 2.数组语法 -->
<div :class="['active-class', 'error-class']"></div>
<div :class=[activeClass, errorClass]></div>

<script>
 new Vue({
   ...
   data:{
     activeClass: "active",
     errorClass: "error"
   },
   ...
 });
</script>
  1. 对象绑定和数组绑定可以结合使用

    <div :class="['active', 'error', { 'test': isTest}]"></div>
    
  2. class绑定的值可以做模板简化

    将数组或者对象提取到data中

    new Vue({
      el: "#app",
      arrClasses: ['active', 'error'],
      objClasses: {
        active: true,
        error: false
      }
    });
    
  3. 默认的class如何处理

    <style>
      .base {
        font-size: 14px;
      }
    </style>
    <div class="base" :class="objClasses">
      {{msg}}
    </div>
    <!--
    默认类被保留
    -->
    

style内联样式绑定

对象绑定

<div :style="{border: borderStyle, width: widthStyle, height: heightStyle}">
  {{msg}}
</div>

<script>
	new Vue({
    ...
    data(){
    	return {
    		borderStyle: '1px solid green',
        widthStyle: '200px',
    		heightStyle: "200px"
  		}
  },
    ...
  });
</script>
<!--也可以单独将style绑定的对象提取到data中以简化模板  -->

也可以进行数据绑定绑定多个样式对象(后面对象的属性会覆盖前面对象的相同属性)

<div v-bind:style="[objStyle, anotherObjStyle]">
  {{msg}}
</div>

1.3.6 分支循环结构

分支结构

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

<h1 v-if="awesome">Vue is awesome!</h1>

也可以用 v-else 添加一个“else 块”:

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

也可以使用v-else-if添加多个判断分支

可以在<template>元素使用v-if条件渲染分组

用key管理可以复用的分组

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address">
</template>

那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,`` 不会被替换掉——仅仅是替换了它的 placeholder

这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key attribute 即可:

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

现在,每次切换时,输入框都将被重新渲染。

循环结构

使用v-for实现循环结构

<ul id="example-1">
  <li v-for="(item, index) in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>

key帮助vue区分元素,从而提高性能, 因此推荐遍历时候都指定key

也可以遍历对象

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

v-if和v-for结合使用

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 `) 上。如:

<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>

1.3.7 v-show

v-show控制元素是否显示(已经渲染到了页面,通过修改样式display: none; display:block;来切换显示隐藏)

v-if控制是否渲染: Falsy不会渲染到页面上

如果频繁显示隐藏的元素推荐使用v-show

如果渲染后就不需要显示隐藏了推荐使用v-if

1.3.8 常用特性

表单输入绑定

我们可以使用v-model在表单input,textarea,select等表单元素上创建双向数据绑定,Vue会根据表单控件的类型自动选取正确的方法来更新元素v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

表单修饰符

.lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步

.number如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">

.trim如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

自定义组件的v-model双向绑定

V-model原理: 通过子组件中$emit方法来派发input事件,父组件监听input事件中传递的value值,并存储在父组件data中,然后父组件再通过prop形式传递给子组件的value值,在子组件中绑定inputvalue属性即可。

子组件传值

子组件需要一个input标签,这个input标签需要绑定input事件,$emit触发父组件的input事件,通过这种方法子组件传递值给父组件

<input type="text" @input="$emit('input', $event.target.value)">

父组件监听input事件,然后将事件携带的input输入的值传入到data1状态中

<my-comp @input="value = $event"></my-comp>

<script>
	export default {
    data(){
      return {
        value: 'initial value'
      }
    }
  }
</script>

至此子组件监听input事件,用户输入字符串即可被父组件https://segmentfault.com/a/1190000018893494

将原生事件绑定到组件

你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on.native 修饰符:

<base-input v-on:focus.native="onFocus"></base-input>

在有的时候这是很有用的,不过在你尝试监听一个类似 的非常特定的元素时,这并不是个好主意。比如上述 组件可能做了如下重构,所以根元素实际上是一个 <label> 元素:

<label>
  {{ label }}
  <input
    v-bind="$attrs"
    v-bind:value="value"
    v-on:input="$emit('input', $event.target.value)"
  >
</label>

这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。

为了解决这个问题,Vue 提供了一个 $listeners property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

有了这个 $listeners property,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 `` 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将所有的对象合并为一个新对象
      return Object.assign({},
        // 我们从父级添加所有的监听器
        this.$listeners,
        // 然后我们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工作
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

现在 组件是一个**完全透明的包裹器**了,也就是说它可以完全像一个普通的 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。

.sync修饰符

2.3.0+ 新增

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。

这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

this.$emit('update:title', newTitle)

然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:

<text-document v-bind:title.sync="doc.title"></text-document>

插槽

Vue 实现了一套内容分发的 API,<slot> 元素作为承载分发内容的出口。

编译作用域

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

<navigation-link url="/profile">
  Logged in as {{ user.name }}
</navigation-link>
该插槽跟模板的其它地方一样可以访问相同的实例 property (也就是相同的“作用域”),而不能访问 <navigation-link> 的作用域。例如 url 是访问不到的:

后备内容

我们可能希望这个 <button> 内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为后备内容,我们可以将它放在 <slot> 标签内:

<button type="submit">
  <slot>Submit</slot>
</button>

具名插槽

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个 元素上使用v-slot指令(2.6.0起废弃slot属性(slot="header", slot="footer"),使用v-slot指令),并以v-slot` 的参数的形式提供其名称:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

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

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

现在 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有v-slot 中的内容都会被视为默认插槽的内容。

然而,如果你希望更明确一些,仍然可以在一个 ` 中包裹默认插槽的内容:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

作用域插槽

自 2.6.0 起有所更新。已废弃的使用 slot-scope attribute 的语法在这里

有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 `` 组件:

<span>
  <slot>{{ user.lastName }}</slot>
</span>

我们可能想换掉备用内容,用名而非姓来显示。如下:

<current-user>
  {{ user.firstName }}
</current-user>

然而上述代码不会正常工作,因为只有 `` 组件可以访问到 user 而我们提供的内容是在父级渲染的。

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 `` 元素的一个 attribute 绑定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

绑定在 `` 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

独占默认插槽的缩写语法

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:

<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:

<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</current-user>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 ` 的语法:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>

解构插槽Prop

作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:

function (slotProps) {
  // 插槽内容
}

这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:

<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user 重命名为 person

<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:

<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

动态插槽名

动态指令参数也可以用在 v-slot 上,来定义动态的插槽名:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

具名插槽的缩写

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

<base-layout>
  <template #header>
    <h1>Here might be a page title</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>

然而,和其它指令一样,该缩写只在其有参数的时候才可用。这意味着以下语法是无效的:

<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
  {{ user.firstName }}
</current-user>

如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:

<current-user #default="{ user }">
  {{ user.firstName }}
</current-user>

废弃语法

自 2.6.0 起被废弃。新推荐的语法请查阅这里

v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slotslot-scope attribute 的 API 替代方案。v-slot 完整的由来参见这份 RFC。在接下来所有的 2.x 版本中 slotslot-scope attribute 仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。

  • 带有slot属性的具名插槽在 上使用特殊的slot` attribute,可以将内容从父级传给具名插槽 (把这里提到过的 `` 组件作为示例):

    <base-layout>
      <template slot="header">
        <h1>Here might be a page title</h1>
      </template>
    
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
    
      <template slot="footer">
        <p>Here's some contact info</p>
      </template>
    </base-layout>
    

    或者直接把 slot attribute 用在一个普通元素上:

    <base-layout>
      <h1 slot="header">Here might be a page title</h1>
    
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
    
      <p slot="footer">Here's some contact info</p>
    </base-layout>
    

    这里其实还有一个未命名插槽,也就是默认插槽,捕获所有未被匹配的内容。上述两个示例的 HTML 渲染结果均为:

    <div class="container">
      <header>
        <h1>Here might be a page title</h1>
      </header>
      <main>
        <p>A paragraph for the main content.</p>
        <p>And another one.</p>
      </main>
      <footer>
        <p>Here's some contact info</p>
      </footer>
    </div>
    
  • 带有slot-scope属性的作用域插槽自 2.6.0 起被废弃。新推荐的语法请查阅这里

    上使用特殊的slot-scope` attribute,可以接收传递给插槽的 prop (把这里提到过的 `` 组件作为示例):

    <slot-example>
      <template slot="default" slot-scope="slotProps">
        {{ slotProps.msg }}
      </template>
    </slot-example>
    

    这里的 slot-scope 声明了被接收的 prop 对象会作为 slotProps 变量存在于 作用域中。你可以像命名 JavaScript 函数参数一样随意命名slotProps`。

    这里的 slot="default" 可以被忽略为隐性写法:

    <slot-example>
      <template slot-scope="slotProps">
        {{ slotProps.msg }}
      </template>
    </slot-example>
    

    slot-scope attribute 也可以直接用于非 ` 元素 (包括组件):

    <slot-example>
      <span slot-scope="slotProps">
        {{ slotProps.msg }}
      </span>
    </slot-example>
    

    slot-scope 的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript 表达式。这意味着在支持的环境下 (单文件组件现代浏览器),你也可以在表达式中使用 ES2015 解构,如下:

    <slot-example>
      <span slot-scope="{ msg }">
        {{ msg }}
      </span>
    </slot-example>
    

    使用这里描述过的 `` 作为示例,与它等价的使用 slot-scope 的代码是:

    <todo-list v-bind:todos="todos">
      <template slot="todo" slot-scope="{ todo }">
        <span v-if="todo.isComplete"></span>
        {{ todo.text }}
      </template>
    </todo-list>
    

渲染函数&JSX

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子节点数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

虚拟DOM

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:

return createElement('h1', this.blogTitle)

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

createElement接受的参数

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

数据对象可以接收的属性

{
  // 与 `v-bind:class` 的 API 相同,
  // 接受一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML attribute
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM property
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器在 `on` 内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层 property
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}

函数式组件

我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。一个函数式组件就像这样:

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
}) 	
注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。

当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。

在 2.5.0 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明:

<template functional>
</template>

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

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

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

自定义指令

Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

例如元素插入时自动获取焦点

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

如果想注册局部指令,组件中也接受一个 directives 的选项:

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

钩子函数

bind 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新

componentUpdated指令所在组件的 VNode 及其子 VNode 全部更新后调用

unbind只调用一次,指令与元素解绑时调用

钩子函数的参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。

  • binding
    

    :一个对象,包含以下 property:

    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。

  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

动态指令参数

v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

传入对象字面量作为参数值

<div v-demo="{ color: 'white', text: 'hello!' }"></div>

计算属性

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作 property vm.reversedMessage 的 getter 函数:

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

计算属性的setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

过滤器

可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!--`v-bind`-->
<div v-bind:id="rawId | formatId"></div>

你可以在一个组件的选项中定义本地的过滤器:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

也可以定义全局过滤器(在Vue实例创建之前)

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})
过滤器可以串联:

{{ message | filterA | filterB }}

过滤器是 JavaScript 函数,因此可以接收参数:

{{ message | filterA('arg1', arg2) }}

侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

除了 watch 选项之外,您还可以使用命令式的 vm.$watch API

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    }
  },
  created: function () {
    // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
    // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
    // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
    // 请参考:https://lodash.com/docs#debounce
    this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
  },
  methods: {
    getAnswer: function () {
      if (this.question.indexOf('?') === -1) {
        this.answer = 'Questions usually contain a question mark. ;-)'
        return
      }
      this.answer = 'Thinking...'
      var vm = this
      axios.get('https://yesno.wtf/api')
        .then(function (response) {
          vm.answer = _.capitalize(response.data.answer)
        })
        .catch(function (error) {
          vm.answer = 'Error! Could not reach the API. ' + error
        })
    }
  }
})
</script>

生命周期

vue每个组件都是独立的,每个组件都有一个属于它的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁,这就是一个组件所谓的生命周期。在组件中具体的方法有:

beforeCreate

created

beforeMount

mounted

(

​ beforeUpdate

​ updated

)

beforeDestroy

destroyed

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBAOfu0G-1604978523231)(/img/lifecycle-vue.png)]

进入/离开&列表过渡

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
.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 被删除),在过渡/动画完成之后移除。

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 ,则 `v-` 是这些类名的默认前缀。如果你使用了,那么 v-enter 会替换为 my-transition-enter

v-enter-activev-leave-active 可以控制进入/离开过渡的不同的缓和曲线,在下面章节会有个示例说明。

<div id="example-1">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#example-1',
  data: {
    show: true
  }
})
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
  transform: translateX(10px);
  opacity: 0;
}

我们可以通过以下 attribute 来自定义过渡类名:

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

显示指定过渡时间

<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

JavaScript钩子函数

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>

可以使用上述钩子函数配合Velocity.js实现JS动画,如果只使用JS动画(设置了v-bind:css="false"),Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。但是enterleave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。

一个使用 Velocity.js 的简单例子:

<!--
Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-bind:css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>
new Vue({
  el: '#example-4',
  data: {
    show: false
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.transformOrigin = 'left'
    },
    enter: function (el, done) {
      Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
      Velocity(el, { fontSize: '1em' }, { complete: done })
    },
    leave: function (el, done) {
      Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
      Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
      Velocity(el, {
        rotateZ: '45deg',
        translateY: '30px',
        translateX: '30px',
        opacity: 0
      }, { complete: done })
    }
  }
})

初始渲染过渡

可以通过 appear attribute 设置节点在初始渲染的过渡

<transition appear>
  <!-- ... -->
</transition>

这里默认和进入/离开过渡一样,同样也可以自定义 CSS 类名。

<transition
  appear
  appear-class="custom-appear-class"
  appear-to-class="custom-appear-to-class" (2.1.8+)
  appear-active-class="custom-appear-active-class"
>
  <!-- ... -->
</transition>

自定义 JavaScript 钩子:

<transition
  appear
  v-on:before-appear="customBeforeAppearHook"
  v-on:appear="customAppearHook"
  v-on:after-appear="customAfterAppearHook"
  v-on:appear-cancelled="customAppearCancelledHook"
>
  <!-- ... -->
</transition>

过渡模式

<transition> 的默认行为 - 进入和离开同时发生

同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开。
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入。

多组件过渡

可以使用动态组件来实现

<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})
.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
  opacity: 0;
}

列表过渡

进入离开过渡

<div id="list-demo" class="demo">
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list" tag="p">
    <span v-for="item in items" v-bind:key="item" class="list-item">
      {{ item }}
    </span>
  </transition-group>
</div>
new Vue({
  el: '#list-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
})
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}

列表排序过渡

<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move class,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name attribute 来自定义前缀,也可以通过 move-class attribute 手动设置。

v-move 对于设置过渡的切换时机和过渡曲线非常有用,你会看到如下的例子:

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>

<div id="flip-list-demo" class="demo">
  <button v-on:click="shuffle">Shuffle</button>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" v-bind:key="item">
      {{ item }}
    </li>
  </transition-group>
</div>
new Vue({
  el: '#flip-list-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9]
  },
  methods: {
    shuffle: function () {
      this.items = _.shuffle(this.items)
    }
  }
})
.flip-list-move {
  transition: transform 1s;
}

这个看起来很神奇,内部的实现,Vue 使用了一个叫 FLIP 简单的动画队列
使用 transforms 将元素从之前的位置平滑过渡新的位置。

我们将之前实现的例子和这个技术结合,使我们列表的一切变动都会有动画过渡。

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>

<div id="list-complete-demo" class="demo">
  <button v-on:click="shuffle">Shuffle</button>
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list-complete" tag="p">
    <span
      v-for="item in items"
      v-bind:key="item"
      class="list-complete-item"
    >
      {{ item }}
    </span>
  </transition-group>
</div>
new Vue({
  el: '#list-complete-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
    shuffle: function () {
      this.items = _.shuffle(this.items)
    }
  }
})
.list-complete-item {
  transition: all 1s;
  display: inline-block;
  margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-to
/* .list-complete-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}
.list-complete-leave-active {
  position: absolute;
}

列表的交错过渡效果

通过 data attribute 与 JavaScript 通信,就可以实现列表的交错过渡:

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="staggered-list-demo">
  <input v-model="query">
  <transition-group
    name="staggered-fade"
    tag="ul"
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
  >
    <li
      v-for="(item, index) in computedList"
      v-bind:key="item.msg"
      v-bind:data-index="index"
    >{{ item.msg }}</li>
  </transition-group>
</div>
new Vue({
  el: '#staggered-list-demo',
  data: {
    query: '',
    list: [
      { msg: 'Bruce Lee' },
      { msg: 'Jackie Chan' },
      { msg: 'Chuck Norris' },
      { msg: 'Jet Li' },
      { msg: 'Kung Fury' }
    ]
  },
  computed: {
    computedList: function () {
      var vm = this
      return this.list.filter(function (item) {
        return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
      })
    }
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.height = 0
    },
    enter: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 1, height: '1.6em' },
          { complete: done }
        )
      }, delay)
    },
    leave: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 0, height: 0 },
          { complete: done }
        )
      }, delay)
    }
  }
})

封装通用过渡组件

Vue.component('my-special-transition', {
  functional: true,
  render: function (createElement, context) {
    var data = {
      props: {
        name: 'very-special-transition',
        mode: 'out-in'
      },
      on: {
        beforeEnter: function (el) {
          // ...
        },
        afterEnter: function (el) {
          // ...
        }
      }
    }
    return createElement('transition', data, context.children)
  }
})

动态过渡

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

当你想用 Vue 的过渡系统来定义的 CSS 过渡/动画在不同过渡间切换会非常有用。

混入

来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。

  • 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先

  • 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

  • 值为对象的选项,例如 methodscomponentsdirectives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

全局混入

混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

2.深入了解组件

组件注册

组件名

vue定义组件名有两种格式
1.kebab-case(短横线分隔命名)

Vue.component('my-component-name',{/* */})
使用的时候:
<my-component-name></my-component-name>

2.驼峰命名

Vue.component('myComponentName',{/* */})
使用的时候:
因为html标签不区分大小写,所以只能使用短横线分隔来使用组件

<my-component-name></my-component-name>

全局注册

Vue.component('my-component-name', {
  // ... 选项 ...
})

局部注册

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

在模块系统中局部注册组件

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

基础组件的自动化全局注册

使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径
  './components',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)

  // 获取组件的 PascalCase 命名
  const componentName = upperFirst(
    camelCase(
      // 获取和目录深度无关的文件名
      fileName
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})

prop

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!--HTML 中是 kebab-case-->
<blog-post post-title="hello!"></blog-post>

Prop类型

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

但是,通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

prop验证

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

类型检查

type 可以是下列原生构造函数中的一个:

String
Number
Boolean
Array
Object
Date
Function
Symbol
额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
你可以使用:

Vue.component('blog-post', {
  props: {
    author: Person
  }
})
来验证 author prop 的值是否是通过 new Person 创建的。

自定义事件

<cus-com @cus-event="onCusEvent"></cus-com>  // 父组件
this.$emit('cus-event', {
  someData: {}
})  // 子组件

动态组件和异步组件

<component v-bind:is="currentTabComponent"></component>

异步组件

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

Vue异步组件处理路由组件加载状态的解决方案

/**
 * 处理路由页面切换时,异步组件加载过渡的处理函数
 * @param {Object} AsyncView 需要加载的组件,如 import('@/components/home/Home.vue')
 * @return {Object} 返回一个promise对象
 */
function lazyLoadView (AsyncView) {
 const AsyncHandler = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: AsyncView,
  // 异步组件加载时使用的组件
  loading: require('@/components/public/RouteLoading.vue').default,
  // 加载失败时使用的组件
  error: require('@/components/public/RouteError.vue').default,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 10000
 });
 return Promise.resolve({
  functional: true,
  render (h, { data, children }) {
   return h(AsyncHandler, data, children);
  }
 });
}

使用

const helloWorld = () => lazyLoadView(import('@/components/helloWorld'))
vue-router中使用
routes: [
  {
    path: '/helloWorld',
    name: 'helloWorld',
    component: helloWorld
  }
]

组件状态缓存

重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive> 元素将其动态组件包裹起来。

<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

访问子组件实例

<base-input ref="usernameInput"></base-input>
现在在你已经定义了这个 ref 的组件里,你可以使用:

this.$refs.usernameInput

依赖注入provide和inject

provide允许我们指定后代组件提供的数据/方法

provide: function () {
  return {
    getMap: this.getMap
  }
}

然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property:

inject: ['getMap']

我们可以把依赖注入看作**“大范围有效的prop”**

程序化的监听事件

  • 通过 $on(eventName, eventHandler) 侦听一个事件
  • 通过 $once(eventName, eventHandler) 一次性侦听一个事件
  • 通过 $off(eventName, eventHandler) 停止侦听一个事件

内联模板和X-Template

当 inline-template 这个特殊的 attribute 出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>
内联模板需要定义在 Vue 所属的 DOM 元素内。

不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 ` 元素来定义模板。

X-Template

另一个定义模板的方式是在一个 `` 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。例如:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

x-template 需要定义在 Vue 所属的 DOM 元素外

强制更新

vm.$forceUpdate();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值