vue学习之路(组件)
什么是组件
组件是可复用的 Vue 实例,且带有一个名字。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用
组件与组件树
<!-- 挂载点:是隐式声明根组件 -->
<div id="root">
<child-component></child-component>
<child-component></child-component>
<child-component></child-component>
</div>
<script>
// 组件:从形式上看就是一个自定义html标签
// 组件是可复用的vue实例,构造函数Vue的子类
// 1. 创建
// const childComponent = Vue.extend({
// template: "<h2>hello world</h2>",
// });
// 2. 注册
// 使用Vue.component()静态方法注册的是 全局组件
// Vue.component(组件名,对象字面量表示的组件配置项)
// 组件名:自定义的html标签
// Vue.component("child-component", childComponent);
Vue.component("child-component", {
template: "<h2>hello world</h2>",
});
// 3. 挂载
// 将标签添加到页面渲染
const vm = new Vue({
el: document.querySelector("#root"),
});
</script>
全局组件
全局组件:全局可见,声明在Vue实例外部
<div id="root">
<button-inc></button-inc>
</div>
<hr />
<div id="app">
<button-inc></button-inc>
</div>
<script>
// 全局组件:全局可见,声明在Vue实例外部
Vue.component("button-inc", {
// 组件中的模板代码:允许存在数据占位符的html字符串
// 模板内容必须写到一对父元素标签中
template: `
<div>
<button @click="count++">点赞:+{{count}}</button>
</div>
`,
data() {
return {
count: 0,
};
},
});
const vm = new Vue({
el: document.querySelector("#root"),
});
const vm1 = new Vue({
el: document.querySelector("#app"),
});
// 全局组件可以在多个Vue实例共享,通常一个项目只有一个vue实例,全局组件尽可能不用,使用局部组件。
</script>
还可以将组件中的 template 提出去到html中。
<div id="root">
<button-inc></button-inc>
</div>
<hr />
<div id="app">
<button-inc></button-inc>
</div>
<!-- 模板开始 -->
<template id="inc">
<div>
<button @click="count++">点赞:+{{count}}</button>
</div>
</template>
<!-- 模板结束 -->
<script>
Vue.component("button-inc", {
template: "#inc",
// 组件中必须使用函数data()来声明组件变量
data() {
return {
count: 0,
};
},
});
const vm = new Vue({
el: document.querySelector("#root"),
});
const vm1 = new Vue({
el: document.querySelector("#app"),
});
// 全局组件可以在多个Vue实例共享,通常一个项目只有一个vue实例,全局组件尽可能不用,使用局部组件。
</script>
局部组件
局部组件是属于vue实例
<div id="root">
<my-child></my-child>
</div>
<template id="hello">
<p>hello {{userName}}</p>
</template>
<script>
const vm = new Vue({
el: document.querySelector("#root"),
// 局部组件是属于vue实例
components: {
"my-child": {
template: "#hello",
data() {
return {
userName: "TJ",
};
},
},
},
});
</script>
另一种简写方案
<div id="root">
<hello></hello>
</div>
<script>
const hello={
template: `<p>hello {{userName}}</p>`,
data() {
return {
userName: "TJ",
},
}
};
const vm = new Vue({
el: document.querySelector("#root"),
// 局部组件是属于vue实例
components: {hello},
},
});
</script>
组件之间的传参:父组件向子组件传参
父组件是通过自定义属性的方式将参数传到子组件中的
<div id="app">
<!-- 父组件是通过自定义属性的方式将参数传到子组件中的 -->
<btn-inc :username="username" :count="count"></btn-inc>
</div>
<script>
const vm = new Vue({
el: document.querySelector("#app"),
data() {
return {
username: "TJ",
count: 0,
};
},
//局部组件
components: {
btnInc: {
props: ["username","count"],
// 报错原因,组件之间的数据传递是单向的,不允许在子组件中更新父组件的数据。
template: `
<div>
<button @click="inc(1)">点赞:+{{num}}</button>
<span>{{username}}</span>
</div>
`,
data(){
return {
num:this.count,
}
},
methods:{
inc(n) {
this.num += n;
}
}
},
},
});
</script>
组件之间的传参:子组件向父组件传参
<div id="app">
<!-- 子组件中的更新父组件数据是通过自定义同名事件完成的-->
<btn-inc
:username="username"
:count="count"
@click-count="handle"
></btn-inc>
</div>
<script>
const vm = new Vue({
// 子组件向父组件传参是通过声明同名事件来实现
el: document.querySelector("#app"),
data() {
return {
username: "TJ",
count: 0,
};
},
//局部组件
components: {
btnInc: {
props: ["username", "count"],
// 报错原因,组件之间的数据传递是单向的,不允许在子组件中更新父组件的数据。
// $emit(父组件中要使用的方法名称,子组件要传给父组件的值)
template: `
<div>
<button @click="$emit('click-count',10)">点赞:+{{count}}</button>
<span>{{username}}</span>
</div>
`,
methods: {
inc(n) {
this.num += n;
},
},
},
},
// 父组件更新数据的方法
methods: {
handle(value) {
this.count += value;
this.username = "T.,j";
console.log(vm.count);
},
},
});
// 总结:
// 1. 父组件 向 子组件 传参: 自定义属性
// 2. 子组件 向 父组件 传参: 自定义方法
</script>
组件之间的双向传参
<div id="demo">
<input type="text" :value="value" @input="value = $event.target.value" />
<p>{{value}}</p>
</div>
<script>
new Vue({
el: "#demo",
data() {
return {
value: 123,
};
},
});
</script>
<hr />
<div id="app">
<p>父组件 单价:{{price}} 元</p>
<p>
<span>子组件</span
><my-input :my-price="price" @input-text="handle"></my-input>元
</p>
</div>
<script>
const vm = new Vue({
// 子组件向父组件传参是通过声明同名事件来实现
el: document.querySelector("#app"),
data() {
return {
price: 4588,
};
},
//局部组件
components: {
myInput: {
props: ["my-price"],
template: `
<input type="text" :value="myPrice" @input="$emit('input-text',$event.target.value)" />
`,
},
},
methods: {
handle(value) {
this.price = value;
},
},
});
</script>
另一种简化方案:将$emit()提出去,写到methods里
<div id="app">
<p>父组件 单价:{{price}} 元</p>
<p>
<span>子组件</span
><my-input :my-price="price" @input-text="handle"></my-input>元
</p>
</div>
<script>
const vm = new Vue({
// 子组件向父组件传参是通过声明同名事件来实现
el: document.querySelector("#app"),
data() {
return {
price: 4588,
};
},
//局部组件
components: {
myInput: {
props: ["my-price"],
template: `
<input type="text" :value="myPrice" @input="foo" />
`,
methods:{
foo(ev){
this.$emit('input-text',ev.target.value)
}
}
},
},
methods: {
handle(value) {
this.price = value;
},
},
});
</script>
插槽:组件内容分发
<div id="app">
<my-comp>world</my-comp>
</div>
<script>
new Vue({
el: "#app",
components: {
myComp: {
template: `
<div>
<h2>Hello <slot>user</slot></h2>
</div>
`,
},
},
});
</script>
注意:
难点是子组件向父组件传参通过同名事件完成 $emit(同名事件名,参数)
,如果把此处提出写到methods
中要使用this.$emit(),因为是要指定是子组件
在手册中提到在组件中使用v-model
自定义事件也可以用于创建支持 v-model 的自定义输入组件。
<input v-model="searchText">
等价于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
当用在组件上时,v-model 则会这样:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
为了让它正常工作,这个组件内的 <input>
必须:
将其 value attribute 绑定到一个名叫 value 的 prop 上
在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
写成代码之后是这样的:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
现在 v-model 就应该可以在这个组件上完美地工作起来了:
<custom-input v-model="searchText"></custom-input>
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>组件使用v-model</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>父组件:{{searchText}}</p>
<custom-input v-model="searchText"></custom-input>
</div>
<script>
new Vue({
el: "#app",
data() {
return {
searchText: "test",
};
},
components: {
"custom-input": {
props: ["value"],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`,
},
},
});
</script>
</body>
</html>
感觉组件使用v-model不是很灵活的样子