1.组件注册:
组件名:
短横线分割命名(kebab-case):注册时若使用此命名方式则在引用组件时也必须使用此方式。
大驼峰命名(PascalCase):注册时若使用此命名方式则引用时这两种方式均可使用,但直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
全局注册: Vue.component('name',{ ... })
它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。在所有子组件中也是如此。
缺点:全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
局部注册:
首先,通过一个普通的js对象来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
其次,在components选项中定义你想要使用的组件:
new Vue({
el: '#app'
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
对于components对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。如果你希望ComponentA在ComponentB中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
基础组件的自动化全局注册: 如果你使用了 webpack (或在内部使用了 webpack 的Vue CLI 3+),那么就可以使用require.context只全局注册这些非常通用的基础组件。注意: 全局注册的行为必须在根 Vue 实例 (通过new Vue) 创建之前发生。
2.Prop:
大小写问题:
当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。
静态&&动态:
<blog-post title="My journey with Vue"></blog-post>
<blog-post v-bind:title="post.title"></blog-post>
如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的v-bind(取代v-bind:prop-name)。下面的两种写法是等价的:
<blog-post v-bind="post"></blog-post>
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
单向数据流:
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。
这里有两种常见的试图改变一个 prop 的情形:
// 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
// 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
// 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,
// 最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
prop验证:
为了定制 prop 的验证方式,你可以为props中的值提供一个带有验证需求的对象,而不是一个字符串数组。
注意:那些 prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如data、computed等) 在default或validator函数中是不可用的。
替换、合并已有特性:
对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入type="text"就会替换掉type="date"并把它破坏!庆幸的是,class和style特性会稍微智能一些,即两边的值会被合并起来,从而得到最终的值。
禁用特性继承:
如果你不希望组件的根元素继承特性,你可以设置在组件的选项中设置inheritAttrs: false。
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
<base-input
v-model="username"
class="username-input"
placeholder="Enter your username"
></base-input>
3.自定义事件:
事件名:
v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
因此,我们推荐你始终使用 kebab-case 的事件名。
自定义组件的v-model:
一个组件上的v-model默认会利用名为value的 prop 和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value特性用于不同的目的。model选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
将原生事件绑定到组件:
<base-input v-on:focus.native="onFocus"></base-input>
当base-input被重构为一个非input类型的组件时,事件监听会默认的失败。Vue 提供了一个$listeners属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。
有了这个$listeners属性,你就可以配合v-on="$listeners"将所有的事件监听器指向这个组件的某个特定的子元素。对于类似<input>的你希望它也可以配合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>
`
})
.sync修饰符:
把title作为prop传进子组件,添加v-on监听器:
<text-document v-bind:title.sync="doc.title"></text-document>
在子组件中:
this.$emit('update:title', newTitle)。
当我们用一个对象同时设置多个 prop 的时候,也可以将这个.sync修饰符和v-bind配合使用:
<text-document v-bind.sync="doc"></text-document>
这样会把doc对象中的每一个属性 (如title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的v-on监听器。
4.插槽:
Vue 实现了一套内容分发的 API,将元素作为承载分发内容的出口。
具名插槽:
base-layout组件的模板:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
向具名插槽提供内容:
<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>
等价于
<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>
默认插槽:
它会作为所有未匹配到插槽的内容的统一出口。
插槽的默认内容:
在标签的内部制定默认内容。
注意: 父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
作用域插槽:
可从子组件获取数据的可复用的插槽。
todo-list组件模板:
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
通过slot-scope特性从子组件取出数据:
<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
</todo-list>
解构:
<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
5.动态组件&&异步组件:
动态组件:
在一个多标签的界面中使用is特性来切换不同的组件:
<component v-bind:is="currentTabComponent"></component>
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题,可以用一个<keep-alive>元素将其动态组件包裹起来。
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
注:<keep-alive>要求被切换到的组件都有自己的名字,不论是通过组件的name选项还是局部/全局注册。
异步组件:
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会被触发,且会把结果缓存起来供未来重渲染。
一个推荐的做法是将异步组件和webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
当使用局部注册当时候,你也可以直接提供一个返回Promise的函数:
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
6.处理边界的情况:(有几个点没看懂)
访问根实例: this.$root
访问父级组件实例: this.$parent
访问子组件实例或元素: this.$refs
<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput
注:$refs只会在组件渲染完成之后生效,并且它们不是响应式的。
依赖注入:provide和inject
provide:允许我们指定我们想要提供给后代组件的数据/方法。
provide: function () {
return {
getMap: this.getMap
}
}
inject:在任何后代组件里,我们都可以使用inject选项来接收指定的我们想要添加在这个实例上的属性:
inject: ['getMap']
注意:
依赖注入还是有负面影响的。它将你的应用目前的组件组织方式耦合了起来,使重构变得更加困难。同时所提供的属性是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟 href="cn.vuejs.org/v2/guide/co…访问根实例">使用$root做这件事都是不够好的。如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像Vuex这样真正的状态管理方案了。
强制更新: $forceUpdate
href="cn.vuejs.org/v2/guide/co…通过-v-once-创建低开销的静态组件">通过 v-once 创建低开销的静态组件:
大量静态内容。在这种情况下,你可以在根元素上添加v-once特性以确保这些内容只计算一次然后缓存起来。如下:
Vue.component('terms-of-service', {
template: `
<div v-once>
<h1>Terms of Service</h1>
... a lot of static content ...
</div>
`
})