vue学习组件化开发
Posted sherrycat
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue学习组件化开发相关的知识,希望对你有一定的参考价值。
一、注册组件
1.全局注册
利用Vue.component()
方法,先传入一个自定义组件的名字,然后传入这个组件的配置。
然后就可以在Vue实例挂载的DOM元素中使用它。
Vue.component(‘mycomponent‘,{ template: `<div>这是一个自定义组件</div>`, data () { return { message: ‘hello world‘ } } })
2.局部注册
在某个Vue实例中注册只有自己能使用的组件。
var app = new Vue({ el: ‘#app‘, data: { }, components: { ‘my-component‘: { template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`, } } })
<div id="app"> <mycomponent></mycomponent> <my-component></my-component> </div> <script> var app = new Vue({ el: ‘#app‘, data: { }, components: { ‘my-component‘: { template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`, } } }) </script>
注意:
(1)组件的模板只能有一个根元素。下面的情况是不允许的:
template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div> <button>hello</button>`,
(2)组件中的data必须是函数。
可以看出,注册组件时传入的配置和创建Vue实例差不多,但也有不同,其中一个就是data
属性必须是一个函数。
这是因为如果像Vue实例那样,传入一个对象,由于JS中对象类型的变量实际上保存的是对象的引用
,所以当存在多个这样的组件时,会共享数据,导致一个组件中数据的改变会引起其他组件数据的改变。
而使用一个返回对象的函数,每次使用组件都会创建一个新的对象,这样就不会出现共享数据的问题来了。
(3)DOM模板的解析。
当使用 DOM 作为模版时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 html 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。尤其像这些元素 <ul>
,<ol>
,<table>
,<select>
限制了能被它包裹的元素,而一些像 <option>
这样的元素只能出现在某些其它元素内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table> <my-row>...</my-row> </table>
自定义组件 <my-row>
被认为是无效的内容,因此在渲染的时候会导致错误。这时应使用特殊的 is
属性:
<table> <tr is="my-row"></tr> </table>
也就是说,标准HTML中,一些元素中只能放置特定的子元素,另一些元素只能存在于特定的父元素中。比如table
中不能放置div
,tr
的父元素不能div
等。所以,当使用自定义标签时,标签名还是那些标签的名字,但是可以在标签的is
属性中填写自定义组件的名字。
应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:
<script type="text/x-template">
- javascript 内联模版字符串
.vue
组件
其中,前两个模板都不是Vue官方推荐的,所以一般情况下,只有单文件组件.vue
可以忽略这种情况。
参考文献:
https://blog.csdn.net/howgod/article/details/90695332
二、组件的属性
1.自定义属性props
Vue组件通过props属性来声明一个自己的属性,然后父组件就可以往里面传递数据。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> </head> <body> <div id="app"><child :title="message"></child></div> //将message传给title <script> Vue.component(‘child‘,{ //组件名称 template:‘<h1>{{title}}</h1>‘, //组件可以用到title中massage传过来的值 props:[‘title‘] //这里props是一个字符串数组 }) var app = new Vue({ el:‘#app‘,data:{message:‘Hello World‘} }) </script> </body> </html>
props除了数组,也可以是一个对象,此时对象的键对应的props的名称,值又是一个对象,可以包含如下属性:
type: :类型,可以设置为:String、Number、Boolean、Array、Object、Date等等 ;如果只设置type而未设置其他选项,则值可以直接用类型,例如:props:{title:Object}
default :默认值
required :布尔类型,表示是否必填项目
validator :自定义验证函数
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <title>Document</title> </head> <body> <div id="app"><child></child></div> <script> Vue.component(‘child‘,{ template:‘<h1>{{title}}</h1>‘, //键为title 值为{default:‘Hello World‘},是一个对象,包含了default属性 props:{title:{default:‘Hello World‘}} //这里定义的title是个对象,含有默认值 }) var app = new Vue({ el:‘#app‘ }) </script> </body> </html>
通过v-bind绑定属性值
v-bind绑定属性值的一个特性:一般情况下,使用v-bind给元素特性(attribute)传递值时,Vue会将""中的内容当做一个表达式。比如:
<div attr="message">hello</div>
上面这样,div
元素的attr
特性值就是message
。
而这样
<div v-bind:attr="message">hello</div>
这里的message
应该是Vue实例的data的一个属性,这样div
元素的attr
特性值就是message
这个属性的值。
所以如果想传递正确的数值,应该使用v-bind
传递,这样就能把传递的值当做一个表达式来处理,而不是字符串。
之所以说是一般情况,是因为class
和style
特性并不是这样。用v-bind:class
和class
传入正常的类名,效果是一样的,因为对于这两个特性,Vue采用了合并而不是替换的原则。
动态绑定特性值
根据上面,想要把父组件的属性绑定到子组件,应该使用v-bind
,这样,父组件中数据改变时能反映到子组件。
注意,根据父组件传递给子组件的属性类型的不同,当在子组件中更改这个属性时,会有以下两种情况:
(1)当父组件传递的属性是引用类型时,在子组件中更改相应的属性会导致父组件相应属性的更改。
<div id="app2"> <div>这是父组件的parentArray:{{parentArray}}</div> <my-component :child-array="parentArray"></my-component> </div> <script> Vue.component(‘my-component‘, { template: ` <div>这是接收了父组件传递值的子组件的childArray: {{childArray}} <br> <button type="button" @click="changeArray"> 点击我改变父元素的parentArray</button> </div>`, props: [‘childArray‘], data () { return { counter: 1 } }, methods: { changeArray () { this.childArray.push(this.counter++) } } }) new Vue({ el: ‘#app2‘, data: { parentArray: [] } }) </script>
(2)当父组件传递值为基本类型时,在子组件中更改这个属性会报错。正确的做法是,在父组件中绑定属性值时,加上.sync
修饰符。
<my-component :child-array.sync="parentArray"></my-component>
然后在子组件中改变相应的属性
methods: { changeArray () { this.counter++ this.$emit(‘update:childArray‘, this.counter) } }
使用$ref实现父子通信
父子组件通信参考https://www.jb51.net/article/140581.htm
(1)如果ref用在子组件上,指向的是组件实例,可以理解为对子组件的索引,通过$ref可能获取到在子组件里定义的属性和方法。
(2)如果ref在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,通过$ref可能获取到该DOM 的属性集合,轻松访问到DOM元素,作用与JQ选择器类似。
通过ref实现通信:
<!-- 父组件 --> <template> <div> <h1>我是父组件!</h1> <child ref="msg"></child> </div> </template> <script> import Child from ‘../components/child.vue‘ export default { components: {Child}, mounted: function () { console.log( this.$refs.msg); this.$refs.msg.getMessage(‘我是子组件一!‘) } } </script>
<!-- 子组件 --> <template> <h3>{{message}}</h3> </template> <script> export default { data(){ return{ message:‘‘ } }, methods:{ getMessage(m){ this.message=m; } } } </script>
也可以参考链接:https://www.jianshu.com/p/623c8b009a85。说得很清楚。可以看到,父组件通过import的方式导入子组件,并在components属性中注册,然后子组件就可以用标签的形式嵌进父组件了。
在 <child ref="msg"></child>中,将子组件实例指给了$ref,所以通过this.$refs.msg.getMessage(‘我是子组件一!‘)调用到子组件的getMessage方法,将参数传递给子组件。
prop和$ref之间的区别
(1)prop 着重于数据的传递,它并不能调用子组件里的属性和方法。像创建文章组件时,自定义标题和内容这样的使用场景,最适合使用prop。
(2)$ref 着重于索引,主要用来调用子组件里的属性和方法,其实并不擅长数据传递。而且ref用在dom元素的时候,能使到选择器的作用,这个功能比作为索引更常有用到。
使用$emit实现子父通信
prop和$ref主要都是父组件向子组件通信,而通过$emit 实现子组件向父组件通信。
vm.$emit( event, arg )
$emit 绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数。
<template> <div> <h1>{{title}}</h1> <child @getMessage="showMsg"></child> //getMessage是子组件的自定义事件, //父组件通过 @事件名="方法" = > @getMessage = "showMsg", //将自定义事件getMessage的参数传给父组件的方法showMsg //因此实现了子组件向父组件传值 </div> </template> <script> import Child from ‘../components/child.vue‘ export default { components: {Child}, data(){ return{ title:‘‘ } }, methods:{ showMsg(title){ this.title=title; } } } </script>
<template> <h3>我是子组件!</h3> </template> <script> export default { mounted: function () { this.$emit(‘getMessage‘, ‘我是父组件!‘) //子组件通过$emit 绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件 } } </script>
三、组件的事件
Vue事件分为普通事件和修饰符事件,这里我们主要介绍修饰符事件。
Vue 提供了大量的修饰符封装了这些过滤和判断,让开发者少写代码,把时间都投入的业务、逻辑上,只需要通过一个修饰符去调用。我们先来思考这样问题:怎样给这个自定义组件 custom-component 绑定一个原生的 click 事件?
<custom-component>组件内容</custom-component>
如果你的回答是<custom-component @click="xxx">
,那就错了。这里的 @click 是自定义事件 click,并不是原生事件 click。绑定原生的 click 是这样的:
<custom-component @click.native="xxx">组件内容</custom-component>
实际开发过程中离不开事件修饰符,常见事件修饰符有以下这些:
1.表单修饰符
(1)lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 。你可以添加 lazy
修饰符,从而转变为使用 change
事件进行同步。适用于输入完所有内容后,光标离开才更新视图的场景。
(2)trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
这个修饰符可以过滤掉输入完密码不小心多敲了一下空格的场景。需要注意的是,它只能过滤首尾的空格!首尾,中间的是不会过滤的。
(3)number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="value" type="text" />
2.事件修饰符
<!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a>
三、插槽
1.什么是插槽
插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。
如下代码:
(1)在子组件中放一个占位符
(2)在父组件中给这个占位符填充内容:
(3)展示的效果
如果子组件中没有放插槽,同样的父组件中在子组件中填充内容,会是啥样的:
(1)子组件代码无插槽:
(2)父组件照常填充内容:
(3)展示的效果:
所以如果子组件没有使用插槽,父组件如果需要往子组件中填充模板或者html, 是没法做到的。
2.插槽的使用
最简单的使用如上所示,不再赘述。接下来看看,插槽其他使用场景。
(1)具名插槽
具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
1. 子组件的代码,设置了两个插槽(header和footer):
2. 父组件填充内容, 父组件通过 v-slot:[name] 的方式指定到对应的插槽中
3.展示的效果:
接下来再来看看,父组件中填充内容的时候,顺序调换下,看下能不能内容能不能对应上:
1. 子组件代码不变,父组件代码中填充顺序调换下(如图,在父组件中,footer 和 header 的填充位置对换):
2. 展示的效果:
由此看出,即使父组件对插槽的填充的顺序打乱,只要名字对应上了,就可以正确渲染到对应的插槽中。即: 父组件填充内容时,是可以根据这个名字把内容填充到对应插槽中的.
(2)默认插槽
默认插槽就是指没有名字的插槽,子组件未定义的名字的插槽,父级将会把未指定插槽的填充的内容填充到默认插槽中。
示例代码如下:
1.子组件代码定义了一个默认插槽:
2.父组件给默认插槽填充内容:
3. 展现的内容
注意:
1. 父级的填充内容如果指定到子组件的没有对应名字插槽,那么该内容不会被填充到默认插槽中。
2. 如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就“不会”填充到子组件的任何一个插槽中。
3. 如果子组件有多个默认插槽,而父组件所有指定到默认插槽的填充内容,将“会” “全都”填充到子组件的每个默认插槽中。
(3)作用域插槽
作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
作用域插槽要求,在slot上面绑定数据:
<slot name="up" :data="data"></slot> export default { data: function(){ return { data: [‘zhangsan‘,‘lisi‘,‘wanwu‘,‘zhaoliu‘,‘tianqi‘,‘xiaoba‘] } }, }
插槽最后显示不显示是看父组件有没有在child下面写模板。(子组件中插入占位符,父组件在引入子组件时写模板填充占位符。)
写了,插槽就总得在浏览器上显示点东西,东西就是html该有的模样,没写,插槽就是空壳子,啥都没有。
有html模板的情况,就是父组件会往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?
正因为作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。
child
内,通过slot-scope
接受一个对象参数,可以根据参数,定制每个代办项。slot-scope="{todo}"
可以结构获取参数。可是定义多个作用域插槽。下面的例子,父组件提供了三种样式(分别是flex、ul、直接显示),都没有提供数据,数据使用的都是子组件插槽自己绑定的那个数组(一堆人名的那个数组)。
父组件:
<template> <div class="father"> <h3>这里是父组件</h3> <!--第一次使用:用flex展示数据--> <child> <template slot-scope="user"> //通过slot-scope接受了对象参数 <div class="tmpl"> <span v-for="item in user.data">{{item}}</span> </div> </template> </child> <!--第二次使用:用列表展示数据--> <child> <template slot-scope="user"> <ul> <li v-for="item in user.data">{{item}}</li> </ul> </template> </child> <!--第三次使用:直接显示数据--> <child> <template slot-scope="user"> {{user.data}} </template> </child> <!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽--> <child> 我就是模板 </child> </div> </template>
子组件:
<template> <div class="child"> <h3>这里是子组件</h3> // 作用域插槽 <slot :data="data"></slot> </div> </template> export default { data: function(){ return { data: [‘zhangsan‘,‘lisi‘,‘wanwu‘,‘zhaoliu‘,‘tianqi‘,‘xiaoba‘] } } }
在vue v2.6.0中,新引入了v-slot指令,他取代了slot和slot-scope这两个目前已经被废弃但是为被移除的特性。
// 根组件 <template> <div> <mo> <template v-slot:header="slotProps"> <h1>{{slotProps.header + ‘ ‘ + msg}}</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here‘s some contact info</p> </template> </mo> </div> </template> <script> import mo from ‘./module.vue‘ export default { components: { mo }, data() { return { msg: ‘这是根组件的消息‘ } } } </script> // 子组件 <template> <div> --header start-- <header> <slot name="header" :header="header"></slot> </header> --header over-- <div></div> --default start-- <slot></slot> --default over-- <div></div> --footer start-- <footer> <slot name="footer"></slot> </footer> --dooter over-- </div> </template> <script> export default { data() { return { header: ‘来自子组件的头部消息‘ } } } </script> <style scoped> </style>
a.组件中可以使用template标签,加v-slot指令制定具名插槽,当没有指定插槽name时,默认出口会带有隐含的名字“default”。
b.根组件可以利用v-slot:header="slotProps"接受组件中的消息,组件中只需要在<slot name="header" :header="header"></slot>就可以了
c.如果被提供的内容只有一个默认插槽时,组件的标签可以直接被当做插槽的模板来使用<mo v-slot="slotProps">
d.动态参数也可是使用到插槽当中<mo v-slot=[dynamicSlotName]>
e.v-slot的缩写是#,但是如果使用#的话,必须始终使用具插槽来代替
<mo #default="slotProps">
以上是关于vue学习组件化开发的主要内容,如果未能解决你的问题,请参考以下文章