组件的特点
- 可复用
- 方便维护
- 每一个vue组件都是一个独立的个体(独立的vm实例);DATA是独立的(不同组件的data互不干扰)、有完整的生命周期、方法都是独立的
- 能够实现组件的嵌套;需要掌握组件之间的信息通信
全局组件
无需单独引用或者配置,直接在大组件中调取全局组件即可 ```javascript Vue.component(组件名,options)
options可以使用的有vm实例具备的大部分(data,methods,生命周期函数…)
每调用一个组件都是创建一个单独的vue实例(VueComponent->Vue)
<a name="7lS22"></a>
## 组件的命名规则
- kebab-case:短横线作为分隔符,只能基于kebab方法调取
- PasalCase:单词首字母大写,也是基于kebab方式调取(如果在template模板中可以使用Pasal方式调取)
- 调取组件的时候,会把所有组件的单词渲染为小写字母(命名除了PaselCase模式外,都要把组件名设置为小写,调取组件的时候可以是大写也可以是小写,最后都是按小写渲染的)
- 命名的时候尽量不要出现其余的特殊字符
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<title></title>
</head>
<body>
<div id="app">
<h3 v-text='title'></h3>
<!--
调取组件
+ 单闭合:不符合w3c规范,调取完成后,后面的视图不识别(避免使用)
+ 双闭合:可以设置除组件规定内容外的其余内容(slot插槽)
-->
<my-button/>
<my-button>
我加的其它内容(slot 插槽)
</my-button>
</div>
<!-- IMPORT JS -->
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
//全局组件
Vue.component('MyButton', {
template: `<button>组件中的按钮</button>`
});
let vm = new Vue({
el: '#app',
data: {
title: '我很开心'
}
});
</script>
</body>
</html>
局部组件
父子通信
子组件通过props接受父组件的数据,子组件通过$emit触发父组件的自定义事件
父组件传递数据给子组件
父组件传递数据给子组件实质: 是通过行内属性的方式传递给子组件
父传子: 让子组件使用父组件的数据
1、$parent 通过获取组件的方式
2、自定义属性 + props (type default required validator)
3、$attrs 可以获取没有被props接收的剩余的属性
4、provide/inject
5、$children + 索引
6、$refs
—— $root
1. props
prop的大小写
在dom中的模块,驼峰命名的prop名要使用短横线分割命名替换,但是如果是字符串模板,则没有此限制
prop的类型
/* props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
*/
function a(id){
this.id=id
}
Vue.component('blog-post', {
props: {
author: a
}
})
prop的验证
//type类型可以是string,number,boolean,array,object,data,function,symbol
//type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// validator 是自定义的校验函数
// val 就是这个 prop 收到的值
// 自定义校验规则,如果校验通过return true,否则抛出异常或者return false
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
注意点:prop 会在一个组件实例创建之前进行验证,实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
prop静态传值或者动态传值
prop是父组件传递数据的自定义属性。
- prop 传递静态的,直接传一个值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<!-- 父组件 -->
<div id="app">
<son sonvalue3="'abcd'" sonvalue="f" sonvalue2="33"></son>
</div>
<!-- 子组件,此处静态传数据时,必须是确定值 -->
<template id="son">
<div>
<h2>我是子组件</h2>
引用父组件中的数据项:{{sonvalue}}- {{sonvalue2}}- {{sonvalue3}}
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
//1.定义组件对象
let Son = {
template: "#son",
props: {
sonvalue: {
type: Number
},
sonvalue2: {
type: Number,
default: 23
},
sonvalue3: {
type: String,
required: true
}
}
}
let vm = new Vue({
el: "#app",
components: { //2.注册组件对象
Son
}
});
</script>
</body>
</html>
- prop使用v-bind,在传入数字,布尔值,数组,对象时,即使是静态的,也必须使用v-bind绑定,因为此时他是表达式不是字符串;当将一个对象所有的属性作为prop传入时,可以使用不带参数的,用一个对象包含所有属性,然后把对象名绑定在元素上即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<!--
父组件把信息传递给子组件:props属性传递
- 默认传递给子组件的属性值都是字符串格式的,如果需要传递数据的格式是数据本身应该具备的格式,我们需要基于v-bind实现传递(哪怕传递的属性值还是固定)
- 可以把子组件当做一个标签,我们可以设置ID/CLASS/STYLE等内置属性值,这些属性也会传递给子组件,VUE帮我们处理好的,该合并的合并,该覆盖的覆盖,无需我们在PROPS中注册处理
-->
<!-- 父组件 -->
<div id="app">
<!-- 子组件 -->
<son :sonvalue3="Parentvalue3" :sonvalue="Parentvalue" :sonvalue2="Parentvalue2"></son>
</div>
<template id="son">
<div>
<h2>我是子组件</h2>
引用父组件中的数据项:{{sonvalue}}- {{sonvalue2}}- {{sonvalue3}}
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
//1.定义组件对象
let Son = {
template: "#son",
props: {
sonvalue: {
type: Number
},
sonvalue2: {
type: Number
},
sonvalue3: {
type: String
}
}
}
let vm = new Vue({
el: "#app",
data:{
Parentvalue:'12',
Parentvalue2:'90',
Parentvalue3:"'asss'"
},
components: { //2.注册组件对象
Son
}
});
</script>
</body>
</html>
单项数据流prop注意点
- prop是父子之间形成的单行向下传递数据,禁止逆向传递。
- 当父组件发生更新时,子组件所有的prop会变成新的值。因此不应该在子组件内部改变prop,会造成错误。
重点
- 情景一:prop传递初始值时,子组件要把他作为一个本地的prop数据使用。
// 解决办法:定义一个本地的data属性并将这个prop用作初始值。
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- 情景二:prop以一种原始的值传入并需要进行转换。
//解决办法:使用这个prop的值定义一个计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
//在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
2. $parent
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">
<h1>{{name}}</h1>
<father></father>
</div>
</body>
<template id="father">
<div>
<div @click="facount++">父组件:{{facount}}</div>
<son :q='facount'></son>
</div>
</template>
<template id='son'>
<div @click.stop='fn'>
子组件:{{$parent.facount}}
<h2>接受父组件的数据:{{q}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue">
</script>
<script type="text/javascript">
// 父传子 就是在子组件使用的标签上 添加行内属性
// 自组件中 通过props属性接收传进来的值;
// 这个props对应的属性的属性值只能看 不能改;
Vue.component('father',{
template:'#father',
data() {
return {
facount:100
}
},
})
Vue.component('son',{
template:'#son',
props:['q'],
data() {
return {
}
},
methods: {
fn(){
//this.$parent 可以获取整个父组件,
//那么整个父组件中的属性或者方法 我们可以随意调用
//this.$parent.facount+=10;
console.log(this.q)
// 从父组件接手过来的数据 我们不能直接修改
// 因为这么修改 有被重写的风险
// 每当父组件更新一下, 传进来的数据就会被重写
this.q= 1000;
}
},
})
let vm = new Vue({
el:'#app',
data:{
name:"珠峰"
},
});
子组件向父组件传递
子传父: 通过让子组件告诉父组件要执行什么代码
1、$parent
2、自定义事件 + $emit : 所有再组件上边的事件都是自定义事件
3、$listeners 获取传给子组件的所有的自定义事件
4、provide/inject provide(){return {name:this.name}}
1. 利用emit
事件名
在向父组件传递时,触发的事件名必须匹配监听事件所用的名称,事件名没有自动化的大小写转换,v-on事件监听器在dom模块中会被自动转换为全小写,会造成事件名不相同造成问题,因此建议使用短横线分割。
思路
在子组件里$emit绑定一个事件($emit(“事件名”,”参数”)),执行emit时,会把参数传递给父组件,父组件通过v-on监听并接收参数
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<h2>下面的数据项是从根组件中取到的</h2>
<ol>
<li v-for="item in todo">{{item}}</li>
</ol>
<hr />
这是一个子组件,我们希望把子组件中的数据传递给父组件
<!-- 第三步:父组件接受子组件的方法 -->
<!--<子组件 @子组件发回事件名="父组件要执行的方法"></子组件>-->
<son @submitmsg="addmsg"></son>
</div>
<template id="son">
<div>
<input type="text" v-model="txt" />
<!-- 第一步:子组件绑定事件 -->
<button @click="add">添加</button>
<p> 在子组件中的数据如下:</p>
<p>{{txt}}</p>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
//1.定义组件对象,子组件
let Son = {
template: "#son",
data: function () {
return {
txt: ""
}
},
methods: {
add: function () {
console.info(this.txt);
//第二步:子组件向父组件发送一个方submitmsg
//emit内容少的时候,可以直接放到@click后面,用值直接写就可以
this.$emit("submitmsg", {
msg: this.txt,
t: new Date()
})
}
}
}
let vm = new Vue({
el: "#app",
data: {
todo: [
"天气变冷了",
"注意不要感冒了"
]
},
components: { //2.注册组件对象
Son
},
methods: {
// 第四步:接受子组件方法之后,执行父组件方法
addmsg: function (info) {
console.info("父组件收到了子组件发布的事件", info);
this.todo.push(info.msg);
}
}
});
</script>
</body>
</html>
2. v-model
一个组件上的v-model默认会利用名为value的prop和名为input的事件。使用父组件v-model传值,子组件props[‘value’]接收而子组件也可以通过$emit(‘input’,false),去改变父组件中v-model 和 子组件中 value 的值 。
**
使用v-model
来进行双向数据绑定的时:
<input v-model="something">
仅仅是一个语法糖:
<input v-bind:value="something" v-on:input="something = $event.target.value">
所以在组件中使用的时候,相当于下面的简写:
<custom v-bind:value="something" v-on:input="something = $event.target.value"></custom>
所以要组件的v-model
生效,它必须:
- 将value绑定到名为value的prop上
- 在其input事件被触发时,将新的值通过自定义的input事件触发
父组件
<template>
<div class="toggleClassWrap">
<!-- 自定义v-model="isShow"应用 此处将isShow 传递给子组件中的props -->
<modelVue v-if="ifShow" v-model="ifShow"></modelVue>
</div>
</template>
<script type="text/javascript">
import modelVue from '../../components/model.vue'
export default{
data () {
return {
ifShow:true,
}
},
components : {
modelVue
}
}
</script>
子组件
<template>
<div id="showAlert">
<div>showAlert 内容</div>
<button class="close" @click="close">关闭</button>
</div>
</template>
<script>
export default{
//1.将value绑定到名为value的prop上
props:{
value:{
type:Boolean,
default:false,
}
},
data(){
return{}
},
methods:{
close(){
//2.将value绑定到名为value的prop上
this.$emit('input',false);//传值给父组件, 让父组件监听到这个变化
}
},
}
</script>
<style scoped>
.close{
background:red;
color:white;
}
</style>
3. .sync
可以通过prop进行双向绑定,同时配合this.$emit(update:myPropName,newValue)触发事件。.sync相当一个语法糖,会被扩展为自动更新父组件属性的v-on监听器,(即让自己手动更新父组件的值,进而使数据来源更改更明显)
//作为语法糖的原本写法
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event">
</text-document>
//利用.sync语法糖简写的形式
<text-document v-bind:title.sync="doc.title"></text-document>
父组件
<template>
<div class="vuexWrap common">
<!--使用sync修饰符可以简化子组件向父组件传递数据的过程;-->
<!--1. 在父组件使用子组件时,prop后面跟一个.sync ,然后取消显式声明的事件监听-->
<!--2. 子组件触发事件:this.$emit('update:prop名字', 新数据)-->
<childrenOne :title.sync="doc.title"></childrenOne>
</div>
</div>
</template>
<script type="text/javascript">
import childrenOne from '../../components/childrenOne.vue'
export default{
data () {
return {
doc:{
title:'index'
},
}
},
mounted (){
//childrenOne
alert(this.doc.title);
},
components : {
childrenOne
}
}
</script>
子组件
<template>
<div class="OneWrap common">
{{title}}
</div>
</template>
<script type="text/javascript">
export default{
props:{
title:""
},
data () {
return {
newTitle:"childrenOne"
}
},
mounted (){
this.$emit('update:title', this.newTitle);
},
}
</script>
4. ref
- ref加在原生的DOM元素上,通过ref获取的是原生的DOM对象(基于ref把当前元素放置到this.$refs对象,实现对dom直接操作);如果加在组件上,获取的是这个组件实例的一个引用;拿到这个实例后可以访问上面的数据、调用组件的方法
- 只有在mounted及之后才能获取到
- $parent和$children是获取组件和子组件的实例,只不过$children是一个数组集合,需要记住组件顺序才可以
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app" :style="{color: xColor}">
<p ref="p1">{{msg}}</p>
<ul>
<!-- <li v-for="(item, index) in ary"
v-if="index % 2 != 0"
ref="listItem"
:key="index">{{item}}</li>-->
<li v-for="(item, index) in oddIndex"
ref="listItem"
:key="index">{{item}}</li>
</ul>
</div>
<script src="vue.js"></script>
<script>
// Vue是数据驱动的,不提倡操作DOM;但是必要的时候还是需要操作DOM的;Vue提供了专门的方式获取DOM;
// ref属性 和 vm.$refs
// 如果这个列表不需要全部渲染,可以写一个计算属性,v-for 这个计算属性
// 或者,v-if 条件渲染
let vm = new Vue({
el: '#app',
data: {
msg: 'hello',
ary: [1, 2, 3, 4],
xColor: ''
},
computed: {
oddIndex() {
return this.ary.filter((item, index) => index % 2 !== 0);
}
},
mounted() {
// console.log(this.$refs);
console.log(this.$refs.p1);
console.log(this.$refs.listItem);
// 我们通过this.$refs获取DOM元素;
this.$refs.p1.style.color = 'red'; // 可以实现,但是不推荐操作DOM;
// 首先要在获取的元素添加 ref="标识符" 的行内属性
// 获取的时候this.$refs.标识符 获取元素;
// 如果相同的ref有一个,获取到的就是带这个ref的原生元素对象
// 如果相同的ref有多个,获取到的是所有带有这个ref的元素组成的数组
//基于REF可以把当前元素放置到this.$refs对象中,从而实现对DOM的直接操作(只有在mounted及之后才可以获取到)
}
})
</html>
获取子组件的实例
- this.$children[0].flag ->需要知道是数组的哪一个
给子组件加ref,this.$refs.实例,
ref用于获取组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{name}}</h1>
<ul>
<li ref='www'></li>
<li ref='www'></li>
<li ref='www'></li>
</ul>
<ul>
<li v-for='item in ary' ref='qqq'></li>
</ul>
<my-btn v-for='item in ary' :key='item' ref='qqq'></my-btn>
</div>
</body>
</html>
<template id='btn'>
<button @click='clickFn' class='aaa' >按钮</button>
</template>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
// ref除了获取元素 还可以获取 组件;
// 获取到组件之后 我们就可以根据自己的需求进行编写
// 父组件怎么调用到 子组件的 methods中的函数
// 子组件怎么调用到 父组件的 methods中的函数
let obj = {
template:'#btn',
methods: {
clickFn(e){
this.$emit('click',e);
console.log(this)
}
},
}
let vm = new Vue({
el:'#app',
data:{
name:"珠峰",
ary:[1,2,3,4]
},
mounted() {
// ref获取元素 在多个元素的时候 只能获取一个
// 若是通过 v-for循环出来的 就都可以获取到
// DOM的更新是一个异步操作
console.log(this.$refs.qqq)// v-for出来的可以获取一组
console.log(this.$refs.www)// 静态写死的只获取一个
this.ary.pop();
this.$nextTick(()=>{
// DOM更新完成之后才会触发; DOM的更新是异步的
console.log(this.$refs.qqq)
})
},
components:{
'my-btn':obj
},
});
</script>
5. $parent和$child
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">
<h1>{{name}}</h1>
<father></father>
</div>
</body>
<template id="father">
<div>
<!-- 只要组件上的事件 不管长成什么样 都是自定义事件 -->
<div @click="facount++">父组件:{{facount}}</div>
<son :q='facount'></son>
</div>
</template>
<template id='son'>
<div @click.stop='fn'>
子组件:{{$parent.facount}}
<h2>接受父组件的数据:{{q}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue">
</script>
<script type="text/javascript">
// 父传子 就是在子组件使用的标签上 添加行内属性
// 自组件中 通过props属性接收传进来的值;
// 这个props对应的属性的属性值只能看 不能改;
Vue.component('father',{
template:'#father',
data() {
return {
facount:100
}
},
})
Vue.component('son',{
template:'#son',
props:['q'],
data() {
return {
}
},
methods: {
fn(){
//this.$parent 可以获取整个父组件,
//那么整个父组件中的属性或者方法 我们可以随意调用
//this.$parent.facount+=10;
console.log(this.q)
// 从父组件接手过来的数据 我们不能直接修改
// 因为这么修改 有被重写的风险
// 每当父组件更新一下, 传进来的数据就会被重写
this.q= 1000;
}
},
})
let vm = new Vue({
el:'#app',
data:{
name:"珠峰"
},
});
6. $attr
可以获取没有被props接收的那些参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="../node_modules/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<div id="app">
<!-- 只要组件上的事件 不管长成什么样 都是自定义事件 -->
<h1>{{name}}</h1>
<mycount :count='count' @add123='fn' @minus123='fn'></mycount>
<el-button type="primary" icon="el-icon-edit" @click='fn2' class='qqq'></el-button>
<my-button @click='fn2' class='qwer'></my-button>
</div>
</body>
</html>
<template id='mycount'>
<div>
<h2>数字是{{count}}</h2>
<button @click='add'>增加</button>
<button @click='minus'>减少</button>
</div>
</template>
<script src="../node_modules/vue/dist/vue.js"></script>
<script src="../node_modules/element-ui/lib/index.js"></script>
<script>
// 子传父 让父组件使用子组件的数据;也就是子组件可以修改父组件的数据
/*
1、$parent
2、自定义事件 + $emit(官推)
3、$listeners 可以接收所有自定义事件 this.$listeners.事件名(参数)
4、provide/inject
// 2 3 4这三种方法 都是一个套路: 把父组件的函数 传给子组件,
然后再子组件中执行对应的函数,并通过参数的方式 把子组件的数据给父组件
*/
let mycount = {
template:'#mycount',
props:['count'],// props 优先于 data
created() {
console.log(this)
},
methods: {
add() {
// this.$parent.count++
// this.$emit('add123',1,2,3,4,5,6,7)
this.$listeners.add123(1,2,3)
},
minus(){
// this.$parent.count--
// this.$emit('minus123',100,200,300,400)
this.qqq(100,200)
}
},
inject:['qqq']
}
let vm = new Vue({
el:'#app',
data:{
name:"珠峰",
count:0
},
components:{
mycount
},
methods: {
fn(n){
console.log(arguments)
this.count += n
},
fn2(){
console.log(arguments)
}
},
provide(){
return {
qqq:this.fn
}
}
});
</script>
隔代通信
- provide和inject
{
provide:{
//对象或者返回对象的函数都可以(属性如果是data中的数据,则必须使用函数的方法进行处理)
name:"aaa";
}
}
//后代组件基于inject声明需要使用的数据并调取使用
{
inject:['name'],
methods:{
function(){
let name=this.name;
}
}
}