作者主页:仙女不下凡
前言介绍:以下 内容都是我个人对于前端知识的总结,会定期更新欢迎持续关注!
欢迎点赞 收藏⭐ 留言 如有错误敬请指正!
看这篇文章之前,请先理解两个概念:什么是Vue
实例,什么是Vue
组件?我在该文章中详细解释过地址https://blog.csdn.net/weixin_55181759/article/details/119957816
学习重点:实例与组件、父子组件、兄弟组件最重要要了解传参的方法,首先我在"一"中我们介绍了组件的基本使用方法与组件基本类型的区别,另外为了方便对比在示例代码中,我没有用脚手架使用了.html
文件引入vue
同时将父子组件,兄弟组件都写在一个文档中,但在真实的vue
项目中每个组件都是一个独立的.vue
文件,希望这点不要给阅读该文章的小伙伴造成误解。
Vue
组件化思想:它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造应用
任何的应用都会被抽象成一颗组件数树(树结构)。
组件使用的三个步骤:❶调用Vue.extend()
方法创建组件构造器❷调用Vue.component()
方法注册组件❸在Vue
实例的作用域内使用组件。
//示例代码: 该文件为.html文件
<div id="app">
<my-cpn>my-cpn> //3.使用组件 (<my-cpn>my-cpn>可以使用多次)
div>
<script src="../js/vue.js">script>
<script>
//1.创建组件构造器
const cpnC = Vue.extend({
template: `
组件
`
})
//2.注册组件
Vue.component('my-cpn',cpnC)
const vm = new Vue({
el: '#app',
data: { message: '你好啊!' }
})
script>
思考: 什么是全局组件与局部组件?下面就写一段代码进行对比介绍。
//示例代码: 该文件为.html文件
<div id="app1">
<my-cpn>my-cpn> //3.使用组件-app1与vm1是对应的作用域
div>
<div id="app2">
<my-cpn>my-cpn> //3.使用组件-app2与vm2是对应的作用域
div>
<script src="../js/vue.js">script>
<script>
const cpnC = Vue.extend({
template: `
组件
`
})
Vue.component('my-cpn',cpnC); /这就是全局组件#app1与app2中都可以使用<my-cpn></my-cpn>/
const vm1 = new Vue({
el: '#app1',
data: { message: '你好啊!' },
components: { cpnC:cpnC }, /<cpnC></cpnC>是局部组件,只能在app1中使用, 在app2中使用就会报错/
})
const vm2 = new Vue({
el: '#app2',
data: {message: '你好'}, /真实项目中一般会有一个Vue实例,这里是想让大家理解全局与局部区别/
})
script>
思考:什么是父组件与子组件?下面就写一段代码进行对比介绍。
//示例代码: 该文件为.html文件
<div id="app">
<cpn2>cpn2>
div>
<script src="../js/vue.js">script>
<script>
const cpnC1 = Vue.extend({ /这个就是子组件/
template: `
子组件
`
})
const cpnC2 = Vue.extend({ /因为在cpnC2中使用了cpnC1, 所以cpnC2就是cpnC1的父组件/
template: `
父组件
`,
components:{ cpnC1:cpnC1 }
})
const vm = new Vue({ /在真实vue项目中, 一般只有一个new Vue()(即Vue实例)/
el: '#app',
data: { message: '你好啊!' },
/cpnC2其实就相当于真实项目中App.vue文件, 是所有组件的集合, 不管有多少个父组件都需要关联在App.vue组件上, 可以理解为App.vue文件是所有组件的祖宗/
components: { cpnC2:cpnC2 },
})
script>
关于父子组件之间传参等方法,在"二"中会详细说明
现在Vue
项目中写组件一般都是用了组件语法糖的方法,很少先使用extend()
再写component()
这样的方法了。下面就用代码演示使用组件的语法糖方法之后的区别。
该文件为.html文件-使用组件语法糖后
<div id="app">
<cpn>cpn>
div>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn',{ /内部自动调用了extend()/
template: `
组件
`,
});
const vm = new Vue({
el: '#app',
data: { message: '你好啊!' }
})
script>
语法糖写法简化了代码,之后所有的示例代码我都会引用语法糖写法。
模板抽离第一种写法,不常用。
该文件为.html文件
<div id="app">
<cpn>cpn>
div>
<script type="text/x-template" id="cpn">
<div>
<h2>组件</h2>
</div>
script>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn',{
template: "#cpn", /这样就可以了/
});
const vm = new Vue({
el: '#app',
data: { message: '你好啊!' }
})
script>
模板抽离第二种写法,较为常用。
该文件为.html文件
<div id="app">
<cpn>cpn>
<cpn>cpn>
div>
<template id="cpn"> /这是组件模板/
<div>
<h2>组件h2>
div>
template>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn',{
template: "#cpn", /这样就可以了/
});
const vm = new Vue({
el: '#app',
data: { message: '你好啊!' }
})
script>
思考:组件可以直接访问Vue
实例的数据data
吗?
结论:Vue
组件不可以直接访问Vue
实例的数据data
,Vue
组件把数据保存在自己的data
函数中,并且data
函数中的数据用return
返回,所以此data非彼data,。
原因: 将Vue
组件中data设计成函数的原因:当我们在不同页面(或者相同的页面)重复使用组件的时候,我们传的数据应该是不同的,如果Vue
组件中data
跟Vue
实例中data
一样是对象类型那么就造成各个组件之间数据相互影响;所以只有Vue
组件中的data
是函数类型才能保证组件多次复用时数据互不影响,根本原因是在函数的栈空间中每次调用函数返回的对象保存在不同的地址,互不影响。
在组件中,使用 构造选项props
来声明需要从父级接收到的数据。
props
的值有两种方式: ▶️字符串数组,数组中的字符串就是传递时的名称。
▶️对象,对象可以设置传递的类型,也可以设置默认值等。
props数据验证-支持所有数据类型(string、number、Boolean、array、object、)
Vue.components('cpn', {
props: {
/基础的类型检查(null匹配任何类型)/
propA: Number,
/多个可能的类型/
propB: [String, Number],
/必填的字符串/
propC: {
type: String,
required: true
},
/带有默认值的数字/
propD: {
type: Numder,
default: 100
},
/带有默认值的对象/
propE: {
type: Object,
default: function(){ /对象成数组默认值必须从工厂函数获取/
return {message:'你好'}
}
},
/自定义验证函数/
propF: {
validator: function(val){
return ['success', 'warning', 'danger'].indexOf(val) !== -1
}
}
}
})
一般子组件向父组件传值都是在发生某些事件(如点击事件)之后,向父组件传递数据,所以传值使用自定义事件。
<div id="app">
<cpn @item-click="cpnClick">cpn>
div> /父组件模板/
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{{item.name}} /点击哪个按钮传递给父组件哪个数据/
button>
div>
template> /子组件模板/
<script src="../js/vue.js">script>
<script>
const cpn = { /子组件/
template: '#cpn',
data(){
return {
categories: [
{id:'001', name: '热门推荐'},
{id:'002', name: '手机数码'},
{id:'003', name: '家具家电'},
{id:'004', name: '电脑办公'}
]
}
},
methods: {
/点击之后发射emit了一个叫itemClick的事件, 让父组件接收/
btnClick(val){ this.$emit('itemClick',val) }
}
}
const vm = new Vue({ /父组件/
el: '#app',
data:{},
components: {cpn},
methods: {
cpnClick(val){ console.log(val) } /这样就得到了子组件的值了/
}
})
script>
有时候父子组件之间不仅仅只需要传参,也需要直接访问其中的方法等。
所以▶️父组件访问子组件:使用$children
或$refs
。
▶️子组件访问父组件:使用$parent
。
<div id="app">
<cpn>cpn>
<button @click="btnClick">button>
div>
<template id="cpn">
<div>我是子组件div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = { /子组件/
template: '#cpn',
data(){},
methods: { showMessage(){'我是子组件的方法'}
}
}
const vm = new Vue({
el: '#app',
data:{},
methods: {
btnClick(){ console.log(this.$children) } /这种方法需要用下标值来获取(this.$children[0])不太好/
}
components: { cpn } /引入子组件/
}
})
script>
因为$children
属性需要用下标值来获取(this.$children[0]
),如果顺序改变会有问题,所以引出了$refs
属性。
<div id="app">
<cpn>cpn>
<cpn>cpn>
<cpn ref="aaa">cpn>
<button @click="btnClick">button>
div>
<template id="cpn">
<div>我是子组件div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = { /子组件/
template: '#cpn',
data(){},
methods: { showMessage(){'我是子组件的方法'}
}
}
const vm = new Vue({
el: '#app',
data:{},
methods: {
btnClick(){ console.log(this.$refs.aaa) } /这样就获取到aaa子组件了/
}
components: { cpn } /引入子组件/
}
})
script>
以下代码演示$parent
使用方法,但是该方法在实际开发中用的很少,因为使用后导致父子组件耦合性太高了,所以不建议这样使用。
<div id="app">
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>我是子组件h2>
<button @click="btnClick">button>
div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = { /子组件/
template: '#cpn',
data(){},
methods: {
btnClick(){console.log(this.$parent)} /这样就可以了/
}
}
const vm = new Vue({ /父组件/
el: '#app',
data:{},
components: { cpn } /引入子组件/
methods: {showMessage(){'我是父组件的方法'}}
}
})
script>
获取根组件,即可以直接获取到组件的Vue
实例对象,与$parent
用法相同。
思考组件的插槽slot
有什么存在的实际意义?
如下图整个导航栏就可以为一个组件,但是其中的展示的文字图标会略有不同,slot
插槽就是解决了这个问题,所以slot
是为了让我们封装的组件更加具有延展性,让使用者可以决定组件内部的一些内容到底展示的是什么。
思考:为什么有具名插槽?
因为很多组件都需要多个插槽,所以产生了具名插槽的概念,使用方法如下代码。
在学习作用域插槽之前需要理解一个概念叫做编译作用域。
写一段代码辅助理解,请思考一下代码运行后页面是空白还是显示?
<div id="app">
<cpn v-show="isShow">cpn>
div>
<template id="cpn"> /子组件模板/
<div>
<h2>我是组件h2>
div>
template>
<script src="../js/vue.js">script>
<script>
const vm = new Vue({ /这个是父组件/
el: '#app',
data: { isShow: true },
components: {
cpn: { /这是引入的子组件/
template:'#cpn',
data(){
return { isShow: false }
}
}
}
})
script>
答案是显示!
原因:父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译,即编译作用域。
作用域插槽意义:父组件替换插槽标签,但是内容是由子组件来提供。下面我用一段代码演示什么意思!
总结:❶子组件需要用
暴露数据❷父组件通过接收数据。
在学习作用域插槽之前需要理解一个概念叫做编译作用域。
写一段代码辅助理解编译作用域,请思考一下代码运行后页面是空白还是显示?
<div id="app">
<cpn v-show="isShow">cpn>
div>
<template id="cpn"> /子组件模板/
<div>
<h2>我是组件h2>
div>
template>
<script src="../js/vue.js">script>
<script>
const vm = new Vue({ /这个是父组件/
el: '#app',
data: { isShow: true },
components: {
cpn: { /这是引入的子组件/
template:'#cpn',
data(){
return { isShow: false }
}
}
}
})
script>
答案是显示! 原因就是:父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译,即编译作用域。
**作用域插槽意义:**父组件替换插槽标签,但是内容是由子组件来提供。下面我用一段代码演示什么意思!
总结:❶子组件需要用
暴露数据❷父组件通过接收数据。
<div id="app">
<cpn>cpn>
<-- 比如下面这个组件展示效果想要成一行用"-"分割,这时候就需要用作用插槽实现了 -->
<cpn>cpn>
div>
<template id="cpn">
<div>
<ul>
<li v-for="item in pLanguages">{{item}}li>
ul>
div>
template>
<script src="../js/vue.js">script>
<script>
const vm = new Vue({
el: '#app',
data: {},
components: {
cpn: {
template:'#cpn',
data(){
return { pLanguages: ['JavaScript','C++','java','c#','python','go'] }
}
}
}
})
script>
总结: 插槽中除了作用域插槽外,其他的都是父组件控制子组件对应插槽的“样式”与“值”,而作用域插槽不同于他的插槽“样式”是父组件控制,但是插槽“值”是由子组件控制,所以在父子组件通信(传值)时,作用域插槽也是其中的选择之一。