Vue.js基础—组件
组件,就是网页上面分割出来的一部分,一部分的内容,小到甚至一个购物车,一个输入框都可以看成是一个组件
1、创建父组件和子组件
创建一个父组件和一个子组件
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
//创建一个Vue实例
const app = Vue.createApp({
template: `
<div>
<div>hello</div>
<div>word</div>
</div>
`
});
const vm = app.mount('#root');
</script>
</body>
运行结果:
把刚刚创建的父组件拆分为两个子组件
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
//创建一个Vue实例,同时也是一个父组件
const app = Vue.createApp({
template: `
<div>
<hello /> <word />
</div>
`
});
//子组件1
app.component('hello', {
template: `
<div> hello </div>
`
})
//子组件2
app.component('word', {
template: `
<div> word </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
2、创建全局组件和局部组件
全局组件:
①只要定义了,处处可以使用,也就是说在父组件里面可以用,在子组件里面也可以用,但是性能不高,因为你不用时候,组件依然挂载在app下面,会占用app的内存
②名字可以小写字母单词,中间用短横线隔开
局部组件:
①定义了,要注册才能使用,性能比较高,使用较麻烦
②名字建议大写字母开头,驼峰命名
③组件使用时候,要做一个名字和组件之间的映射对象,若不写映射,Vue底层也会尝试帮你自动做映射
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
//定义局部组件1
const Counter = {
data() {
return {
count: 1
}
},
template: `<div @click='count += 1'> {{count}} </div>`
};
//定义局部组件2
const HelloWord = {
template: `<div> hello word </div>`
};
const app = Vue.createApp({
//注册组件,并且映射
components: {
//省略映射,Vue会尝试自动帮你映射
// 'counter': Counter,
// 'hello-word': HelloWord
HelloWord, Counter
},
template: `
<div>
<counter />
<hello-word />
</div>
`
});
const vm = app.mount('#root');
</script>
</body>
运行结果:
定义局部组件,并放到Vue实例后面,会报:无法在初始化前访问“计数器” 的错误
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
//注册组件,并且映射
components: { counter: Counter },
template: `
<div>
<counter />
</div>
`
});
//定义局部组件,并放到Vue实例后面
const Counter = {
data() {
return {
count: 1
}
},
template: `<div @click='count += 1'> {{count}} </div>`
};
const vm = app.mount('#root');
</script>
</body>
运行结果:报错
3、父组件数据传递给子组件
在子组件使用props属性接收从父组件传过来的属性名,属性名通常会携带数据
①动态传参,使用v-bind绑定属性,数据从data里面提供,灵活
②静态传参,直接context=‘num’,其中num的类型为字符串,容易写死
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return { num: 123 }
},
template: `
<div> <test1 context='num' /> </div>
<div> <test2 :content='num' /> </div>
`
});
app.component('test1', {
props: ['context'],
template: `
<div> {{context}}---{{typeof context}} </div>
`
})
app.component('test2', {
props: ['content'],
template: `
<div> {{content}}---{{typeof content}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
③校验父组件传过来的数据,这时候不是数组,而是用对象
type:String、Boolean、Object、Array、Function、symbol
required:true(要你传) 、false(不要你传)
default:默认值/fn —>1、当你传,不会展示默认值,当你不传,会展示默认值
validator:fn —>对传过来参数内容做限制
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return { num: 123 }
},
template: `
<div> <test :content='num'/> </div>
`
});
app.component('test', {
props: {
content: {
type: Number,
required: false,
default: function () {
return 888
},
validator: function (value) {
return value < 124
}
}
},
template: `
<div> {{content}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
4、当父组件中数据有很多时候,可以在父组件template使用v-bind来处理渲染多个参数
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
params: {
content: 1234,
a: 123,
b: 234,
c: 345,
}
}
},
template: `
<div> <test v-bind='params'/> </div>
`
});
app.component('test', {
props: ['content', 'a', 'b', 'c'],
template: `
<div> {{content}}--{{a}}--{{b}}--{{c}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
5、单向数据流:子组件可以使用父组件传递过来参数,但是绝对不能修改父组件里面的数据
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return { num: 1 }
},
template: `
<div> <counter :count='num' /> </div>
`
});
app.component('counter', {
props: ['count'],
template: `
<div @click='count += 1 '> {{count}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
在子组件data对象定义一个自己参数myCount用来接收父组件传递过来的值
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return { num: 1 }
},
template: `
<div> <counter :count='num' /> </div>
`
});
app.component('counter', {
props: ['count'],
data() {
return {
//用来接收父组件传递过来值
myCount: this.count,
}
},
template: `
<div @click='myCount += 1 '> {{myCount}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
6、在子组件没有写props属性时候,如果需要传递数据该怎么做?这时候就要使用到Non-props属性
①在子组件标签里面使用v-bind=’$attrs’用来接收父组件所有数据
②在子组件标签里面使用:msg1=’ $attrs.msg1 '来接收单个需要传递的数据
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
template: `
<div> <counter msg1='孙悟空' msg2='猪八戒' msg3='沙和尚' /> </div>
`
});
app.component('counter', {
template: `
<div v-bind='$attrs'> hello word </div>
<div :msg1='$attrs.msg1'> hello word </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
③在生命周期函数里面想要传递过来数据,应该使用this.$attrs来获取
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
template: `
<div> <counter msg1='孙悟空' msg2='猪八戒' msg3='沙和尚' /> </div>
`
});
app.component('counter', {
mounted() {
console.log(this.$attrs);
},
template: `
<div v-bind='$attrs'> hello word </div>
<div :msg1='$attrs.msg1'> hello word </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
4、子组件通过 向外暴露事件–$emit 来像父组件传递数据
因为我们知道父组件传递给子组件的数据,在子组件里面修改,Vue是不允许的,那么这时候需要在子组件里面定义 $emit 事件告诉父组件,在父组件里面操作修改数据,然后再传递给子组件进行展示
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 1
}
},
methods: {
handleAddOne() {
this.count += 1;
}
},
// 绑定'add-one'参数,并进行监听和做加1的处理函数
template: `
<counter :count='count' @add-one='handleAddOne' />
`
});
app.component('counter', {
props: ['count'],
methods: {
handleClick() {
//$emit-->向父组件暴露'addOne'参数
this.$emit('addOne');
}
},
template: `
<div @click='handleClick'> {{count}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
上面只是传递一个参数,当你想要传递多个参数,应该怎么做呢?可以这样$emit(‘addOne’ ,2 , 4)携带多个参数,同时在父组件里面操作处理函数定义形参param用来接收实参
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 1
}
},
methods: {
//定义形参param用来接收实参
handleAddOne(param) {
this.count += param;
}
},
// 绑定'add-one'参数,并进行监听和做加3的处理函数
template: `
<counter :count='count' @add-one='handleAddOne' />
`
});
app.component('counter', {
props: ['count'],
methods: {
handleClick() {
//$emit-->向父组件暴露'addOne'参数,并进行加3操作
this.$emit('addOne', 3);
}
},
template: `
<div @click='handleClick'> {{count}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
高级用法–>在组件里面使用 v-model 简化代码
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 1
}
},
template: `
<counter v-model='count' />
`
});
app.component('counter', {
//'modelValue'是固定的
props: ['modelValue'],
methods: {
handleClick() {
//触发事件名字'update:modelValue'固定的
//this.modelValue + 3 向外触发值,会自动替换掉父组件count,完成更新
this.$emit('update:modelValue', this.modelValue + 3);
}
},
template: `
<div @click='handleClick'> {{modelValue}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
使用多个 v-model 属性进行绑定
①通过 v-model:count=‘count’ ,可以自定义名字
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 1,
count1: 1
}
},
template: `
<counter v-model:count='count' v-model:count1='count1' />
`
});
app.component('counter', {
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 vm = app.mount('#root');
</script>
</body>
运行结果:
如何在 v-model 绑定修饰符,从而将字符串转换为大写呢?
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
count: 'a',
}
},
template: `
<counter v-model.uppercase='count' />
`
});
app.component('counter', {
props: {
'modelValue': String,
//接收修饰符uppercase
'modelModifiers': {
//默认值,不传uppercase,默认给个空对象
default: () => ({})
}
},
mounted() {
console.log(this.modelModifiers);
},
methods: {
handleClick() {
let newValue = this.modelValue + 'b';
if (this.modelModifiers.uppercase) {
newValue = newValue.toUpperCase();
}
this.$emit('update:modelValue', newValue)
},
},
template: `
<div @click='handleClick'> {{modelValue}} </div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
console.log(this.modelModifiers)打印结果:
5、slot插槽和作用域
为什么Vue需要设计slot插槽?
因为使用props属性传递dom节点,标签是非常麻烦,而有了slot插槽,刚好解决这些痛点,因为slot插槽就是用来传递dom节点和标签用的
理解:什么是slot插槽?
当我们调用子组件时候,想要一个是div按钮,一个是button按钮时候,这时候应该如何解决?
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
template: `
<myform />
<myform />
`
});
app.component('myform', {
template: `
<div>
<input type="text">
<button> 提交 </button>
</div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
这时候就需要使用 slot插槽 来解决问题
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
text: '提交'
}
},
//{{text}}使用父模板里面的data数据
template: `
<myform>
<div> {{text}} </div>
</myform>
<myform>
<button> {{text}} </button>
</myform>
`
});
app.component('myform', {
template: `
<div>
<input type="text">
<slot></slot>
</div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:
使用slot插槽还需要注意:
①父模板里调用的数据属性,使用都是父模板里面的数据
②子模板里调用的数据属性,使用都是子模板里面的数据
当我们在调用子组件时候,有个标签里面不用slot插槽,而我们又想它会带上默认值,这时候应该怎么处理?
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
text: '提交'
}
},
template: `
<myform>
<div> {{text}} </div>
</myform>
<myform>
<button> {{text}} </button>
</myform>
<myform>
</myform>
`
});
app.component('myform', {
template: `
<div>
<input type="text">
<slot> default value </slot>
</div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果:对于前面2个slot插槽有值,不会使用默认值,只有没有值的时候,才会帮你添加上去
具名插槽的理解
就是把大slot插槽拆分成几个小插槽,并且配上name属性,这样调用时候,更加灵活便捷
有三个layout布局,分别是header、content、footer,现在我想把content放在中间,使用slot插槽是比较难实现的,这时候需要使用具名插槽
①添加一个 < template >占位符,使结构更加清晰,不会渲染成dom节点
②使用冒号:
<body>
<div id="root"></div>
<script src="./vue.global.js"></script>
<script>
const app = Vue.createApp({
template: `
<layout>
<template v-slot:header>
<div> header </div>
</template>
<template v-slot:footer>
<div> footer </div>
</template>
</layout>
`
});
app.component('layout', {
template: `
<div>
<slot name='header'> </slot>
<div> content </div>
<slot name='footer'></slot>
</div>
`
})
const vm = app.mount('#root');
</script>
</body>
运行结果: