基本使用
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容11111</p>
<p>我是内容22222</p>
</div>`
})
// 2.注册组件(全局组件,可以在多个Vue的实例下使用)
// 语法:Vue.component('组件的标签名',构造器名)
Vue.component('my-cpn', cpnC)
</script>
局部组件
<div id="app">
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>`
})
const app = new Vue({
el: '#app',
data: {
message: '你好'
},
// 在 vue 实例注册的组件是局部组件
components: {
// cpn:使用组件时的标签名
cpn: cpnC
}
})
const app2 = new Vue({
el: '#app2',
})
</script>
父子组件
<div id="app">
<cpn2></cpn2>
</div>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容1</p>
</div>`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容2</p>
<cpn1></cpn1>
</div>`,
components: {
// 注册组件
cpn1: cpnC1
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好'
},
components: {
cpn2: cpnC2,
}
})
</script>
简写注册
省去了调用 Vue.extend()
的步骤,实际上原理还是调用了 Vue.extend()
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script>
// 全局注册
Vue.component('cpn1', {
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容1</p>
</div>`
})
const app = new Vue({
el: '#app',
data: {
message: '你好'
},
// 局部注册
components: {
cpn2: {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容2</p>
</div>`
}
}
})
</script>
模板分离
- 使用 script 标签
- 使用 template 标签
```vue
我是标题2
我是内容2
<a name="2tTnH"></a>
## 组件复用
组件中并不能用 Vue 实例中的属性,只能使用组件对象中的 data 属性。并且 data 属性必须是一个函数
```vue
<div id="app">
<cpn1></cpn1>
</div>
<!-- 2.使用 template 标签 -->
<template id="cpn1">
<div>
<h2>{{title}}</h2>
</div>
</template>
<script>
// 注册全局组件
Vue.component('cpn1', {
template: '#cpn1',
// data 属性必须是一个函数,并且返回一个对象
data() {
return {
title: 'abc'
}
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好'
}
})
</script>
组件中的 data 为什么是函数?
因为组件是可以复用的,所以必须保持组件内的数据独立性。正是因为组件内的 data 是一个函数,每创建一个组件实例都会 new 一个新的 Object 实例
// 1.注册组件
Vue.component('counter', {
template: '#counter',
data() {
return {
counter: 0
}
// 如果不想数据独立,可以返回同一个对象(可以有,没必要)
// return obj
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好'
}
})
组件通信
组件之间传递数据
父传子
通过 props
向子组件传递数据
注意:当使用 _v-bind_
时,不支持驼峰式写法,但可以用‘-’连接, _v-bind: c-info_
基本用法
<!-- 语法::组件 props 的属性名="传递的数据" -->
<!-- 当使用实例中的 data 时需要使用 v-bind -->
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
<script>
Vue.component('blog-post', {
// 数组的写法
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
</script>
对象写法
对象的写法可以限制数据传递到子组件的数据类型
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
<script>
Vue.component('blog-post', {
// 对象的写法
props: {
// 传入一个函数可以实现自定义检测类型
title: String,
// 限制多种类型
title: [String, Number],
},
template: '<h3>{{ title }}</h3>'
})
</script>
多种属性
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
<script>
Vue.component('blog-post', {
// 对象的写法
props: {
title: {
// 限制类型
type: String,
// 提供默认值
default: 'Why Vue is so fun',
// required 为 true 时,必须传一个值过来
required: true,
}
},
template: '<h3>{{ title }}</h3>'
})
</script>
注意:当默认值是对象或者数组时,必须从一个工厂函数获取(高版本才有要求)
<script>
Vue.component('blog-post', {
// 对象的写法
props: {
cmovies: {
type: Array,
default() {
return []
}
},
},
template: '<h3>{{ title }}</h3>'
})
</script>
验证函数
验证传递过来的数据
<script>
Vue.component('blog-post', {
// 对象的写法
props: {
type: String,
validator: function (val) {
// 这个值必须匹配下列字符串中的一个
return val === 'fade' || val === 'slide'
},
defalut:'slide'
}
template: '<h3>{{ title }}</h3>'
})
</script>
子传父
通过 $emit
向父组件发送自定义事件
<!-- 父组件模板 -->
<div id="app" :style="{ fontSize: postFontSize + 'em' }">
<p>Hello World</p>
<!-- 2.用来监听子组件发送出来的事件,通过 $event 来拿到传递过来的参数 -->
<cpn @enlarge-text="postFontSize += $event"></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<!-- 1.通过 $emit 发送一个事件,并附带参数,第二个值是可选的 -->
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
</div>
</template>
<script>
// 子组件
const cpn = {
template: '#cpn',
}
// 父组件
const app = new Vue({
el: '#app',
data: {
postFontSize: 1
},
components: {
cpn
},
})
</script>
或者,如果这个事件处理函数是一个方法:
<cpn @enlarge-text="onEnlargeText"></cpn>
那么这个值将会作为第一个参数传入这个方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
访问元素&组件
直接访问元素或组件中的数据,在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。
注意:节制地使用 $parent
和 $children
- 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
访问根
在每个 new Vue
实例的子组件中,其根实例可以通过 $root
property 进行访问。在绝大多数情况下,强烈推荐使用 Vuex 来管理应用的状态。
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
所有的子组件都可以将这个实例作为一个全局 store 来访问或使用。
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
子访父
和 $root
类似,$parent
property 可以用来从一个子组件访问父组件的实例,以替代将数据以 prop 的方式传入子组件的方式。
this.$parent
组件化的目的是提高复用性,当子组件需要向父组件获取数据时,复用性就变低了。
父访子
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。
$refs
注意:$refs
只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
。
通过 ref
这个 attribute 为子组件赋予一个 ID 引用(同样也可以在组件内部定义)
<base-input ref="usernameInput"></base-input>
通过 $refs 访问这个组件实例
this.$refs.usernameInput.focus()
当 ref
和 v-for
一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
$children
_
所有子组件都会存放在一个数组里面,通过 $children
来拿到这个数组
for (let c of this.$children) {
console.log(c.name);
}
组件插槽
基本使用
它允许你像这样合成组件:
<navigation-link>
Your Profile
</navigation-link>
然后通过 <slot>
标签在组件中预留一个插槽
<a>
<slot></slot>
</a>
组件渲染的时候,<slot></slot>
将会被替换为“Your Profile”。插槽内可以包含任何模板代码
<a>
<slot>Hello World</slot>
</a>
当 <navigation-link>
里面没有内容时, <slot>
的内容将会是默认内容
具名插槽
有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout>
组件:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
<slot>
元素有一个特殊的 attribute:name
。这个 attribute 可以用来定义额外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot>我是默认内容</slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带 name
的 <slot>
出口会带有隐含的 name“default”。
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 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>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
任何一种写法都会渲染出:
<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>
现在 <template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot
的 <template>
中的内容都会被视为默认插槽的内容。
然而,如果你希望更明确一些,仍然可以在一个 <template>
中包裹默认插槽的内容:
<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>
注意 v-slot 只能添加在 上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。
缩写语法 把参数之前的所有内容 ( 有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 我们可能想换掉备用内容,用名而非姓来显示。如下: 然而上述代码不会正常工作,因为只有 为了让 绑定在 在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 缩写语法 当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 不带参数的 注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。只要出现多个插槽,请始终为所有的插槽使用完整的基于 解构插槽 作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里: 所以可以使用 ES2015 解构来传入具体的插槽 prop 它同样开启了 prop 重命名等其它可能,例如将 你甚至可以定义 default 内容,用于插槽 prop 是 undefined 的情形: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>
组件:<span>
<slot>{{ user.lastName }}</slot>
</span>
<current-user>
{{ user.firstName }}
</current-user>
<current-user>
组件可以访问到 user
而我们提供的内容是在父级渲染的。user
在父级的插槽内容中可用,我们可以将 user
作为 <slot>
元素的一个 attribute 绑定上去:<span>
<!-- 将 user 传给了属性 user -->
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
<slot>
元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字:<current-user>
<!-- 将属性 user 对象传给 slotProps -->
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
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>
<template>
的语法:function (slotProps) {
// 插槽内容
// 返回的是一个对象
}
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
user
重命名为 person
:<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>