组件
组件是 Vue 强大的功能之一
Vue组件具有封装可复用的特点,能够让你在复杂的应用中拆分成独立模块使用
在开发中,我们可以将重复使用的功能封装为组件,方便开发提高效率
因为组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项
例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
为什么需要组件
- 提高复用性
- 解耦
- 提升开发效率
创建组件
Vue.js 使用component();
函数来创建组件,该函数中可以传入两个参数 分别为组件名、以对象的形式定义(描述)组件。
我们来看一下如何定义一个简单的组件
<body>
<div id="app">
<!--
定义一个 sc标签,该标签没有任何功能
甚至可以说该标签在 html中不合法,
我们可以 以定义组件的方式使其拥有功能且合法
-->
<sc></sc>
</div>
<!--引入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//定义组件: 第一个参数为组件名,第二个参数为定义组件
Vue.component("sc",{
data: function () {
return {
x: 0
}
},
//template属性的值就是组件的模板,这里我们定义了一个点击会 +1的小按钮
template: '<div><button v-on:click="x++">点我+1s</button><span>--->{{x}}s</span></div>'
});
//组件是可复用的 Vue 实例,且带有一个名字,在这个例子中是 <sc>。
// 我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用
var vm = new Vue({
el: "#app"
});
</script>
</body>
这里就不测试了,感兴趣可以自己拷过去玩一下。
组件的复用
组件是可复用的,且每个组件中数据是封装在组件内部的,相同组件之间数据互不影响。
我们可以将上述组件多复制几个来测试一下
<div id="app">
<!--将 sc组件复制四份在网页中观察组件中的数据是否独立-->
<sc></sc>
<sc></sc>
<sc></sc>
<sc></sc>
</div>
没有问题,x的值是独立的
当我们定义这个 <sc>
组件时,你可能会发现它的 data
并不是像这样直接提供一个对象:
data: {
x: 0
}
取而代之的是,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
如果 Vue 没有这条规则,那我们就没办法做到组件之间的数据相互独立。
组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
以 Vue.component();函数定义的组件为全局注册的组件
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用
局部注册
在这些情况下,你可以通过一个普通的 JavaScript对象来定义组件,并使用 Vue实例中的 components属性来添加一个组件作为该实例中的局部组件
<body>
<div id="app">
<sc></sc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 使用普通 JavaScript对象的形式定义一个组件实例
var sc = {
data(){
return{
x: 0
}
},
template:'<div><button v-on:click="x++">点我+1s</button><span>--->{{x}}s</span></div>'
}
var vm = new Vue({
el: "#app",
// 使用 components属性局部注册到该 Vue实例中
components: {
// key为 组件名,value为组件实例
'sc':sc
}
});
</script>
</body>
注意:局部注册的组件在其子组件中不可用。
例如,如果你希望 ComponentA
在 ComponentB
中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
Prop
每个Vue组件实例都有独立范围的作用域的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。
但 Vue.js可以通过使用 props参数实现父组件向子组件传递数据这一操作。
示例:
为了便于理解,你可以将这个Vue实例看作<lc>
的父组件。
如果我们想使父组件的数据,则必须先在子组件中定义props属性,否则无法拿到父组件中的数据。
<body>
<div id="app">
<!--但实际上并拿不到-->
<lc v-for="item in la"></lc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('lc',{
// 组件定义没有任何问题,按理应该可以拿到数组中遍历的每一项
template: '<li>{{item}}</li>'
});
new Vue({
el: "#app",
data: {
la: ["js","java","c#","go"]
}
});
</script>
</body>
那么父组件如何动态的传递数据给子组件呢?
还记得v-bind指令的作用吗,其作用是用于动态绑定 html属性或者是组件的 props值
,所以应该使用v-bind指令来动态传递数据。
<body>
<div id="app">
<!--添加 v-bind:将遍历出来的每一项绑定到 test中-->
<lc v-for="item in la" v-bind:test="item"></lc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('lc',{
// 使用 props属性拿到上面 v-bind指令中绑定的test
props:['test'],
// 在模板中取出props拿到的 test
template: '<li>{{test}}</li>'
});
new Vue({
el: "#app",
data: {
la: ["js","java","c#","go"]
}
});
</script>
</body>
关于Prop如果存在疑问请移步 Vue官网
template标签
在上文中我们使用 component();函数创建组件,该方式属于一种注册语法糖。
尽管语法糖简化了组件注册,但在 template属性中拼接 HTML元素是比较麻烦的。
这也导致了 HTML和 JavaScript的高耦合性,庆幸的是,Vue.js提供了两种方式将定义在 JavaScript中的 HTML模板分离出来。
一种是使用 script标签,但这种方式仍不是最优解 就不提了。
我们直接快进到 <template></template>
<body>
<div id="app">
<!-- 3、使用该组件-->
<mc></mc>
</div>
<!-- 1、使用 template标签,在标签体中定义组件的内容-->
<template id="myComponent">
<div>
<strong>在这里定义组件模板</strong>
<p>的话就不需要</p>
<span>做一些拼接 Html标签的操作了</span>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 2、注册组件
Vue.component("mc",{
// 选中 template标签中的 id
template: "#myComponent"
});
new Vue({
el: "#app"
});
</script>
</body>
如上
插槽
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。
这个处理称为内容分发,Vue.js实现了一个内容分发 API,使用特殊的 <slot>
元素作为原始内容的插槽。
插槽简单示例
如下,定义了一个父组件和一个子组件。
在父组件中使用了子组件,并尝试为子组件分发文本内容。
<body>
<div id="app">
<!--使用父组件-->
<parent></parent>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 注册父组件
Vue.component("parent",{
// 在父组件的模板中使用子组件
template: '<div><p>我是父组件</p><child><p>使用子组件,看看能不能在渲染时显示这行字</p></child></div>'
});
// 注册子组件
Vue.component("child",{
template: '<div><p>我是子组件</p></div>'
});
new Vue({
el: "#app"
});
</script>
</body>
测试:
结果是父组件分发的文本内容 并没有显示出来。
怎么让父组件分发的内容显示出来呢?
很简单,在子组件中为父组件留一个插槽即可 也就是使用<solt></solt>
标签
使用<solt></solt>
标签后,如果父组件在使用子组件时 为子组件分发了内容,则显示父组件分发的内容。
反之未分发内容 则会显示子组件<solt></solt>
标签中的内容
// 注册子组件
Vue.component("child",{
// 在子组件模板中留一个插槽
template: '<div><slot>我是子组件中的 slot</slot></div>'
});
使用该标签后,再进行测试
再将父组件中分发的内容注掉
// 注册父组件
Vue.component("parent",{
// 在父组件的模板中使用子组件,注释掉父标签分发的内容
template: '<div><p>我是父组件</p><child><!--<p>使用子组件,看看能不能在渲染时显示这行字</p>--></child></div>'
});
插槽也有分类,像上面使用的这种就是默认插槽,它是一个匿名slot,它只能表示一个插槽。
除了默认插槽还有具名插槽以及作用域插槽。
具名插槽
顾名思义,具名插槽 就是存在名字的插槽,也就是拥有 name属性的插槽。
为插槽定义 name自然是为了能够更准确的将数据插入到指定的插槽中。
我们来看一下示例:
下列代码和默认插槽示的例代码没有太大区别,无非是多了个插槽 且都加上了 name属性。
可以预见的 网页中会渲染出子组件中默认的标题和文本内容。
<body>
<div id="app">
<!-- 3、使用父组件,不进行任何操作-->
<page></page>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('page',{
// 2、在父组件的模板中调用子组件,不进行任何额外操作。
template: "<div><ct></ct></div>"
});
// 1、注册子组件
Vue.component('ct',{
// 1.1、在子组件的模板中定义两个插槽,并为这两个个插槽分别 添加 name属性
template: "<div><h1><slot name='pageTitle'>默认标题</slot></h1><p><slot name='pageText'>默认文本内容</slot></p></div>"
});
new Vue({
el: "#app",
});
</script>
</body>
网页中的显示:
我们在父组件的模板中做一些操作
Vue.component('page',{
// 对模板进行一些改动,在子组件中新增 span标签,为该 span标签添加 slot属性,值为'pageText'
template: "<div><ct><span slot='pageText'>新文本内容</span></ct></div>"
});
在模板中 往调用的子组件里 写一个 span标签,并为其添加 slot属性。
该属性的值为:子组件中定义插槽 name属性的值
<slot name='pageText'>默认文本内容</slot>
我们再来看看网页会如何渲染
子组件中 name为 'pageText' 插槽的文本内容,被替换为了 span标签中的文本内容。
那我们再将该 span标签的 slot属性值改为 'pageTitle',是不是就意味着能够替换掉 name为 'pageTitle' 插槽的文本内容呢?
Vue.component('page',{
// 对模板进行一些改动,在子组件中新增 span标签,为该 p标签添加 slot属性,值为'pageTitle'
template: "<div><ct><span slot='pageTitle'>新文本内容</span></ct></div>"
});
确实是如此
也就是说为插槽设置 name后,父组件想要使用哪个插槽 指定该插槽的 name即可。
当然了,该用法早已废弃。❌(虽然被废弃,但仍可继续使用)
新用法
我们再看看2.6.0之后的具名插槽如何使用
在2.6.0后,v-slot
指令替代了 slot属性。
需要注意的是,v-slot
指令只能使用在 template标签中,这一点和已经废弃的 slot属性有所不同。
我们在一个 <template>
元素上使用 v-slot
指令,并传入参数的形式提供其名称:
<body>
<div id="app">
<!--3、使用父组件-->
<page>
</page>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1、注册子组件
Vue.component("ct",{
// 1.1、在模板中使用 slot标签并添加对应的 name属性
template: `<div>
<slot name="pageTitle">默认标签</slot>
<slot>默认插槽的内容</slot>
<slot name="pageText">默认内容</slot>
/div>`
});
// 2、注册父组件
Vue.component("page",{
template: `<div>
// 2.1、在父组件模板中调用子组件标签
<ct>
// 2.2、在子组件标签中书写 template标签,并使用 v-slot指令,指令的参数为插槽 name的值
<template v-slot:pageTitle>
<h1>
<span>新标签</span>
</h1></template>
// 2.3、该 template标签不使用 v-slot指令
<template>
<p>我会替换掉默认插槽的内容</p>
</template>
<template v-slot:pageText>
</template>
</ct>
</div>`
});
</script>
</body>
<template>
元素中的所有内容都将会被传入相应的插槽。
任何没有被包裹在带有 v-slot
的 <template>
中的内容都会被视为默认插槽的内容。
测试:
一个未添加 name
属性 的 <slot>
标签 ,会默认携带隐藏的 name属性值 “default” 。
也就是说我们可以在 template标签中使用 v-slot
指令,传入 'default' 参数使其包裹的内容改变为默认插槽的内容。
template: `<div>
// 2.1、在父组件模板中调用子组件标签
<ct>
// 2.2、在子组件标签中使用 template标签,并使用 v-slot指令,指令的参数为插槽 name的值
<template v-slot:pageTitle>
<h1>
<span>新标签</span>
</h1></template>
// 2.3、该 template标签不使用 v-slot指令
<template>
<p>我会替换掉默认插槽的内容</p>
</template>
/*
2.4、为该template标签的 v-slot指令传入参数 default,
使其覆盖掉第二个 template标签中默认插槽的内容。
*/
<template v-slot:default>
<p>尝试再次替换默认插槽的内容</p>
</template>
</ct>
</div>`
测试:
作用域插槽
在上述两种插槽中,都是子组件接收父组件传递的数据。
而作用域插槽则是用来,向父组件中传入子组件的数据
可能有些人就要问了,我们直接在父组件调用子组件时不分发内容,使用子组件插槽中的内容不就实现了该操作吗?
匿名插槽和具名插槽中,插槽上不绑定数据,所以父组件提供的模板既要包括样式又要包括数据。
而作用域插槽是子组件提供数据,父组件只需要提供一套样式即可。
使用作用域插槽比较简单,无非两步:
- 将子组件数据绑定给
slot
上的属性 - 父组件模板中通过
slot-scope
拿到slot
对象,并进行属性访问。
以下为示例:
<body>
<div id="app">
<!-- 2、为子组件中拿到的数据添加不同的样式-->
<ct>
<!--
2.1、调用子组件标签后书写 template标签,并使用 slot-scope拿到子组件插槽中的数据,
将该数据命名为 sc(这个可以随意),我们可以通过该名称访问到子组件中的数据
-->
<template slot-scope="sc">
<!--ul li的形式输出-->
<ul>
<li v-for="item in sc.test">{{item}}</li>
</ul>
</template>
</ct>
<ct>
<template slot-scope="sc">
<!--以加粗的形式输出,每个值之间使用 "--》"字符串分隔 -->
<strong>{{sc.test.join('-->' )}}</strong>
</template>
</ct>
<ct>
<template slot-scope="sc">
<!--正常输出,每个值之间使用 " / "分隔-->
<p>{{sc.test.join(' / ')}}</p>
</template>
</ct>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1、注册子组件
Vue.component("ct",{
// 1.1、在模板中 通过:test="name" ,将子组件的 name绑定到 slot的 test属性上
template: '<div><slot :test="names"></slot></div>',
data(){
return{
names: ["molu","qiu","lin","zhang"]
}
}
});
new Vue({
el: "#app"
});
</script>
</body>
代码块比较长,但都是一些很基础的东西,对着注解顺序应该很容易看懂
测试:
同样的,作用域插槽在 2.6.0之后的版本也有新的写法,上面这种写法也已被废弃。
新用法
新写法没什么讲究也没什么需要注意的,将 slot-scope
修改为 v-slot
指令即可,如下:
<ct>
<!--将 slot-scope 修改为 v-slot-->
<template v-slot:default="sc">
<ul>
<li v-for="item in sc.test">{{item}}</li>
</ul>
</template>
</ct>
关于插槽更多的可以移步Vue.js官网
作者本人实际上是写后端的(从代码风格应该也能看出端倪),对这些东西研究尚浅 就不发表什么高论了。
也查阅了很多,发现作用域插槽好像能做不少文章 不是我一个业余前端能说明白的,感兴趣可以自行搜索其他文章。
写的比较匆忙也比较乱,应该有不少错误的地方吧。
醒过来再检查吧......现在的时间是凌晨三点半,写到昏厥
放松一下眼睛
原图P站地址
画师主页
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!