第5章〓Vue.js组件开发

本章学习目标

通过本章的学习,能够熟悉组件的命名规范,掌握组件注册方法; 掌握组件间常用的通信方法; 掌握插槽的分类和定义方法; 能够在实际工程中使用插槽传递数据。

Web前端开发工程师应知应会以下内容: 

 熟悉组件的命名规范; 

 掌握全局、局部注册组件的方法; 

 掌握组件间常用的通信方法; 

 掌握插槽的分类和定义方法。

5.1组件基础

组件(Component)是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。组件也可以看作自定义的HTML元素。可以使用独立可复用的小组件构建大型应用,几乎任意类型应用的界面都可以抽象为一棵组件树,如图51所示。



图51组件树

5.1.1组件命名

注册组件前,必须给组件命名,组件名应该采用多个单词构成,以免与现有的或未来的HTML元素有冲突,由于所有HTML元素名称都是由单个单词构成。Vue组件中通常采用kebabcase(短横线分隔式)或camelCase(驼峰式)命名方式。

1. kebabcase命名方式

当定义一个组件时,必须在引用自定义元素时使用kebabcase命名方式。Vue props最好直接使用kebabcase方式命名,如myname、mycomponentname等。

2. camelCase命名方式

camelCase(也称为驼峰式)命名方式就是当组件名称是由一个或多个单词构成时,第1个单词首字母小写,从第2个单词开始的每个单词的首字母都采用大写,如myFirstName、myLastName、myComponent。注意,尽管如此,直接在DOM(即非字符串的模板)中使用时,只有kebabcase形式的名称是有效的。

3. PascalCase命名方式

与camelCase命名方式不同,PascalCase命名方式的第1个单词的首字母是大写的。当使用PascalCase命名方式定义一个组件时,在引用自定义元素时kebabcase和PascalCase两种形式都可以使用,也就是说mycomponentname和MyComponentName都是可接受的。

以上都是官方文档所述,因为HTML不区分大小写,所以不管是用kebabcase还是camelCase方式命名的组件,在写入HTML后都要改写为kebabcase形式。

【基本语法】



// 在组件中局部注册,自定义组件名称必须加上引号,如'kebab-cased-component'

components: {

// 使用 kebab-case 注册

'kebab-cased-component': { /* … */ },

// 使用 camelCase 注册

'camelCaseComponent': { /* … */ },

// 使用 PascalCase 注册

'PascalCasedComponent': { /* … */ }

}






在HTML模板中使用kebabcase命名方式。



<!-- 在HTML模板中始终使用 kebab-case命名方式 -->

<kebab-cased-component></kebab-cased-component>

<camel-cased-component></camel-cased-component>

<pascal-cased-component></pascal-cased-component>






当使用字符串模式时,可以不受HTML大小写敏感的限制。也就是说,实际上在模板中可以使用以下方式引用这些组件: 

 kebabcase; 

 camelCase或kebabcase(如果组件已经被定义为camelCase形式); 

 kebabcase、camelCase或PascalCase (如果组件已经被定义为PascalCase形式)。



// 在组件中定义

components: {

'kebab-cased-component': { /* … */ },

'camelCaseComponent': { /* … */ },

'PascalCasedComponent': { /* … */ }

}

// 在字符串模板中,可以采用以下格式引用

const app=Vue.createApp({});

app.component('MyCom',{

template:`<div>

<kebab-cased-component></kebab-cased-component>

<camel-cased-component></camel-cased-component>

<camelCaseComponent></camelCaseComponent>

<pascal-cased-component></pascal-cased-component>

<pascalCasedComponent></pascalCasedComponent>

<PascalCasedComponent></PascalCasedComponent>

</div>`

})






5.1.2组件注册

组件注册通常分为全局注册和局部注册。全局组件适用于所有实例,局部组件仅供本实例使用。

1. 全局注册组件

【基本语法】



// 全局组件注册

const app=Vue.createApp({})

app.component(tagName, options)

// 直接定义选项

app.component('myComponet', {

// data必须定义为函数,并通过return返回相关数据

template: 'HTML元素定义'

})

// 也可以链式注册全局组件

app

.component('ComponentA', ComponentA)

.component('ComponentB', ComponentB)

.component('ComponentC', ComponentC)






【语法说明】

tagName为组件名,options为配置选项,选项是一个对象,通过模板(template)选项定义组件的外观。ComponentA、ComponentB、ComponentC为已定义JavaScript对象或选项对象。当模板中包含多个HTML元素时,必须使用一个根元素包裹其他HTML元素,否则定义的组件不会生效。与Vue 2.x中Vue实例data选项的定义方法不同,在组件中定义data选项时,必须定义为函数,其中的数据通过return返回。也可以使用data(){return{}}格式来定义,具体格式如下。



data: function(){ // 也可以使用data(){return {…}}

return {

//定义相关数据属性

}

}






注册后,可以调用组件,调用方式如下。



<myComponet></ myComponet >

<ComponentA/>

<ComponentB/>

<ComponentC/>






在Vue组件中,必须通过components选项定义自己的组件。也可以使用普通的JavaScript对象定义组件,然后在全局注册和局部注册时直接引用。全局注册的组件可用于此应用程序内任何组件的模板中,甚至适用于所有子组件,这意味着所有组件也将彼此内部可用。

2. 局部注册组件

【基本语法】



const componentA = {template: '<h1>h1-Js自定义局部组件-A组件</h1>'};

// 创建Vue实例

const { createApp } = Vue;

const App = {// 定义根组件

components: {// 局部注册子组件

 mycomp1: componentA,

 mycomp2: tmp1

}

})


createApp(App).mount('#app');

//也可以采用以下方法

createApp({









components: {

 mycomp1: componentA,

 mycomp2: tmp1

}

}).mount('#app');






【语法说明】

局部注册时,需要在components选项下注册组件,mycomp1和mycomp2为组件名,componentA为组件对应的JavaScript对象或选项对象。在Vue中,一个组件本质上是一个拥有预定义选项的Vue实例,在Vue中注册组件非常简单。

【例51】全局注册与局部注册组件实战。代码如下,页面效果如图52所示。



图52全局注册与局部注册组件





1.<!-- vue-5-1.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>组件基础</title>

7.<script src="../js/vue.global.js"></script>

8.<style type="text/css">

9.button {border: 1px dotted blue;border-radius: 8px;

10.width: 100px;height: 40px;margin: 5px 10px; }

11.</style>

12.</head>

13.<body>

14.<div id="app">

15.<fieldset>

16.<legend>定义全局组件与复用</legend>

17.<my-com></my-com>


18.<my-com></my-com>


19.<my-com></my-com>

20.</fieldset>


21.<fieldset>


22.<legend>定义全局组件</legend>

23.<mycomp2></mycomp2>

24.</fieldset>

25.<fieldset>


26.<legend>定义局部组件</legend>

27.<mycomp1></mycomp1>

28.</fieldset>

29.</div>

30.<script type="text/javascript">









31.const { createApp } = Vue; // 解构赋值createApp

32.// 定义JS对象componentA

33.var componentA = {

34.template: "<h1>h1-JS自定义局部组件-A组件</h1>",

35.};

36.// 定义JS对象componentB

37.var componentB = {

38.data() {

39.return {title: "我自己定义的标题信息!",};

40.},

41.template: `

42.<div>

43. <p>多个HTML元素时,使用根元素div</p>

44. <h3 v-bind:title="title" >h3-JS自定义全局组件-B组件,盘旋会有提示</h3>

45.</div>`,

46.};

47.// 局部注册组件mycomp1

48.const App = {

49.components: {mycomp1: componentA,},

50.};

51.const app1 = createApp(App);

52.// 全局注册组件my-com,定义1个计数器的按钮

53.app1.component("my-com", {

54.data() {

55.return {count: 0, };

56.},

57.template: '<button v-on:click="count++">单击{{count}}次按钮</button>',

58.});

59.// 通过JavaScript对象componentB来定义mybutton2组件

60.app1.component("mycomp2", componentB);

61.// 定义App组件,并在根组件中注册子组件mybutton1

62.app1.mount("#app"); // 挂载

63.</script>

64.</body>

65.</html>






上述代码中,第14~29行定义视图内容,主要用于展示全局注册和局部注册组件的使用; 第33~46行定义componentA、componentB两个JavaScript组件对象供引用; 第49行定义局部注册组件mycomp1; 第51行使用createApp(App)创建Vue实例app1; 第53~58行使用app1.component()定义全局注册组件mycom,其中第57行模板中按钮绑定单击事件,实现count变量累加,并同时修改按钮的提示信息; 第60行全局注册组件mycomp2。

5.2组件间通信



图53父子组件通信

组件的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。父组件可以使用props属性把数据传给子组件。


在Vue中,父子组件的关系可以总结为“props向下传递,事件向上传递”。父组件通过props向子组件下发数据,子组件通过事件向父组件发送消息,如图53所示。子组件需要显式地用defineProps()函数声明props。props的值可以是两种,一种是字符串数组,另一种是对象。


5.2.1父组件向子组件传值
1. 使用props传递数据

【基本语法】



<!--HTML模板部分 -->

<div id="app">

<my-component message="父组件通过props传递参数"></my-component>

</div>

<!--JS部分 -->

<script type="text/javascript">

const app=Vue.createApp({});

//组件定义

app.component('my-component',{

props: ['message'] ,

template: '<div>{{ message }}</div>'

});

App.mount('#app');

</script>






【语法说明】

<mycomponent> </mycomponent >为自定义组件,采用kebabcase命名方式,相当于一个HTML元素。message为组件的属性,其值作为传递给子组件的内容。props属性为字符串数组(用方括号包围),其中的属性必须使用引号包围(如'message'),可以在模板中通过文本插值的方式使用。这种父子组件通信是单向的。组件中的数据共有3种形式: data、props、computed。

 注意组件中data和props都可以为组件提供数据,但它们是有区别的。data选项的类型为对象,对象中返回的数据属于组件内部数据,只能用于组件本身。props选项的类型可以是字符串数组,也可以是对象,用来声明组件从外部接收的数据。这两种数据都可以在模板(template)、计算属性(computed)和方法(methods)中使用。
【例52】父组件通过props向子组件下发数据实战。代码如下,页面效果如图54所示。



图54父组件通过props下发数据





1.<!-- vue-5-2.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>









5.<meta charset="UTF-8" />

6.<script src="../js/vue.global.js"></script>

7.<title>props下发数据</title>

8.</head>

9.<body>

10.<div id="app">

11.<my-component message="父组件props下发数据 "></my-component>

12.</div>

13.<script type="text/javascript">

14.const { createApp } = Vue;

15.// 创建Vue实例

16.const app = createApp({});

17.// 全局注册组件my-component

18.app.component("my-component", {

19.data: function() {

20.return {

21.title: "我是组件中的内部数据!",

22.};

23.},

24.props: ["message"],

25.template: "<div>{{ message }}-<strong>{{title}}</strong></div>",

26.});

27.app.mount("#app");

28.</script>

29.</body>

30.</html>






2. 静态props传递数据

HTML中的属性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当使用DOM中的模板时,camelCase形式的props名称需要使用其等价的kebabcase形式。

通常父组件单向(正向)传递数据给子组件,需要以下3个步骤。

(1) 创建父、子组件构造器。通过Vue.extend()方法构建组件。

(2) 注册父、子组件。可以全局或局部注册各类组件。

(3) 在Vue实例范围内使用组件。

【例53】静态props传递数据实战。代码如下,页面效果如图55所示。



图55静态props传递数据





1.<!-- vue-5-3.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />









6.<title>props-父组件将数据传递给子组件</title>

7.<script type="text/javascript" src="../js/vue.global.js"></script>

8.</head>

9.<body>

10.<!--#app是Vue实例挂载的元素,在挂载元素范围内使用组件-->

11.<div id="app">

12.<h3>我的待办事项-日期:{{today}}</h3>

13.<mytodo :todo-data="myToDos"></mytodo>

14.</div>

15.</body>

16.<script>

17.// 定义根组件App,准备data

18.const App = {

19.data() {

20.return {

21.today: new Date().toLocaleDateString(),

22.myToDos: [

23.{ id: 0, text: "完成月度工作总结" },

24.{ id: 1, text: "主持本周项目讨论会" },

25.{ id: 2, text: "通知周五提交周工作计划" },

26.],

27.};

28.},

29.};

30.// 创建Vue实例

31.const app = Vue.createApp(App);

32.// 构建一个子组件,使用字符串模板(反引号表示)

33.const todoChild = {

34.template: ` <li> {{ text }} </li> `,

35.props: {

36.text: { type: String, default: "" },

37.},

38.};


39.// 构建一个父组件,使用字符串模板(反引号表示)


40.const todoParent = {

41.template: `

42.<ul>

43. <todo-item v-for="(item, index) in todoData" v-text="item.text" v-bind:key="item.id" ></todo-item>

44.</ul>

45.`,

46.props: {todoData: { type: Array, default: [] }, },

47.components: { todoItem: todoChild }, // 注册局部子组件

48.};

49.// 注册全局组件mytodo

50.app.component("mytodo", todoParent);

51.app.mount("#app");

52.</script>

53.</html>






上述代码中,第18~29 行定义根组件 App,并设置data选项,为myToDos对象数组准备数据; 第33~48 行定义两个组件,分别为todoChild和todoParent,并在todoParent组件中局部注册子组件todoChild为todoItem; 第50行将todoParent组件注册为全局组件mytodo; 第51行进行挂载。

3. 动态props传递数据

要动态地绑定父组件的数据到子模板的props,与绑定到任何普通的HTML属性相类似,就是使用vbind指令。每当父组件的数据发生变化时,都会实时地将变化后的数据传递给子组件。

【例54】动态props传递数据实战。代码如下,页面效果如图56和图57所示。



1.<!-- vue-5-4.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>props-父组件将动态传递数据</title>

7.<script type="text/javascript" src="../js/vue.global.js"></script>

8.</head>

9.<body>

10.<div id="app">

11.<h3>1.父组件动态props传递数据给子组件(v-model)</h3>

12.<input type="text" v-model="parentInputText" placeholder="请输入内容" />

13.<h3>2.子组件接收动态数据(v-bind)</h3>

14.<my-component :message="parentInputText"></my-component>

15.<h3>3.子组件修改其值(computed),但不影响父组件数据</h3>

16.<my-component :message="changeParentInputText"></my-component>

17.<h3>4.子组件使用v-model绑定值时控制台会报错,也不会改变父组件的数据</h3>

18.<my-component-1 :message="parentInputText"></my-component-1>

19.</div>

20.</body>

21.<script>

22.//定义根组件,并准备数据

23.const App = {

24.data() {

25.return { parentInputText: "",};

26.},

27.computed: {

28.//通过计算属性修改子组件数据


29.changeParentInputText: function() {

30.return "子组件修改其值了!" + this.parentInputText;

31.},

32.},

33.};

34.const app = Vue.createApp(App);

35.//全局注册组件my-component,组件内直接使用message

36.app.component("my-component", {

37.props: ["message"],

38.template: "<div>{{ message }}</div>",

39.});

40.//全局注册组件my-component-1,组件内通过v-model指令绑定message

41.app.component("my-component-1", {

42.props: ["message"],

43.template: '<div> <input type="text" v-model="message" /></div>',

44.});

45.app.mount("#app"); // 挂载视图

46.</script>

47.</html>







上述代码中,第18行引用mycomponent1组件,此时在组件中修改props 传递过来的数据,控制台会发出警告,如图57所示。所以,通常采用computed计算属性修改props传递过来的值。本地定义属性,并将props作为初始值,props传入之后需要进行转换。

 注意在使用props传值时,如果不使用vbind指令传递数字、布尔、数组、对象类型的数据,此时传递的数据都是字符串类型,由于模板中未使用vbind指令绑定属性,因此不会被编译。





图56动态props传递数据



图57修改数据控制台报错



【例55】传值时vbind的使用实战。代码如下,页面效果如图58所示。



图58传值时vbind的使用





1.<!-- vue-5-5.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<script src="../js/vue.global.js"></script>

7.<title>传值时v-bind的使用</title>

8.</head>

9.<body>

10.<div id="app">

11.<h3>使用v-bind时,将['123','456','789','ABD']作为数组类型</h3>

12.<my-component:message="['123','456','789','ABD']"></my-component>

13.<h3>未使用v-bind时,将['123','456','789','ABD']作为字符串</h3>

14.<my-component message="['123','456','789','ABD']"></my-component>

15.</div>

16.<script type="text/javascript">

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

18.app.component("my-component", {

19.props: ["message"],

20.template: "<div>message.length={{ message.length }}</div>",

21.});

22.app.mount("#app");

23.</script>

24.</body>

25.</html>






4. props数据验证

组件props选项的值可以是数组类型,也可以是对象类型。props选项的对象类型可以用于对外部传递进来的参数进行数据验证。当封装了一个组件时,对于内部接收的参数进行校验是非常必要的。部分代码如下。



<script>

const app = Vue.createApp({});

app.component('my-comp',{

props:{

//必须是数字类型

propA: Number,

//必须是字符串或数字类型

propB:[String, Number],


//布尔值,如果没有定义,默认值就是true

propC:{

type: Boolean,

default: true

},

//数字,而且是必选

propD: {

type: Number,

required: true

},

//如果是数组或对象,默认值必须是一个函数来返回

propE: {

type: Array,

default: function() {

return {};

}

},

//自定义验证函数

propF: {

viladator: function(value) {

return value > 10;

}

}

}

});

</script>






其中,default表示默认值; required表示必选。

验证的类型(type)可以是String、Number、Boolean、Object、Array、Function。type也可以是一个自定义构造器,使用instanceof检测。props验证失败时,开发版本下会在控制台抛出一条警告。

【例56】props数据验证实战。代码如下,页面效果如图59和图510所示。



图59props数据验证





1.<!-- vue-5-6.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>


5.<meta charset="UTF-8" />

6.<title>props数据验证</title>

7.<script src="../js/vue.global.js"></script>

8.<style type="text/css">

9.fieldset {width: 350px;height: 200px;margin: 0 auto;text-align: center;}

10.</style>

11.</head>

12.<body>

13.<div id="app">

14.<fieldset>

15.<legend align="center">讲师基本信息</legend>

16.<my-teacher name="李明明" no="2022010199" :age="35"></my-teacher>

17.</fieldset>

18.</div>

19.<script>

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

21.// 全局注册组件my-teacher

22.app.component("my-teacher", {

23.props: {

24.no: {

25.type: String,

26.required: true, //必选,不选报错

27.},

28.name: {

29.type: String,

30.required: true, //必选,不选报错

31.},

32.age: {

33.type: Number,

34.validator: function(value) {

35.return value >=0 && value <=130; //年龄为0~130

36.},

37.},

38.detail: {

39.type: Object,

40.default: function() {

41.return {

42.department: "计算机科学与工程学院",

43.address: "敏行楼B05",

44.};

45.},

46.},

47.},

48.template: `

49.<div class="person">

50.<p>工号:{{this.no}}</p>

51.<p>姓名:{{this.name}}</p>

52.<p>年龄:{{this.age}}岁</p>

53.<p>单位:{{this.detail.department}}</p>

54.<p>地址:{{this.detail.address}}</p>

55.</div>

56.`,

57.});

58.app.mount("#app");

59.</script>

60.</body>

61.</html>






上述代码中,第16行使用myteacher组件设置name和no两个必选属性以及age属性并赋值。第22~57行全局注册组件myteacher,定义props为对象,其中定义no、name为字符串,必选; age为数字型,取值范围为0~130; detail为对象,包含address和department两个属性。

将第16行代码中的no属性设置项取消,同时将“:age=35”改为“:age=135”,将会报错,如图510所示。



图510组件属性设置不符合验证要求时报错

5.2.2子组件向父组件传值
1. 自定义事件

当子组件需要向父组件传递数据时,需要使用自定义事件。子组件使用$emit()触发自定义事件,父组件使用$on()侦听子组件的事件。父组件也可以直接在子组件的自定义标记上使用von指令侦听子组件触发的自定义事件,数据就通过自定义事件进行传递。

1) “父组件von:自定义事件”传递数据



<child-comp v-on: eventName ="functionName"></child-comp>

//在父组件的Vue组件中定义methods选项

methods:{

functionName(postdata){

//对传递来的数据postdata进行处理,并对父组件的数据进行运算

}

}






2) 子组件$emit()触发自定义事件传递数据



<button @click='increase'>增加100</button>

methods: {

'increase': function() {

this.$emit('eventName ', data); // 第1个参数为自定义事件名称,第2个参数为数据

},

…

}






子组件通过this.$emit('eventName',data)方式触发父组件约定的事件绑定的处理函数functionName,同时子组件传递数据给父组件。父组件使用von指令侦听自定义事件。需要注意,$emit('eventName', data)函数的第1个参数是自定义事件的名称,第2个参数为数据,数据可以有多个。

【例57】子组件向父组件传值实战——周薪调整。代码如下,页面效果如图511所示。



图511子组件向父组件传值(1)





1.<!-- vue-5-7.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>Vue子组件向父组件传值$emit()+v-on</title>

7.<script src="../js/vue.global.js"></script>

8.<style type="text/css">

9.button {width: 100px;height: 35px;border: 1px dashed red;border-radius: 10px;}

10..childClass {margin: 0 auto; width: 300px;height: 100px;

11.border: 1px dotted green;text-align: center;}

12.#app {margin: 0 auto;width: 350px;height: 200px;

13.border: 1px dotted green; padding: 10px; }

14.</style>

15.</head>

16.<body>

17.<div id="app">

18.<h3>父组件--周薪调整</h3>

19.<p>子组件传值:调整后周薪salary={{salary+total}}</p>

20.<my-comp @increase="changeTotal" @reduce="changeTotal"></my-comp>

21.</div>

22.<script>

23.// 1.定义根组件App,并配置data和methods选项

24.const App = {

25.data() {

26.return {

27.total: 0, //父组件的数据


28.salary: 3500,


29.};

30.},









31.methods: {

32.changeTotal(total) {

33.this.total = total; //total是子组件传过来的数据

34.},

35.},

36.};

37.// 2.以App组件创建Vue实例

38.const app = Vue.createApp(App);

39.// 3.全局注册组件my-comp

40.app.component("my-comp", {

41.template: `

42.<div class='childClass'>



43.<h4>这是子组件:调节量amount={{amount}}</h4>

44.<button @click='increase'>增加100</button>

45.<button @click='reduce'>减少100</button>

46.</div> `,

47.data() {

48.// 子组件的数据

49.return { amount: 0 };

50.},

51.methods: {

52.// 子组件通过事件处理函数执行$emit(),传递数据

53.increase() {

54.// 调节量增100

55.this.amount += 100;

56.this.$emit("increase", this.amount);

57.},

58.reduce() {

59.// 调节量减100

60.this.amount -= 100;

61.this.$emit("reduce", this.amount);

62.},

63.},

64.});

65.// 挂载视图并渲染

66.app.mount("#app");

67.</script>

68.</body>

69.</html>






上述代码中,第17~21行定义视图,在视图中使用子组件<mycomp>侦听自定义事件increase和reduce,当事件发生时调用changeTotal方法实现传值; 第40~64行定义子组件,在子组件<template>标记内定义两个按钮,并定义单击事件处理子组件的数据,将子组件的数据传出。

2. 使用vmodel指令传递数据

由于Vue 3.x中vmodel指令的使用方法已经变更,用于自定义组件时,vmodel指令中prop和事件默认名称已更改为以下格式。 



prop:value -> modelValue;

event:input -> update:modelValue;






在父组件上使用vmodel:modelValue='xxx'指令,子组件中使用this.$emit('update: modelValue',this.子组件属性)进行传值。

1) 父组件中通过vmodel指令绑定数据



<div id="app">

<my-comp v-model: modelValue = modelValue '></my-comp>

</div>






2) 子组件中通过事件触发$emit('update:modelValue',data)



const app=Vue.createApp(App);

app.component('my-comp', {

template: `

<div class='childClass'>

<button @click='increase'>增加1</button> 

</div> `,

methods: {

increase() { //计数器增1

this.counter++;

this.$emit('update:modelValue', this.counter);

},

}

})






在Vue 2.x中,在组件上使用vmodel指令相当于绑定value prop和input事件。



<ChildComponent v-model="pageTitle" />

<ChildComponent :value="pageTitle" @input="pageTitle = $event.target.value" />






子组件中的事件处理函数中,使用$emit()发送数据。



this.$emit("input", postdata);






【例58】vmodel指令传递数据实战——课程刷分。代码如下,页面效果如图512所示。



1.<!-- vue-5-8.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>使用v-model动态传递数据</title>

7.<script src="../js/vue.global.js"></script>

8.<style type="text/css">

9.button {width: 100px;height: 35px;

10.border: 1px dashed red; border-radius: 10px; }

11..childClass {width: 280px; height: 120px; margin: 0 auto;

12.border: 1px dotted green; text-align: center; }

13.#app {margin: 0 auto; width: 350px;height: 250px;

14.border: 1px dotted green; padding: 10px;}

15.</style>

16.</head>

17.<body>

18.<div id="app">

19.<h3>父组件v-model:课程刷分</h3>


20.<p v-once>课程初始成绩:{{score}}</p>

21.<p>子组件传值:调整后课程成绩score={{score+total}}</p>

22.<my-comp v-model:total="total"></my-comp>

23.</div>

24.<script>

25.const App = {


26.data() {

27.return { // 父组件的数据

28.score: 65,

29.total: 0,

30.};

31.},

32.};

33.const app = Vue.createApp(App);

34.// 全局注册组件my-comp

35.app.component("my-comp", {









36.template: `

37.<div class='childClass'>

38.<h4>这是子组件:刷分量amount={{amount}}</h4>

39.<button @click='increase'>增加1</button>

40.<button @click='reduce'>减少1</button>

41.</div> `,

42.data() {

43.//子组件的数据

44.return { amount: 0 };

45.},

46.methods: {

47.increase() {

48.//刷分量增1

49.this.amount++;

50.this.$emit("update:total", this.amount);

51.},

52.reduce() {

53.//刷分量减1

54.this.amount--;

55.this.$emit("update:total", this.amount);

56.},

57.},

58.});

59.app.mount("#app");

60.</script>

61.</body>

62.</html>








图512子组件向父组件传值(2)



 注意虽然Vue允许子组件修改父组件数据,但是在实际业务中,子组件应该尽量避免依赖父组件的数据,更不应该主动修改它的数据。由于父、子组件属于紧耦合,如果仅从父组件来看,很难理解父组件的状态,因为它可能被任意组件修改。所以,通常情况下,组件能修改自己的状态,父、子组件之间最好还是通过props和$emit()通信。
5.2.3父链与子组件索引

除了上述方法外,采用父链与子组件索引同样可以实现组件间通信。

在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$children(返回值为数组类型)通过索引值访问它的所有子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。

子组件利用父链this.$parent获取设置父组件的数据,部分代码如下。



const msg=this.$parent.message;//获取数据

this.$parent.message = '消息:组件my-comp修改数据'//设置父组件的数据






子组件索引this.$refs.indexName修改组件数据,部分代码如下。



<my-comp1 ref="comp1"></my-comp1>

<my-comp2 ref="comp2"></my-comp2>

// 在事件处理函数中或JS代码中

this.message = this.$refs.comp1.message;//通过$refs获取子组件的数据

this.$refs.comp1.message=this.message//通过$refs设置子组件的数据






【例59】父链与子组件索引应用实战。代码如下,页面效果如图513和图514所示。



1.<!-- vue-5-9.html -->

2.<!DOCTYPE html>

3.<html lang="en">

4.<head>

5.<meta charset="UTF-8" />

6.<title>父链与子组件索引的应用</title>

7.<script src="../js/vue.global.js"></script>

8.</head>

9.<body>


10.<div id="app">

11.<fieldset>

12.<legend>子组件利用父链修改父组件数据</legend>

13.<p>{{message}}</p>

14.<my-comp></my-comp>

15.</fieldset>

16.<fieldset>

17.<legend>父组件通过子组件索引访问子组件数据</legend>

18.<p v-show="message1!=''|| message2!=''">{{message1}},{{message2}}</p>

19.<my-comp1 ref="comp1"></my-comp1>

20.<my-comp2 ref="comp2"></my-comp2>

21.<button @click="handlerCompRef">通过ref获取子组件实例</button>

22.</fieldset>

23.</div>

24.<script>

25.const App = {


26.data() {

27.return { message: "", message1: "", message2: "" };

28.},

29.methods: {

30.handlerCompRef: function() {









31.// 通过$refs获取子组件实例

32.this.message1 = this.$refs.comp1.message;

33.this.message2 = this.$refs.comp2.message;

34.},

35.},

36.};

37.const app = Vue.createApp(App);

38.// 全局注册组件my-comp

39.app.component("my-comp", {

40.template:

41.'<button @click="changeMessage">通过父链直接修改数据</button>',

42.methods: {

43.changeMessage: function() {

44.// 通过this.$parent直接修改父组件的数据

45.this.$parent.message = "消息:组件my-comp修改数据";

46.},

47.},

48.});

49.// 全局注册组件my-comp1

50.app.component("my-comp1", {

51.template: "<div>子组件1-{{message}}</div>",

52.data() {

53.return { message: "my-comp1-欢迎使用我的数据!" };

54.},

55.});

56.// 全局注册组件my-comp2

57.app.component("my-comp2", {

58.template: "<div>子组件2-{{message}}</div>",

59.data() {

60.return { message: "my-comp2-请下载使用优质资源!" };

61.},

62.});

63.app.mount("#app");

64.</script>

65.</body>

66.</html>








图513父链与子组件索引应用初始页面




图514单击按钮后数据获取页面效果


上述代码中,第19行和第20行为两个组件设置ref属性,值分别为comp1和comp2; 第32行和第33行父组件通过this.$refs访问指定名称的子组件的数据; 第45行子组件通过$parent获取或设置父组件数据。

 注意$refs只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案,应当尽量避免在模板或计算属性中使用$refs。

5.3插槽

在开发组件时, 组件内一些子元素希望是由调用者来定义,组件只负责核心功能,其他非核心由用户自由定义,可以增加组件的灵活性和可扩展性。这种场景非常适合使用插槽。插槽(Slot)是Vue提出的一个概念,用于决定将所携带的内容插入指定的某个位置,从而使模板分块,具有模块化的特点和更强的重用性。

插槽是否显示、怎样显示是由父组件控制的,而插槽在哪里显示就由子组件进行控制,父页面在组件标记内插入任意内容,子组件内插槽控制摆放位置(匿名插槽、具名插槽)。

插槽分类如下。

(1) 匿名插槽: 默认(default)插槽。没有命名,有且只有一个。

(2) 具名插槽: 指<slot>标记带name属性的插槽。

(3) 作用域插槽: 子组件内数据可以被父页面拿到(解决了数据只能从父页面传递给子组件的问题)。

5.3.1匿名插槽

匿名插槽使用<slot> </slot>标记为需要传递的内容占位,类似于内容占位符。

【基础语法】

(1) 在父组件中使用子组件标记,并携带传递的内容。



<child><p>父组件需要分发给子组件的内容...</p></child>






(2) 在子组件模板中插入插槽标记。



const Comp1 = {

template: `

<div class="comp">

<h3>这是子组件...</h3>

<slot>插槽默认的内容1</slot>

<slot>插槽默认的内容2</slot>

…









</div>

`

}

//App组件内注册子组件

const App={

components: {child: Comp1,}

}






【语法说明】

子组件渲染时,会将<slot>标记用父组件中子组件标记的内容替换。子组件中如果有多个匿名插槽,将会同时被父组件传递的内容所替换。如果在子组件的模板中没有插入<slot>标记,则父组件中使用子组件标记的内容将被忽略。

【例510】匿名插槽实战。代码如下,页面效果如图515所示。



1.<!-- vue-5-10.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>匿名插槽slot的应用</title>

7.<script type="text/javascript" src="../js/vue.global.js"></script>

8.<style type="text/css">

9..comp {width: 300px;border: 1px dotted black; padding: 10px;}

10.#app {border: 1px dotted green;width: 350px;padding: 10px;}

11.</style>

12.</head>

13.<body>

14.<div id="app">

15.<h2>父组件空间</h2>


16.<child>

17.<p>父组件需要分发给子组件的内容</p>

18.</child>

19.</div>

20.<script type="text/javascript">

21.const Comp1 = {

22.template: `

23.<div class="comp">

24.<h3>子组件空间</h3>

25.<slot></slot>

26.<slot></slot>

27.<slot>这是子组件插槽默认的内容。父组件中无内容传递时,显示该内容,否则会被替代。</slot>

28.</div>

29.`,

30.};

31.const App = {

32.components: {

33.child: Comp1,

34.},

35.};

36.const app = Vue.createApp(App);

37.app.mount("#app");

38.</script>

39.</body>

40.</html>








图515匿名插槽的应用

5.3.2具名插槽

slot元素可以用一个特殊的name属性配置如何分发内容,多个插槽可以有不同的名字,根据具名插槽的name属性进行匹配,显示内容。如果有匿名插槽,那么没有匹配到的内容将会显示到匿名插槽中; 如果没有匿名插槽,那么没有匹配到的内容将会被抛弃。

在向具名插槽提供内容时,也可以在一个template元素上使用vslot指令,并以vslot的参数的形式提供其名称(如vslot:slotname)。template元素中的所有内容都将会被传入相应的插槽中。任何没有被包裹在带有vslot的template元素中的内容都会被视为匿名插槽的内容。当然,也可以在一个template元素中包裹匿名(默认)插槽的内容。

【基本语法】

(1) 在父组件中的子组件标记内,使用带slot属性的标记或带vslot指令参数的template元素携带传递的内容。



<child>

<p>该内容将显示在匿名插槽中。</p>

<template>

<p>使用匿名插槽--该内容将显示在匿名插槽中。</p>

</template>

<template v-slot:slot1 | #slot1>

<p>使用v-slot指令--该内容将显示在具名插槽slot1中。</p>

</template>

<p slot='slot2'>使用slot属性--该内容将显示在具名插槽slot2中。</p>

</child>






具名插槽可以采用缩写的方式。与von和vbind一样,vslot也有缩写,即把参数之前的所有内容(vslot:)替换为字符#。例如,vslot:slot1可以简写为#slot1。

(2) 在子组件模板中插入具名插槽标记(使用name属性)。



const Comp1 = {

template: `

<div class="comp">

<h3>这是子组件...</h3>

<slot name='slot1'></slot>

<slot name='slot2'></slot>

<slot></slot>

</div>

`

}









// App组件中注册子组件

components: {child: Comp1,}







【语法说明】

在父组件中使用子组件标记,并在其中插入多个带slot属性的标记,其属性值为子组件中定义slot元素所指定的name属性值。子组件渲染时,匹配到slot元素(name的属性值)会被父组件中子组件标记内具有相应的slot属性值的标记的内容所替换。在父组件中可以使用带vslot:slotname参数的template元素携带传递内容,同样会匹配到带name属性的具名插槽。

 注意vslot一般添加在<template> </template>标记上,这一点和已经废弃的slot属性不同。
【例511】具名插槽实战。代码如下,页面效果如图516所示。



图516具名插槽





1.<!-- vue-5-11.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>具名插槽slot的应用</title>

7.<script type="text/javascript" src="../js/vue.global.js"></script>

8.<style type="text/css">

9..comp {width: 430px;border: 1px dotted black;padding: 10px;}

10.#app {border: 1px dotted green;width: 450px;padding: 10px;}

11.</style>

12.</head>

13.<body>

14.<div id="app">

15.<h3>父组件空间</h3>

16.<child>

17.<p>使用匿名插槽--该内容将显示在匿名插槽内。</p>

18.<template v-slot:slot1>

19.<h4>使用v-slot指令--该内容将显示在具名插槽slot1内。</h4>

20.</template>

21.<h4 slot="slot2">使用slot属性--该内容将显示在具名插槽slot2内。</h4>

22.</child>

23.</div>

24.<script type="text/javascript">

25.// 子组件中设有3个slot,其中两个为具名插槽,一个为匿名插槽

26.const Comp1 = {

27.template: `

28.<div class="comp">









29.<h4>子组件空间</h4>

30.<slot name='slot1'></slot>

31.<slot name='slot2'></slot>

32.<slot></slot>

33.<p>以上是具名插槽与匿名插槽的使用区别。</p>

34.</div>

35.`,

36.};

37.const App = {

38.components: { child: Comp1 },

39.};

40.const app = Vue.createApp(App);

41.app.mount("#app");

42.</script>

43.</body>

44.</html>






上述代码中,第14~23行定义父组件,并在父组件中使用子组件child,在子组件标记内添加<p>、<template>、<h3>等标记,其中<p>标记用于匹配匿名插槽,<template>和<h3>标记用于匹配两个不同的具名插槽; 第26~36行定义子组件Comp1,并在App组件中定义components选项,局部注册子组件child(第38行)。在子组件Comp1中使用两个具名插槽和一个匿名插槽。

5.3.3作用域插槽

Vue 2.6.0引入vslot指令,提供支持slot和slotscope属性的API替代方案https://github.com/vuejs/rfcs/blob/master/activerfcs/0001newslotsyntax.md。在接下来所有2.x版本中slot和slotscope属性仍会被支持,但已经被官方废弃且不会出现在Vue 3.0中。作用域插槽可用作一个能被传递数据的可重用模板。

匿名插槽和具名插槽的内容和样式皆由父组件决定,即显示什么内容和怎样显示都由父组件决定; 作用域插槽的样式由父组件决定,内容却由子组件控制。简单来说,匿名插槽和具名插槽不能绑定数据,作用域插槽是一个带绑定数据的插槽。

获取子组件slot中携带的数据的关键步骤如下。

(1) 在slot元素上使用vbind指令绑定一个特定属性,这个属性称为插槽prop。



<slot v-bind: customAttribute="childDataOptions"></slot>






其中,customAttribute为用户自定义的属性,为父组件提供数据对象; childDataOptions为子组件data选项中的属性或对象。

(2) 在父组件访问绑定到slot插槽上的prop对象。



<template v-slot:default|slotname="slotProps">

 <p>来自父组件的内容</p>

 <p>{{slotProps. customAttribute }}</p>

</template>






使用vslot指令时可以绑定相应的具名插槽或匿名插槽(默认名称为default,也可以省略)。slotProps为子组件上绑定的数据(插槽的prop对象)。如果自定义属性customAttribute是对象,可以通过slotProps.customAttribute.xxx使用绑定的数据。

【例512】作用域插槽实战。代码如下,页面效果如图517所示。



图517作用域插槽





1.<!-- vue-5-12.html -->

2.<!DOCTYPE html>

3.<html lang="en">

4.<head>

5.<meta charset="UTF-8" />

6.<title>作用域插槽的应用</title>

7.<script type="text/javascript" src="../js/vue.global.js"></script>

8.<style type="text/css">

9..mylist {width: 500px;border: 1px dashed black;padding: 10px;}

10.</style>


11.</head>


12.<body>

13.<div id="app">

14.<p>作用域插槽的应用:来自父组件的内容</p>

15.<h3>使用v-slot:default指令-匿名插槽获取子组件数据</h3>

16.<child>

17.<template v-slot:default="props">

18.<span>{{props.item.id}}--{{props.item.name}}--{{props.item.age}}&nbsp;</span>

19.</template>

20.</child>

21.<h3>使用v-slot:user指令-具名插槽获取子组件数据</h3>

22.<child>

23.<template v-slot:user="props">

24.<span>{{props.item.id}}--{{props.item.name}}--{{props.item.age}}&nbsp;</span>

25.</template>

26.</child>

27.</div>

28.<script>

29.// 创建Vue实例

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

31.app.component("child", {

32.data() {

33.return {

34.items: [

35.{ id: 1, name: "储久良", age: 50 },

36.{ id: 2, name: "张晓兵", age: 40 },

37.{ id: 3, name: "王大伟", age: 35 },

38.{ id: 4, name: "陈小娟", age: 28 },

39.],

40.};

41.},

42.template: `

43. <div class='mylist'>









44. <slotv-for='item in items' :item='item'></slot>

45.<slot name='user' v-for='item in items' :item='item'></slot>

46. </div>

47.`,

48.});

49.app.mount("#app"); // 实例挂载

50.</script>

51.</body>

52.</html>






上述代码中,第16~20行使用子组件child,并在其中通过带vslot:default指令的template元素使用作用域插槽,将包含所有插槽prop的对象命名为props(临时变量),并使用props.item.xxx的形式获取子组件提供的数据; 第22~26行使用子组件child,并在其中通过vslot:user指令的template元素使用作用域插槽; 第31~48行全局定义子组件child,在模板选项中插入两个插槽(一个匿名、一个具名),绑定item属性,循环遍历items数组变量中的所有数据,其中vfor='item in items'中的item是临时变量。而绑定在插槽slot元素上的item属性称为“插槽prop”,在父级作用域中,可以使用带值的vslot指令定义子组件提供的插槽prop的名称,方法如第18行和第24行中的{{props.item.id}}。

5.3.4动态插槽名

动态指令参数也可以用在vslot上,定义动态插槽名。部分示例代码如下。



<my-child>

<template v-slot:[dynamicSlotName]>

...

</template>

</my-child>






动态插槽名需要使用方括号来包裹,dynamicSlotName是一个变量,然后在App组件的data选项中定义动态插槽名,并赋初值。通过其他事件改变动态插槽名,达到动态渲染子组件。

【例513】动态插槽名实战。代码如下,页面效果如图518和图519所示。



1.<!-- vue-5-13.html -->

2.<!DOCTYPE html>

3.<html>

4.<head>

5.<meta charset="UTF-8" />

6.<title>动态插槽名的应用</title>

7.<script src="../js/vue.global.js" type="text/javascript"></script>

8.</head>

9.<body>

10.<div id="app">

11.<h3>插槽名动态更新</h3>

12.<child>

13.<template v-slot:[dynslot]>

14.<h3>动态切换插槽:显示重要信息!</h3>

15.</template>

16.</child>

17.<button @click="changeSlot">更新插槽名称</button>

18.</div>

19.<script>

20.const child = {

21.template: `









22.<div>

23. <p><slot name='slot1'>插槽1:欢迎使用动态插槽!</slot></p>

24. <p><slot name='slot2'>插槽2:欢迎使用动态插槽!</slot></p>

25.</div>

26.`,

27.};

28.const App = {

29.data() {

30.return {dynslot: "",flag: true,};

31.},


32.components: { child,},

33.methods: {

34.changeSlot() { // 先根据flag值,给动态参数赋值为指定插槽名

35.this.dynslot = this.flag ? "slot1" : "slot2";

36.this.flag = !this.flag;// 然后flag值取反,为下次切换做准备

37.console.log(this.flag + "--" + this.dynslot);// 控制台输出

38.},

39.},

40.};

41.const app = Vue.createApp(App); // 创建Vue实例


42.app.mount("#app");//挂载

43.</script>

44.</body>

45.</html>








图518动态插槽名默认为空时的页面效果




图519单击按钮后动态插槽名被重新赋值后的页面效果


当第1次单击“更新插槽名称”按钮时,flag值为true,将slot1赋给了dynslot,此时插槽1的默认内容被父组件传递的内容所替换,而插槽2显示的还是默认信息,如图519所示。当第2次单击“更新插槽名称”按钮时,flag值为false,将slot2赋给了dynslot,此时插槽2的默认内容被父组件传递的内容所替换,而插槽1显示的还是默认信息。继续单击按钮会继续进行动态赋值。

本章小结

本章主要介绍了组件基础、组件间通信和插槽。

在组件基础中,重点介绍了组件的命名规范和注册方法。Vue组件中通常采用camelCase(驼峰式)或 kebabcase(短横线分隔式)命名方式。组件可以全局注册,也可以局部注册。

在组件间通信中,主要讲解了父组件向子组件传值、子组件向父组件传值和父链与子组件索引等。在子组件中通过props使用父组件传递过来的数据,这是单向传递,只能从父组向子组件传递。子组件用$emit()触发自定义事件,父组件通过$on()或von:eventname侦听子组件的事件,父组件也可以使用vmodel指令通过input事件传递数据。当然,也可以通过父链与子组件索引传递数据。

在插槽中,重点讲解了匿名插槽、具名插槽、作用域插槽及动态插槽名的应用场景和基本语法。


扫一扫



自测题


练习5

1. 选择题

(1) 下列组件命名符合驼峰式命名方式的是()。

   

A. MYCOMPONENETB. mycomponent
C. MyComponentD. myComponent

(2) 在Vue 3.0中,父组件向子组件传递两个数据data1和data2时,子组件下正确的声明方式是()。

A. props:['data1','data1']B. props:{'data1','data1'}

C. props:[data1:String,data1:Number]D. props:(data1,data2)

(3) 当父组件使用vmodel指令侦听事件时,子组件中可以使用()发送数据。

A. this.$emit('input',Data)B. this.emit('input',Data)

C. this.$emit('vmodel',Data)D. this.emit('vmodel',Data)

(4) 下列具名(slotName)插槽缩写方式中正确的是()。

A. vslot: slotNameB. :slotName
C. #slotNameD. @slotName

(5)  父组件通过元素的slot属性可以将数据信息传递至指定()属性的具名插槽中。

A. nameB. slotC. vslotD. slotscope

(6)  下列动态插槽名(dyncSlotName)定义中正确的是()。

A. <template vslot:dynSlotName>B. <template vslot:"dyncSlotName">

C. <template slot=[dynSlotName]>D. <template vslot:[dynSlotName]>

2. 填空题

(1) 全局注册组件必须使用指令来实现,局部注册组件必须在App实例的选项中注册。

(2) 在HTML模板中,myComponent组件只能使用命名方式,引用格式如。

(3) 在单组件文件的template元素中,引用PascalCasedComponent组件,可能的格式有: 、、。

(4) 插槽通常分为、具名插槽、作用域插槽等。父组件可以使用slot属性将信息插入指定的具名插槽中。

(5) vslot指令一般添加在元素上。其他元素上可以使用属性来设置。

3. 简答题

(1) 简述组件中data与props属性在使用上的区别。

(2) 简述兄弟组件间通信常用的方法。

项目实战5

1. 父子组件间通信实战——计算任意区间连续整数累加和


扫一扫



视频讲解


【实战要求】

(1) 学会定义Vue组件,并学会全局注册组件。

(2) 学会使用CSS定义<div>、<button>、<input>标记的样式。

(3) 学会使用父子组件通信实现两个组件间相互通信,能够使用props、$emit()传递数据。

(4) 学会在Vue 3.x中使用Options API定义选项。

(5) 学会在<body>标记中定义模板(<template id='temp1'> </ template >)供组件引用(通过ID号引用,如template:'#temp1')。

【设计要求】

参照如图520所示页面,完成“计算任意区间连续整数累加和”项目设计。具体要求如下。



图520计算任意区间连续整数累加和初始界面


(1) 定义两个组件。组件名称分别为sendData和receiveData,组件的内容如图520所示。

(2) 在sendData组件中,在两个文本框中输入起始数和终止数后,通过props 下发给receiveData组件,receiveData组件中数据同步渲染。单击“计算累加和并发送给父组件”按钮后,执行事件处理函数sum(number1,number2),完成传递数据合法性检查、累加和计算和发送结果。当数据合法检查通过后,将计算结果显示在子组件中,同时发送数据给sendData组件,累加和会同步渲染在receiveData组件中,如图521所示。



图521输入数据合法情况下的页面效果


(3)  当父组件sendData中输入的起始数大于或等于终止数时,子组件receiveData会向父组件传递错误消息,并在父组件相关的<div>标记中显示红色的错误信息“父组件中起始数不能大于或等于终止数!请重新输入数据!”,如图522所示。



图522输入数据不合法情况下的页面效果


(4) 页面样式要求如下。

 父组件#app样式: 有边界(上下为0,左右自动),宽度为600px,高度为370px,有边框(15px,实线,#CACACA),内容居中对齐,圆角边框(半径为25px)。

 <button>和<input>样式: 高度为24px,圆角边框(半径为10px),有边框(1px,虚线,#0000EE)。

【实战步骤】

(1) 建立HTML文件。项目文件名为vueex51.html,引用vue.global.js文件。在<body>标记中插入id为app的<div>,并在其中引用组件<senddata> </senddata>。

(2) 定义组件。按图520~图522效果分别定义sendData、receiveData组件。
其中,子组件receiveData发送计算结果数据和出错信息。



this.$emit("compute", this.result);// 数据合法,发送计算结果

this.$emit("error","父组件中起始数不能大于或等于终止数!请重新输入数据!");






(3) 组件通信测试。在sendData、receiveData组件中分别输入相关内容后,单击“计算累加和并发送给父组件”按钮,查看运行结果,调试并完善代码。

2. 插槽综合实战——父子组件数据传递


扫一扫



视频讲解


【实战要求】

(1) 学会引用vue.global.js文件,学会定义Vue组件,构建基本HTML文件。

(2) 学会使用CSS定义<div>、<button>、<ul>等标记的样式。

(3) 利用props属性实现父组件向子组件传值,子组件通过$emit()向父组件发送数据。

(4) 学会定义匿名插槽、具名插槽和作用域插槽,利用作用域插槽向父组件提供数据。

(5) 学会创建Vue实例和配置相关选项,会定义相关方法实现相关功能。

【设计要求】

参照如图523和图524所示的页面,完成项目设计。具体设计要求如下。



图523插槽综合应用初始页面




图524单击“修改数据”按钮后页面效果


(1) 在父组件中定义使用子组件标记,其标记为<son1> </son1>,并绑定name、age、sex属性值。侦听自定义事件updatedata,绑定事件处理函数updateData()。定义两个template模板,插槽名分别为top和bottom,第2个模板为作用域插槽,使用子组件中绑定的courses对象中的数据。

(2) 在子组件中插入3个插槽,名称分别为top、default、bottom。

(3) 在匿名插槽中显示姓名、年龄、性别等信息,并实现“修改数据”按钮的功能。单击“修改数据”按钮后,执行updateData()函数,将姓名、年龄和性别封装在data1对象中,并发送给父组件。父组件中显示同步渲染,更新后页面效果如图524所示。作用域插槽中绑定的课程信息为“计算机组成原理,56元”“Vue.js前端框架技术与实战,69.8元”“Web前端开发技术实验与实践,45元”。


【实战步骤】

(1) 建立HTML文件。项目文件名为vueex52.html,引用vue.global.js文件。在<body>标记中分别插入<div>、<template>和<script>标记,并在<div>中插入子组件son1。

(2) 定义父组件。按照设计要求(1)完成父组件的设计。

(3)  定义子组件。要求使用 app.component("son1", {})全局注册子组件son1。按照设计要求(2)和设计要求(3)完成子组件的设计,同时通过父组件向子组件传值(name、age、sex),供子组件使用。

(4) 定义样式。

 子组件son1中<div>的son1类样式: 内容居中显示,有边界(1px,虚线,#1B5764)。

 <button>样式: 宽度为200px,高度为35px,有边界(1px,虚线,#CCCCCC),圆角边框(半径为20px)。

 <ul>样式: 列表样式类型为none。

 插槽中<div>的box类样式: 背景颜色为#DAFBFC。

(5) 调试页面并运行。代码设计完成后,通过浏览器运行查看页面效果,调试并完善代码。