vue基础学习-组件

  • 组件(Component)是 Vue.js 最强大的功能之一。
  • 组件可以扩展 HTML 元素,封装可重用的代码
  • 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

在这里插入图片描述

根组件

每个 Vue 应用都是通过用 createApp() 函数创建的,传递给 createApp() 的选项用于配置根组件。

当我们挂载应用时,该组件被用作渲染的起点。一个应用需要被挂载到一个 DOM 元素中。

<div id="app"></div>

//根组件
const RootComponent = { /* 选项 */ }
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')

全局组件

全局注册的组件可以在随后创建的 app 实例模板中使用,也包括根实例组件树中的所有子组件的模板中。通过component()全局注册。

const app = Vue.createApp({...})

app.component('global-component', {
  /* ... */
})

global-component组件名,/* … */ 为配置选项。注册后,我们可以使用以下方式来调用组件:

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<title>Vue 测试实例</title>
	<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
	<div id="app">
		<button-counter></button-counter>
		<button-counter></button-counter>
		<button-counter></button-counter>
	</div>
</body>

<script>
	// 创建一个Vue 应用
	const app = Vue.createApp({})

	// 定义一个名为 button-counter 的新全局组件
	app.component('button-counter', {
		template: `
		    <button @click="count++">
				点了 {{ count }} 次!
			</button>
			`,
		data() {
			return {
				count: 0
			}
		}
	})
	app.mount('#app')

</script>

在这里插入图片描述

⚠️注意:template 中 ` 是反引号,不是单单引号 '。

结果可见,各个子组件的变量 count 互不影响,子组件间相互独立,各个子组件都有自己的数据域。

局部组件

全局注册往往是不够理想的,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,可以注册局部组件。

const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}

然后,在 components 选项中定义你想要使用的组件:

const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字(component-acomponent-b),其属性值就是这个组件的选项对象(ComponentAComponentB)。

示例:

<body>
	<div id="app">
		<component-a></component-a>
		<br>
		<component-b></component-b>
	</div>
</body>

<script>
	const ComponentA = {
		template: `<div> A custom component! </div>`
	}
	const ComponentB = {
		template: `<div> B custom component! </div>`
	}

	const app = Vue.createApp({
		components: {
			'component-a': ComponentA,
			'component-b': ComponentB
		}
	})

	app.mount('#app')

</script>

在这里插入图片描述

组件嵌套

组件是可复用的 Vue 实例,所以同样通过选项 components 注册

<body>
	<div id="app">
		<child></child>
	</div>
</body>

<script>
	const ComponentA = {
		template: `<div> A custom component! </div>`
	}
	const ComponentB = {
		template: `<div> B custom component! </div>`
	}

	//组件的嵌套。组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,同样通过选项 components 注册
	const Child = {
		template: `
		    <div>
			    <component-a></component-a>
		        <br>
		        <component-b></component-b>
			</div>
		`,
		components: {
			'component-a': ComponentA,
			'component-b': ComponentB
		}
	}

	const app = Vue.createApp({
		components: {
			'child': Child
		}
	})

	app.mount('#app')

</script>

在这里插入图片描述

⚠️注意:template 定义多个标签时,必须用<div> </div> 包括起来。

组件传递数据

  • props/$emit
  • $ref
  • Vuex
  • slot

1. props/$emit

  • 父传子:子组件通过 props 显式声明 自定义 属性,接收父组件的传值。
  • 子传父:子组件通过 $emit() 显式声明 自定义 事件,父组件调用自定义事件接收子组件返回的参数。

1.1 props-父组件传递数据给子组件

props 是子组件用来接受父组件传递过来的数据的一个自定义属性。一个组件需要 显式声明 它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props。

<body>
    <div id="app">
        <component-a title="Google" user-name="Lily"></component-a>
        <component-a :title="title" :user-name="userName"></component-a>
    </div>
</body>

<script>
    const ComponentA = {
        template: `<div> A custom component! title = {{title}} userName = {{userName}}</div>`,
        props: ['title', 'userName']
    }

    const app = Vue.createApp({
        components: {
            ComponentA
        },
        data: function () {
            return {
                title: 'Taobao',
                userName: 'Jack'
            }
        }
    })

    app.mount('#app')

</script>

在这里插入图片描述

📌 子组件通过 props: ['title','userName'] 显式声明它所接受的属性 titleuserName

💦 父组件有两种方式调用:

  • 第一种直接赋值:
<component-a title="Google" user-name="Lily"></component-a>

title=“Google”user-name=“Lily” 将属性值传递给子组件。

  • 第二种,使用 v-bind 动态设置标签的属性值。
<component-a :title="title" :user-name="userName"></component-a>

titleuserNamedata 中动态赋值。需要注意一点: 需要使用格式 data: function () { }data:{ } 的简写方式会加载失败。

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

ComponentA --- <component-a>
userName --- user-name

动态 Prop

类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件。

<body>
	<div id="app">
		<title-info v-for="item in posts" :id="item.id" :title="item.title"></title-info>
		<!-- template 形式
		<template v-for="item in posts">
			<title-info :id="item.id" :title="item.title"></title-info>
		</template>
		-->
	</div>
</body>

<script>
	const titleInfo = {
		template: `<h4>{{ id }} - {{ title }}</h4>`,
		props: ['id','title']
	}

    new Vue({
    	el: '#app',
    	components: {
    		titleInfo
    	},
    	data: function () {
    		return {
    			posts: [
    				{ id: 1, title: 'One' },
    				{ id: 2, title: 'Two' },
    				{ id: 3, title: 'Thre' }
    			]
    		}
    	}	
    })
</script>

在这里插入图片描述

1.2 $emit-子组件传递数据给父组件

  • 子组件:this.$emit('自定义事件名称', '返回参数'),子组件通过 $emit() 显式声明 自定义事件 myEvent返回参数 this.context
  • 父组件:@my-event="mainFun",父组件调用自定义事件 my-event 接收子组件返回的参数。

⚠️注意:事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以自定义事件应使用 @my-event

<body>
    <div id="app">
        <child @my-event="mainFun"></child>
		<p> {{context}} </p>
    </div>
</body>

<script>
	const child = {
		template: `<div>
			          <button @click="funHandle">组件</button> | 
					  <input type="text" v-model="context">
					</div>
					`,
		data: function () {
            return {
                context: ''
            }
        },
		methods: {
			//内建的 $emit 方法返回自定义的事件名称 myEvent
			funHandle(){
				this.$emit('myEvent', this.context)
			}
		}
	}

	new Vue({
    	el: '#app',
    	components: {
			child
		},
    	data: function () {
            return {
                context: ''
            }
        },
		methods: {
			mainFun(param){
			    //返回参数
				this.context = param
			}
		}
    })
</script>

在这里插入图片描述

💦 上述 @my-event="mainFun" 接收子组件传递父组件的值,父组件 mainFun(param) 获取子组件传递值,如果 mainFun 方法需要传递其他参数呢?

$event

<body>
    <div id="app">
        <child @my-event="mainFun($event, 'aaa')"></child>
		<p> {{context}} </p>
    </div>
</body>

<script>
	...

	new Vue({
    	el: '#app',
    	...
		methods: {
			mainFun(param, arg1){
			    //返回参数
				console.log("param=", param)
				console.log("arg1=", arg1)
				this.context = param
			}
		}
    })
</script> 

在这里插入图片描述

💦 如果有多个回调参数呢?

箭头函数 + ... 扩展运算符 参考:📖vue如何给自带默认回调参数的函数添加自定义参数

<body>
    <div id="app">
        <child @my-event="(...event) => {mainFun(event, '自定义参数')}"></child>
		<p> {{context}} </p>
    </div>
</body>

<script>
	const child = {
		template: `<div>
			          <button @click="funHandle">组件</button> | 
					  <input type="text" v-model="context">
					</div>
					`,
		data: function () {
            return {
                context: ''
            }
        },
		methods: {
			//内建的 $emit 方法返回自定义的事件名称 myEvent
			funHandle(){
			    // 返回两个参数
				this.$emit('my-event', this.context, '返回参数2')
			}
		}
	}

	new Vue({
    	el: '#app',
    	...
		methods: {
			mainFun(event, customParam){
			    //返回参数
				console.log("event[0]=", event[0])
				console.log("event[1]=", event[1])
				console.log("customParam=", customParam)
				this.context = event[0]
			}
		}
    })
</script> 

在这里插入图片描述

回调方法的两个回调参数和本地参数均能获取到。

💦 箭头函数与直接调用的区别?

(...event) => {mainFun(event, '自定义参数')},这实际上创建了一个新的箭头函数,该函数在接收到事件参数后,再调用 mainFun 函数并传入处理过的参数。箭头函数的一个重要特性是:自动捕获其所在上下文this 值,这对于React组件中的事件处理特别有用,可以确保 this 指向你期望的组件实例

如果直接写成mainFun(...event, '自定义参数'),这意呀着你希望在事件处理器被注册时就立即调用 mainFun 函数,而不是等到实际事件触发时。这显然不是你希望的行为,因为这样会在注册事件处理器的时候就执行函数,而不是在用户触发事件时执行。

总结来说,使用 (...event) => {mainFun(event, '自定义参数')} 是为了创建一个新的函数,这个函数等待事件触发时被调用,并且能够正确地处理 event 对象以及传递额外的参数(在这个例子中是字符串 ‘自定义参数’)。直接写 mainFun(...event, '自定义参数') 则会尝试立即执行函数,而非作为事件处理器等待事件触发。

2. $ref / $parent

$refs 是用于访问组件实例或 DOM 元素的引用。首先在模板中使用 ref 属性为元素或组件设置引用名称,然后在 Vue 实例中通过 this.$refs 访问这些引用。

<template>
  <div>
    <input type="text" ref="inputValue" />
    <button @click="showValue">显示输入值</button>
  </div>
</template>

export default {
  methods: {
    showValue() {
      const inputValue = this.$refs.inputValue.value;
      console.log(inputValue);
    }
  }
}

也可以运用于父子组件传值:

  • 父传子:父组件的方法中通过 this.$refs 访问子组件的引用。
  • 子传父:子组件中通过 this.$parent.$refs 访问父组件的引用。

2.1 父传子-$refs

<body>
    <div id="app">
		<button @click="funHandle">点击父组件</button> | 
        <child ref="child"></child>
		<p> {{FaMessage}} </p>
    </div>
</body>

<script>
	const child = {
		template: `<div> 
			<button @click="funHandle">点击子组件</button> | 
			child sonMessage = {{sonMessage}} </div>`,
		data: function () {
    		return {
    			sonMessage: ''
    		}
    	},
		methods: {
			funHandle(){
				//子传父方式二:$parent
			    this.$parent.FaMessage = "子传父$parent"
			}
		}
	}
	
	new Vue({
    	el: '#app',
    	components: {
			child
		},
		data: function () {
    		return {
    			FaMessage: ''
    		}
    	},
		methods: {
			funHandle(){
				//父传子方式二:$refs
			    this.$refs.child.sonMessage = "父传子$refs"
			}
		}
    })
</script>

this.$refs.child.sonMessage = "父传子$refs" 父组件向子组件传值,还可以调用子组件的方法。

2.2 子传父-$parent

this.$parent.FaMessage = "子传父$parent" 子组件向父组件传值,还可以调用父组件的方法。

3. 插槽-slot

slot 插槽,是子组件提供给父组件使用的一个 占位符父组件可以在该占位符填充任何模板代码

主要作用:就是为了更好的拓展和定制化组件,例如弹窗组件、表格组件等。分为默认插槽具名插槽作用域插槽

  • 默认插槽、具名插槽:本质一样,就是 替换 子组件对应的slot,区别在于具名插槽定义了名字。(父传子
  • 作用域插槽:本质上会把父组件内容渲染成函数,子组件调用函数,并将数据传递给它。当需要将子组件的数据传递给父组件展示时,就可以使用作用域插槽。(子传父

应用场景:自定义的表格组件,允许用户传入自定义的结构和数据

3.1 默认插槽

通过 <slot></slot> 标签来定义一个插槽。这个插槽可以有默认内容,也可以是没有任何内容的。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Vue slot-插槽</title>
  <script src="https://cdn.staticfile.org/vue/2.6.2/vue.min.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<div id="app">
	<div>
		<h1>父组件</h1>
		<child>
			<p>这是插入到子组件中的内容</p>
		</child>
	</div>
</div>
</body>
<script>
Vue.component('child', {
	template: `
		<div> 
			<h2>子组件</h2>
			<!-- 这里是插槽 -->
			<slot>默认插槽内容</slot>
		</div>`
})

// 创建根实例
new Vue({
	el: '#app'
})
</script>

子组件通过 <slot></slot> 标签定义一个默认插槽,父组件在 <child> 标签中包含一个 <p> 标签,作为插入到子组件中的内容。

3.2 具名插槽

<body>
<div id="app">
	<div>
		<h1>父组件</h1>
		<child>
			<!-- 具名插槽 -->
			<p slot="center">center1,无色</p>
			
			<template slot="center">
				<p style="color:red;">center2,字体为红色</p>
			</template>
			
			<template v-slot:center>
				<p style="color:blue;">center3,字体为蓝色</p>
			</template>
			
			<template #center>
				<p style="color:green;">center4,字体为绿色</p>
			</template>	
			
		</child>
	</div>
</div>
</body>
<script>
Vue.component('child', {
	template: `
		<div> 
			<h2>子组件</h2>
			<!-- 具名插槽 -->
			<slot name="center">插槽默认内容</slot> 
		</div>`
})

// 创建根实例
new Vue({
	el: '#app'
})
</script>

子组件通过 <slot name="center"></slot> 标签定义一个具名插槽,父组件可以通过四种方式插入子组件,并且 最后一个插槽的内容会覆盖掉上面同名插槽的内容

一个不带 name 的 slot,会带有隐含的名字 default

3.3 作用域插槽

作用域插槽用于父组件访问到子组件中的内容,相当于子组件将数据传递给父组件(子传父

<body>
<div id="app">
	<div>
		<child> 
			<!-- 方式一: scope -->
			<template scope="scopeData">
				<ul>
					<li v-for="item in scopeData.games"> {{item}} </li>
				</ul>
				<h2>{{scopeData.msg}}</h2>
			</template>
			
			<!-- 方式二: slot-scope -->
			<template slot-scope="scopeData">
                <ul>
					<li v-for="item in scopeData.games"> {{item}} </li>
				</ul>
				<h2>{{scopeData.msg}}</h2>
            </template>
			
			<!-- 方式三: v-slot -->
			<template v-slot:default="scopeData">
                <ul>
					<li v-for="item in scopeData.games"> {{item}} </li>
				</ul>
				<h2>{{scopeData.msg}}</h2>
            </template>
		</child>
	</div>
</div>
</body>
<script>
Vue.component('child', {
	template: `<div> 
					<!-- 作用域插槽 -->
					<slot :games="gameData" msg="hello"></slot>
			   </div>`,
	data() {
		return {
			gameData:['红色警戒','穿越火线','劲舞团','超级玛丽']
		}
	}
})

// 创建根实例
new Vue({
	el: '#app'
})
</script>

子组件通过 <slot :games="gameData" msg="hello"></slot> 对外暴露属性 gamesmsg,父组件通过 v-slot:default="scopeData" 获取子组件传递的数据 scopeData,再通过 scopeData.games 获取实际数据。

⚠️注: 变量 scopeData 可以随便定义,它只是一个标识。scopeData.games 中的 games 才是子组件属性。

还可以 解构 传递的数据

<template v-slot:default="{games,msg}">
	<ul>
		<li v-for="item in games"> {{item}} </li>
	</ul>
	<h2>{{msg}}</h2>
</template>

3.4 小结

参考:
📖 插槽Slot的作用和基本使用
📖 Vue 中 slot 是什么?作用?分类?如何实现?

使用插槽 Slot,父组件可以向子组件指定位置插入 html 结构,更好的拓展和定制化组件,也是一种组件间通信的方式。分为:默认插槽具名插槽作用域插槽

📌 1 默认插槽:
子组件中:

<template>
    <div>
       <!-- 默认插槽 -->
       <slot> 插槽默认内容... </slot>
    </div>
</template>

父组件中:

<Category>
   <div> html结构1 </div>
   
   <template v-slot:default>>
      <div> html结构1 </div>
   </template>	
   
   <template #default>
      <div> html结构1 </div>
   </template>
</Category>
  • 子组件通过 <slot> 标签来创建默认插槽
  • 默认插槽带有隐含的名字 default
  • 父组件可以在子组件标签内直接插入 html,也可以使用<template v-slot:default><template #default>

📌 2 具名插槽:
子组件中:

<template>
    <div>
       <!-- 具名插槽 -->
       <slot name="slot-name">插槽默认内容...</slot>
    </div>
</template>

父组件中:

<Category>
    <template slot="slot-name">
		<div> html结构1 </div>
    </template>

    <template v-slot:slot-name>
       <div> html结构2 </div>
    </template>
	
	<template #slot-name>
       <div> html结构3 </div>
	</template>	
</Category>
  • 子组件通过给 <slot> 元素添加 name 属性来创建具名插槽
  • 父组件中可以使用 <template slot="slot-name"><template v-slot:slot-name>,或者简写 <template #slot-name>
  • Vue中,自定义的组件标签名称、slot 的 name 属性、变量,多个英文单词之间尽量采用 - 连接不要使用_或者驼峰命名,比如上例使用 slot-name
  • Vue2.6.0之前,使用 slot="插槽名",Vue2.6.0 之后,建议使用 v-slot:插槽名 或者 #插槽名

📌 3 作用域插槽
子组件中:

<template>
    <div>
        <slot :games="gameData" title="hello"></slot>
    </div>
</template>
        
<script>
    export default {
        name:'Category',
        data() {
            return {
                gameData:['红色警戒','穿越火线','劲舞团','超级玛丽'],
            }
        },
    }
</script>

父组件中:

<Category>
    <template scope="scopeData">
		<h2>{{scopeData.title}}</h2>
		<h4 v-for="item in scopeData.games"> {{item}} </h4>
    </template>
	
	<template slot-scope="scopeData">
		<h2>{{scopeData.title}}</h2>
		<h4 v-for="item in scopeData.games"> {{item}} </h4>
    </template>
	
	<template v-slot:default="scopeData">
		<h2>{{scopeData.title}}</h2>
		<h4 v-for="item in scopeData.games"> {{item}} </h4>
    </template>
	
	<template #default="scopeData">
		<h2>{{scopeData.title}}</h2>
		<h4 v-for="item in scopeData.games"> {{item}} </h4>
    </template>
</Category>
  • Vue2.6.0之前,父组件使用 scope="变量名"slot-scope="变量名" 获取子组件数据,Vue2.6.0 之后,使用 v-slot:插槽名="变量名" 或者 #插槽名="变量名",默认插槽还可以直接使用 v-slot="变量名"
  • 可以通过解构获取,v-slot="{games,title}

3.5 注意点

  • vue 2.6.0 引入 v-slot 指令,为具名插槽和作用域插槽提供新的统一的语法,所以 vue 2.6.0 之后的版本就推荐直接使用 v-slot 了。
  • v-slot 属性只能在 <template> 上使用,但在只有默认插槽时可以在组件标签上使用。
  • 默认插槽名为 default,可以省略 default 直接写 v-slot;缩写为 # 时不能不写参数,写成 #default。
  • 使用 element-ui 时要注意版本, element-ui 2.13.2 版本中的 vue 是 2.5 的,而 v-slot 是 vue 2.6 中才引入的,使用 v-slot 一直没效果,所以如果想在插槽中使用最新的 v-slot 指令,记得看下 vue 的版本哟。

4. Vuex

Vuex是一个专为vue.js 应用程序开发的状态管理模式, 它采用集中式存储管理数据,以相应的规则保证状态以一种可预测的方式发生改变, 一变全变,同步更新数据。

// 注册代码
const store = new Vue.Store({
  state:{
    count: 100
  },
  mutations: {
    addCount(state, val = 1) {
      state.count += val;
    },
    subCount(state, val = 1) {
      state.count -= val;
    }
  }
})
 
// 组件调用
this.$store.commit('addCount');  // 加 1
this.$store.commit('subCount');  // 减 1
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值