组件是Vue另一大特性,扩展HTML代码。在一个页面上,页面与组件的对应关系往往如下:

1.组件注册

1.1全局组件注册:

Vue.component("navbar",{template:`<nav><button @click="handleBack()">返回</button><span>{{ title }}</span></nav>`,data(){return{title:'XXX'}},methods:{handleBack(){console.log('handlerBack');}}
})使用:<navbar></navbar>

在浏览器渲染时,会把template模板的内容复制到使用处。 所以说在组件里computed、watch都可以写,也可以使用差值表达式。但是有三个点需要格外注意:

首先就是在组件里,自己的data必须是函数式写法,必须有返回值!

第二就是,在组件里template只能有一个根节点!如果有有两个根节点就会报错。比如下面的情况就会报错,因为有两个根节点:

Vue.component("navbar",{template:`<nav><button @click="handleBack()">返回</button><span>{{ title }}</span></nav><nav2></nav2>`
})

第三就是组件与组件之间的数据的相互隔离独立的,父子组件之间可以通过一定的方法进行通信。

1.2 局部组件注册

局部组件注册的组件,只能组件内自己使用,别的组件用不了。

Vue.component("navbar",{template:`<nav><button @click="handleBack()">返回</button><span>{{ title }}</span></nav>`,data(){return{title:'XXX'}},component:{navbarChild:{template:`<div>只是navbar的子组件</navbar>`}},methods:{handleBack(){console.log('handlerBack');}}
})

在局部组件里,也可以继续使用监听器、计算属性、方法等一系列东西,唯一的特点就是局部组件只能父组件自己内部使用。

还可以在外部定义局部组件,在父组件中注册使用:

const NavBarChild = {template:`<div>只是navbar的子组件</navbar>`
}
Vue.component("navbar",{template:`<nav><button @click="handleBack()">返回</button><span>{{ title }}</span></nav>`,data(){return{title:'XXX'}},component:{ 'nav-bar-child': NavBarChild},methods:{handleBack(){console.log('handlerBack');}}
})

Vue会在驼峰式和连线式命名上自动转换。

2.组件通信(父传子属性down,子传父Event up)

2.1父传子(属性传值法,来自老父亲的叮嘱)

父传子最基本的方法就是通过Props组件。父组件在调用子组件的时候,通过在子组件的标签上加自定义的属性值,子组件在内部通过Props接受组件值。代码如下:


父组件通过在子组件标签上添加属性值,向子组件传递信息:
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>子组件通过props属性来接收,在子组件里就可以通过自己的data来使用。
Vue.component('blog-post', {props: ['title'],template: '<h3>{{ title }}</h3>'
})老父亲的叮嘱也可以通过v-bind来动态绑定,如:
<blog-post :title="titles"></blog-post>
这样的话,titles就是父组件的data里面的一个变量
还有就是,如果要传布尔值给子组件,必须要用v-bind。
不然的话,true或false会被当成字符串传给子组件

属性传值还有一个知识点,就是属性验证。属性验证就是在传入属性值时,同时将期望接收的数据类型也传进来。

接收两个属性值,一个是字符串类型,还有一个是布尔类型
Vue.component('blog-post', {props: {title:string,is_show:boolean},template: '<h3>{{ title }}</h3>'
})

最复杂的就是,还有一种对象的形式:

Vue.component('blog-post', {props: {title: {type: Array,default: ''}},template: '<h3>{{ title }}</h3>'
})default后面还可以传函数,比如:
Vue.component('blog-post', {props: {title: {type: Array,default: () => {return 123}}},template: '<h3>{{ title }}</h3>'
})还可以进一步增加更多的校验功能:
Vue.component('blog-post', {props: {title: {type: Array,validator: function(value) {return value < 1000},default: ''}},template: '<h3>{{ title }}</h3>'
})

在向子组件传值是,也可以将一个对象直接传给子组件,从子组件里面再对对象解析。

父节点是这样的:
new Vue({el: '#blog-post-demo',data: {posts: [{ id: 1, title: 'My journey with Vue' },{ id: 2, title: 'Blogging with Vue' },{ id: 3, title: 'Why Vue is so fun' }]}
})子组件里面想使用post值,父组件可以这样传值:
<blog-postv-for="post in posts"v-bind:key="post.id"v-bind:title="post.title"
></blog-post>如果对象里面的元素值太多,这么写就会很累很累。
所以可以直接将post作为一个整体传给子组件。
如下:
<blog-postv-for="post in posts"v-bind:key="post.id"v-bind:post="post"
></blog-post>这样在子组件接收的时候,可以这样:
Vue.component('blog-post', {props: ['post'],template: `<div class="blog-post"><h3>{{ post.title }}</h3><div v-html="post.content"></div></div>`
})
就可以直接使用了。

除了传对象,还可以传函数。

const app = Vue.createApp({data() {return { num: ()=> { alert(123) } }},template: `<div><test :content="num" /></div>`
})app.component('test'. {props: {content: Function,},methods: {handleClick(){this.content()}},template: `<div @click-"this.handleClick"></div>`
})

如果父组件通过xx-yy来传属性,子组件要使用xxYy的方式接收。

还有个概念叫单向数据流, 就是父组件向子组件传递数据,子组件接收后只能使用父组件传过来的数据,但是子组件不能修改父组件传递过来的数据。

如果父组件传值,子组件不接收会怎么办呢?这个就是所谓的Non-props属性,此时,会将父组件传的属性加在子组件的最外层标签上。一般适用的地方就是为子组件加上style样式。如果子组件中不只一个根节点,那么Non-props属性就不会加在子组件上,如果此时想加上可如下写:

app.component('demo', {inheritAttrs: false,template: ` <div></div><div v-bind="$attrs"></div>
`
})如果父组件传递多个属性,但是子组件的某个最外层标签想获取父组件传递的某一个属性,可如下写:
app.component('demo', {inheritAttrs: false,template: ` <div></div><div v-bind:msg="$attrs.msg"></div>
`
})

在子组件中,无论是否用Props接收父组件传值,在子组件内部的方法里都可以使用this.$attrs来获取属性值。如this.$attrs.msg1.

如果子组件又不想在自己的最外层标签上显示属性值怎么办,只要在子组件中加上inheritAttrs: false,具体如下:

app.component('demo', {inheritAttrs: false,template: `<div></div>`
})

2.2子传父(通过事件,儿子通过event找爸爸说事情)

父组件在调用子组件时候,在属性值里面留一个事件(给儿子一个电话),子组件通过该事件来触发父组件(打电话)。

父组件:
<blog-post...v-on:enlarge-text="enlarge_text"
></blog-post>如上,父组件在调用组件标签的时候,递给子组件一个名为enlarge_text的电话号码,告诉子组件,有事就打这个电话号码。子组件里:
Vue.component('blog-post', {props: ['post'],template: `<div class="blog-post"><h3>{{ post.title }}</h3><button @click='handle_click'>Enlarge text</button><div v-html="post.content"></div></div>`,methods:{handle_click(){this.$emit('enlargeText','hello,father')  触发事件用驼峰,监听事件用短横线}}
})如上,子组件通过 this.$emit()来打电话,这个第一个函数传入父组件传进来的值,也就是放老父亲给的电话号码,第二个参数就是要传达的消息。

可以在组件中增加emits属性来集中管理事件。

app.component('demo', {emits: ['add'],methods: {handleClick() {this.$emit('add')}}
})在子组件中的事件,如果在写emits之后,但是没有在emits中注册,那么事件就会发不出去了。此外,emits中还可以写函数,用于检验传出去的参数是否满足要求。app.component('demo', {props: ['counter'],emits: {add: (count) => {if(count > 0){return true}return false}},methods: {handleClick() {this.$emit('add', this.count + 3)}}
})

上述的过程其实就是父组件传值给子组件,子组件通过事件将修改值再传给父组件。这种方式还有一种骚操作,但是我觉得平时可以不怎么用。

const app = Vue.createApp({data() {return { count: 1 }},template:`<demo v-model="count" />`
})app.component('demo', {props: ['modelValue'],methods: {handleClick() {this.$emit('update:modelValue', this.modelValue + 3)}},template: `<div @click="handleClick">{{modelValue}}</div>`
})

上述的代码中,modelValue、update:modelValue都是固定写法,不能改变,如果改变就不起作用了。如果非要修改的话,可以采用下述的方法:

const app = Vue.createApp({data() {return { count: 1 }},template:`<demo v-model:app="count" />`
})app.component('demo', {props: ['app'],methods: {handleClick() {this.$emit('update:app', this.app + 3)}},template: `<div @click="handleClick">{{app}}</div>`
})

如果有多个属性,想通过这个方法,可以通过相同的方法加一些属性:

const app = Vue.createApp({data() {return { count: 1,count1: 2}},template:`<demo v-model:count="count" v-model:count1="count1"/>`
})app.component('demo', {props: ['count', 'count1'],methods: {handleClick() {this.$emit('update:count', this.count+ 3)},handleClick1() {this.$emit('update:count1', this.count1 + 3)}},template: `<div @click="handleClick">{{count}}</div><div @click="handleClick1">{{count1}}</div>`
})

如果父组件想传递修饰符怎么办?见下:

const app = Vue.createApp({data() {return { count: 1 }},template:`<demo v-model.uppercase="count" />`
})app.component('demo', {props: {'modelValue': String,'modelModifiers': {   // modelModifiers是固定写法default: () => ({})}},mounted(){console.log(this.modelModifiers)   // 通过this.modelModifiers访问},methods: {handleClick() {if(this.modelModifiers.uppercase){如果存在uppercase修饰符,做的事情写在这里}}},template: `<div @click="handleClick">{{modelValue}}</div>`
})

2.3 ref通信

通过在签上加上ref的属性,就可以通过$refs来访问到绑定此属性的标签,相当于给标签打了标记,然后$refs收集了所用打上ref属性的标签,也就是DOM节点如:

<input type="text" ref="my_text" />在函数中就可以通过this.$refs.my_text.value获取到input框的值

如果在子组件的标签上加上ref属性,那么父组件就可以通过$refs 获取到子组件对象,从而可以获取到子组件所有的数据和方法,示例如下:

子组件:
Vue.component("child"{data:{return{data:'123'}},methods:{getValue(value){console.log(value);}}
})父组件调用
<child ref="my_child"></child>父组件在方法这样使用来传达消息:
this.$refs.my_child.getValue("老父亲给儿子的值")

加上ref属性后,就可以拿到这个子组件对象,从而可以从子组件对象中可以拿到子组件所有的东西。子组件传值给父组件就更简单了,因为父组件拿到子组件的对象后,就等于拿到了子组件的一切了。

但是ref用多了,整个页面会比较乱,到处都是ref。

2.4 bus事件总线

bus事件总监就是超越了父子关系,直接独立于五行之外有一个公共大对象,只要一方面发布消息,一方面订阅消息就行了,也就是中央事件总线。

举个例子,比如微博中的关注,你无需关注你关注的人和你是啥关系,微博就是一个中央平台,你关注的人发布消息,你直接订阅消息就行了。一个用$emit发布,$on来订阅,也就是监听。

那么中央时间总线是啥,其实就是一个空的Vue实例。

子组件一,发送一个自定义的kerwin事件:
Vue.component("child1"{template:`<div>child1<button @click="handleClick">click</button></div>`,methods:{handleClick(){bus.$emit("kerwin","来自child1的问候");}}
})子组件二,发送一个:
Vue.component("child2"{template:`<div>child2</div>`,mounted(){bus.$on("kerwin",(data)=>{console.log("接收消息");console.log(data);}})
})
监听事件在组件被挂载的时候,就要绑定上去。子组件二中的data参数就是传递过来的消息。
中央事件总线
var bus = new Vue();

3.动态组件

如果要动态控制几个组件显示与隐藏,我们一般的思路都是通过在每个调用的标签上,添加v-show的属性,然后徐通过v-show的属性值来对标签进行控制。这样做,要管理的状态量太多,所以通过动态组件的方式进行控制。

假设Vue根组件里有三个子组件:
var vm = new Vue({el:"#app",data:{who:"home"},components:{home:{template:`... ... `},list:{template:`... ... `},sidebar:{template:`... ... `},}
})那么,如何动态调用这三个子组件呢?
可以通过一个神奇的标签:
<component is="home"></component>
这个标签的is属性写哪个组件,component标签就会被渲染成哪个组件。也可以通过给is绑定上v-bind属性,然后对data属性的who变量进行管理就行了。
<component :is=who></component>

如果就按照上面的写法,那么组件之间的切换是通过删除之前的组件,在创建组件的方式进行的,如果之前的组件有用户输入的未提交的值,比如input框里面输入了值但是没有提交,那么通过这种方式切刀其他组件再切回来,那么之前的输入就没有了。再加之,如果经常对

如何解决这个问题?

可以在component标签外再包裹一个keep-alive标签。

<keep-alive><component is="home"></component>
</keep-alive>

4.slot插槽

设想一个场景,如果我们准备为用户封装一个可复用的组件,但是我们自己写出来的组件不可能让所有的用户都可以使用,因为每个用户的需求都是相似的(大体相同,但有区别),那我们在封装组件的时候,把大体相同的部分写上自己的代码,把需要用户自己定制的地方留下来,给用户在使用的时候,把自己需要的东西填写在我们留的空白里就可以了。这就是插槽

插槽的直白理解就是在组件注册的时候,在组件的模板里留一块地方给父组件调用的时候,让父组件可以写自己东西的地方,实现子组件的一种自定义调用的方法。即子组件在自己的模板里放插槽(slot)父组件调用的时候往子组件放插槽地方写东西。

用官方的说法就是内容分发。即混合父组件的内容与子组件自己的模板。

子组件:Vue.component("Swiper",{template:`<div><slot></slot>   // 通过slot留下一块地方给父组件调用的时候,写自己的东西。</div>`
})父组件调用:
<Swiper><ul><li>1</li><li>2</li></ul>
</Swiper>

在调用过程中,父组件模板的内容在父组件作用域内编译,子组件模板的内容在子组件的作用域内编译,换句话就是父模板里调用的数据属性,使用的都是父模板里的数据。子模板里调用的数据属性,使用的都是子模板里的数据。

插槽有一个显著的有点就是,父组件自己定制化填写的内容是可以访问到父组件自己的状态的。

<slot></slot>标签中间可以填写数据,如果父组件调用子组件时没有传入插槽的内容,那么就会默认用slot标签中的内容。

插槽还有一种类型叫具名插槽。

具名插槽通常使用在子组件中有多个插槽的情况,这样的话,每个插槽都会把父组件在调用时候写的东西在每个slot地方渲染一遍。而我们的想法是把在父组件里面写的东西分门别类的放到不同的插槽中。解决这个问题,办法就是:在子组件中,为每个插槽起一个名字,然后父组件在调用的时候,按照子组件插槽的名字进行填写就行,示例如下:

子组件:Vue.component("Swiper",{template:`<div><slot name='a'></slot>   </div><div><slot name='b'></slot>   </div><div><slot name='c'></slot>   </div>`
})父组件调用:
<Swiper><ul v-slot:a> // 还可以简写为<ul #a><li>1</li><li>2</li></ul><ul v-slot:b><li>3</li><li>4</li></ul><ul v-slot:c><li>5</li><li>6</li></ul>
</Swiper>

有时在div标签中使用v-slot:a是没用的,这时需要使用template标签占位符包裹div标签,在template标签上使用v-slot:a的方式来使用具名插槽。

5.作用域组件

有种情况,就是插槽的样式由父组件决定,但是插槽里的数据却是由子组件提供的,那么如何解决这个问题呢?答案就是通过作用域插槽来解决问题。

// 父组件调用子组件插槽,子组件通过属性将数据传递给父组件。
const app = Vue.createApp({template:`<list v-slot="slotProps"><span>{{ slotProps.item }}</span></list>`
})app.component('demo', {data(){ return {list:[1,2,3]}},template: `<div><slot v-for="item in list" :item="item"></slot> /div>`
})还有一种简写的方式,通过解构的运算符:const app = Vue.createApp({template:`<list v-slot="{item}"><span>{{ item }}</span></list>`
})

6.异步组件

异步组件不会调用就立即加载,而是等到Promise得到满足触发时才会加载。

const app = Vue.createApp({template: `<div><async-common-item /></div>`
})app.component('async-common-item',  Vue.defineAsyncComponent(()=>{return new Promise((resolve, reject)=>{})
}))

7. Provide与inject

provide与inject是用于多级组件传值的时候使用,在祖宗组件里利用provide进行声明,在后代组件中,使用inject进行接收。

const app = Vue.createApp({data(){ return { count: 1}},provide: {count: 1,},template: `<div><child :count="count" /></div>`
})app.component('child',  {props: ['count'],template: ` <child-child />
})app.component('child-child',  {inject: ['count'],template: `<div>{{count}}</div>`
})

如果想provide父组件data中的数据,则需要把provide写成函数的形式,如下:

const app = Vue.createApp({data(){ return { count: 1}},provide() {return {count: this.count}},template: `<div><child :count="count" /></div>`
})app.component('child',  {props: ['count'],template: ` <child-child />
})app.component('child-child',  {inject: ['count'],template: `<div>{{count}}</div>`
})

provide传值只进行一次,如果父组件中的count发生改变,provide也不会响应式的改变,子组件中接收的还是原来的值。

8.组件的生命周期

每一个组件都可以理解为是一个Vue实例,那么在一个Vue实例的生命周期都有啥?码一张官方文档的生命周期图:

对应生命周期,有相应的生命周期函数,就是在某一时刻会自动执行的函数。

具体的生命周期函数如下:

出生阶段的生命周期函数:beforeCreate created beforeMount Mounted。

created:只有数据流,页面DOM还没有渲染,可以访问数据,但是获取不到Dom。此时常用于异步数据获取。

beforeMount:在模版已经被编程函数之后立即自动执行的函数,即组件被渲染到页面之前自动执行的函数。

Mounted:页面已经被渲染完成,该解析的解析完。知道Mounted函数时,dom才创建,可用于访问数据和dom元素。

触发页面更新:beforeUpdate updated,这两个函数在页面初次加载的时候不会触发。

beforeUpdate:更新前,常用于获取更新前各种状态。当data中的数据发生变化时会自动执行的函数。

updated:当data中的数据发生变化,且页面完成更新后,会自动执行的函数。

消亡阶段:beforeDestory destoryed(Vue3开始没有这两个函数,由beforeUnmount和unmounted两个函数取代)

beforeDestory:销毁前,可用于一些定时器或订阅的取消

beforeUnmount:当Vue应用实效时自动执行的函数

unmounted:当Vue应用实效时,且DOM完全销毁之后,自动执行的函数。

v-if的指令销毁组件会触发消亡阶段的方法,再用v-if让组件出现,会触发beforeCreate created。

数据创建完成 = 页面DOM渲染完。数据更新完之后,虚拟DOM节点比对等一系列操作是异步进行的。

Vue知识点唠嗑之组件相关推荐

  1. vue知识点1-父子组件传值 插槽  wath  vuex

    props  $emit  插槽  wath  vuex 父组件   <template> <div> <children :num="num" @i ...

  2. 【Vue知识点- No4.】vue组件、组件通信、Todo案例

    知识点自测 this指向 let obj = {fn: function(){// this指向此函数的调用者},fn () {// this指向当前函数的调用者 (如果都是在vue里, this指向 ...

  3. 【Vue知识点- No6.】动态组件、插槽、自定义指令、tabbar案例

    动态组件.插槽.自定义指令 学习目标 1.能够了解组件进阶知识. 2.能够掌握自定义指令的创建和使用. 3.能够完成tabbar案例的开发. 1.组件进阶 1.0 动态组件 问题:如何切换2个组件,互 ...

  4. 【Vue知识点- No7.】路由、vant组件库的使用

    No7.路由.vant组件库的使用 学习目标 1.能够了解单页面应用概念和优缺点 2.能够掌握vue-router路由系统使用 3.能够掌握链接导航和编程式导航用法 4.能够掌握路由嵌套和路由守卫 5 ...

  5. Vue.js——60分钟组件快速入门(上篇)

    组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢? 组件可以扩展HTM ...

  6. Vue 知识点汇总(下)--附案例代码及项目地址

    文章目录 Vue 预备知识与后续知识及项目案例 一.简介 1.Vue (读音 /vjuː/,类似于 view)的简单认识 2.Vue.js安装 二.Vue知识量化 三.内容 1.Webpack 详解 ...

  7. 前端知识基础之Vue知识点串讲

    一.Vue知识点串讲 复习一下Vue中的核心知识点. 复习完基本的知识点以后,后面再来看一下其它的面试内容 1.基本使用 下面,先来看一段最简单的代码,如下所示: <!DOCTYPE html& ...

  8. 熬夜总结50个Vue知识点

    这几天发生了很多事(具体是啥我就不说了),这些事,吓得我把我这些年珍藏的Vue知识点都拿出来整理了一遍,巴不得能多总结出一道是一道,我拿出了我的笔记,并且使劲回忆,终于悟出了这50道知识点(咱不要太俗 ...

  9. Vue知识点整理(待更新)

    Vue知识点整理(待更新) 参考Vue.js中文官网,Vue 知识点汇总(上)–附案例代码及项目地址,Vue 知识点汇总(下)–附案例代码及项目地址,Vue知识点汇总[持更] 文章目录 Vue知识点整 ...

最新文章

  1. keras/tensorflow 模型保存后重新加载准确率为0 model.save and load giving different result
  2. linux中文乱码问题及locale详解
  3. IntelliJ IDEA使用心得
  4. python sqlsever 时间_Python sqlalchemy时间戳及密码管理实现代码详解
  5. 函数传参string_JavaScript 高阶函数入门浅析
  6. NM的完整形式是什么?
  7. layui生产管理系统_ssm开发生产制造业MES系统源码
  8. 计算机文科类专业二本,适合文科生的二本大学 文科二本分数线是多少
  9. IOS开发中的几种设计模式
  10. CentOS 配置httpd使局域网能够正常訪问
  11. 百度api申请key
  12. ABAP Debug 调试功能
  13. 结巴分词有前空格_jieba英文空格分词问题
  14. win10计算机启动慢,win10开机慢?如何使开机破8秒
  15. 直播回放:巧用Webpack、Eui Compiler
  16. NBS-Predict:基于脑网络的机器学习预测
  17. 【问题解决】Springboot中@Value()读取不到配置文件属性解决方法
  18. 口袋之旅html5超强账号,口袋之旅好号和密码
  19. 围场一中2021高考成绩查询,2017围场一中录取分数线及2017高考成绩喜报
  20. 代号:生机迟迟不上线?腾讯另一款沙盒生存手游我的起源却今日开测

热门文章

  1. L4 详解centos7 emergency模式,rescue模式,linux相互登录,克隆
  2. gatsby-config.js有什么作用?
  3. go.uber.org/zap
  4. 压力大,觉得身心俱疲,年轻人该怎么应对?
  5. 易中天讲座的十句人生感悟
  6. 易中天:《帝国的终结》片断 02
  7. 什么是企业云邮箱域名?什么是邮箱移动办公oa?
  8. 自动延期时间 php,延长session的过期时间
  9. 《经典诗文诵读的实践与研究》开题报告
  10. 智慧医疗——北京融威众邦