vue.js 学习笔记(二)组件化开发
一、组件化开发
???????组件化开发的思想:将复杂的问题拆分成很多小问题。若我们将页面中所有的逻辑处理全部放在一起,处理起来会非常复杂,且不利于后续的管理和扩展。若我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护都会变得容易
???????
1.1 组件使用的基本步骤
组件的使用的三个步骤:(Vue 2.x开始实际上就不用,只是是基础)
- 创建组件构造器:调用Vue.extend()
- 注册组件:调用Vue.component()
- 使用组件:在Vue实例作用范围内使用
???????
(1)Vue.extend()
- 调用Vue.extend()创建的是一个组件构造器
- 通常在创建构造器时,传入的template代表我们自定义组件的模板,该模板就是在使用组件的地方,要显示的HTML代码
(2)Vue.component()
- 将刚才的组件构造器注册为一个组件,并给他起一个组件的标签名称
- 传递的参数:注册组件的标签名和组件构造器
(3)使用组件
? 组件必须挂载在某个Vue实例下,否则起不生效
???????
1.2 全局组件和局部组件
Vue.component('cpn',cpnC);
const app = new Vue({
el:'#app',
data: {
message:'你好'
},
components: {
cpn: cpnC
}
})
???????
1.3 父组件和子组件
const cpnC1 = Vue.extend({
template:`
<div>
<h2>xxxx</h2>
<p>hhhh</p>
</div>
`
});
const cpnC2 = Vue.extend({
template:`
<div>
<h2>xxxx</h2>
<p>hhhh</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1: cpnC1
}
});
const app = new Vue({
el:"#app",
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
})
???????
1.4 语法糖(重要,之前的写法现在不用了)
1.4.1 注册全局组件
Vue.component('cpn1',{
template:`
<div>
<h2>xxxx</h2>
<p>hhhh</p>
</div>
`
});
注意:cpn1用单引号或双引号包裹的
???????
1.4.2 注册局部组件
const app = new Vue({
el:"#app",
data: {
message: '你好啊'
},
components: {
'cpn2': {
template:`
<div>
<h2>xxxx</h2>
<p>hhhh</p>
</div>
`
}
}
})
注意:cpn2用单引号或双引号包裹的
???????
1.5 组件模板的分离写法
<script type="text/x-template" id="cpn">
<div>
<h2>xxxx</h2>
<p>hhhh</p>
</div>
</script>
// 注册全局组件
<script>
Vue.component('cpn', {
template: '#cpn'
})
</script>
???????
1.6 组件访问vue的数据
- 组件是一个单独功能模块的封装,这个模块有个属于自己的HTML,也应该有属性自己的数据data
- 组件不能直接访问Vue实例中的data
1.6.1 组件自己数据的存放
- 组件对象也有data属性(也可以有methods等属性),但该data属性必须是一个函数,即data(),原因:函数返回自己的对象,相互之间不会互相影响。
- 该函数返回一个对象,对象内部保存着数据
<div id="app">
<my-cpn></my-cpn>
</div>
<template id="myCpn">
<div>消息:{{message}}</div>
</template>
<script>
const app = new Vue({
el:"#app",
components: {
'my-cpn': {
template: "#myCpn",
data() {
return {
message: 'hello world'
}
}
}
}
})
</script>
???????
1.7 父子组件的通信
???????背景:我们知道组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据或是Vue实例数据的。 但在开发中,往往会有一些数据需要从上层传递到下层的,如:在页面中,从服务器请求到了很多数据,其中一部分数据并非是整个大组件来展示的,而是需要下面的子组件来展示。这时并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)
1.7.1 父子组件通信的方法
- 父组件通过 props 向子组件传递数据
- 子组件通过事件 events 向父组件发送信息
- 总结: props down, events up
???????
1.7.2 props基本用法(父组件向子组件传递信息)
父组件向子组件传递数据分为两种方式:动态和静态
1. 静态props
???????子组件要显式地用 props 选项声明它期待获得的数据,静态 Props 通过为子组件在父组件中的占位符添加特性的方式来达到传值的目的
<div id="app">
<child-cpn :message="message"></child-cpn>
</div>
<template id="childCpn">
<div>消息:{{message}}</div>
</template>
<script>
const app = new Vue({
el:"#app",
data: {
message: 'hello'
},
components: {
'child-cpn': {
template: "#childCpn",
props: ['message']
}
}
})
</script>
props传递的过程:
- Vue实例中初始化data
- 子组件中初始化props
<child-cpn :message="message"></child-cpn> 通过:message="message"将data中的数据传给了props(双引号中的message才是绑定的数据变量)- 将props中的数据显示在子组件中
???????
2. 动态props
???????在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的HTML特性相类似,就是用 **v-bind 。**每当父组件的数据变化时,该变化也会传导给子组件
???????
3. props值的两种表达形式
const cpn = {
template: '#cpn',
props:['cmovies','cmessage']
}
- 对象:对象可以设置传递时的类型,也可以设置默认值等
const cpn = {
template: '#cpn',
props: {
cmessage: {
type: String,
default: 'hhhhh',
required: true
}
cmovies: {
type: Array,
default() {
return []
}
}
}
}
???????
4. props中的命名约定
- 对于props声明的属性来说,在父级HTML模板中,属性名需要使用中划线写法,不支持驼峰
var parentNode = {
template: `
<div class="parent">
<child my-message="aaa"></child>
<child :my-message="message"></child>
</div>`,
components: {
'child': childNode
}
};
- 子级props属性声明时,使用小驼峰或者中划线写法都可以;而子级模板使用从父级传来的变量时,需要使用对应的小驼峰写法
var childNode = {
template: '<div>{{myMessage}}</div>',
props:['myMessage']
}
???????
5. props验证
对 props 进行类型等验证时,需要用对象写法
验证支持的数据类型有:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
当有自定义的构造函数时,也支持自定义的类型
Vue.component('my-component', {
props: {
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) {
return ['success','warning','danger'].indexOf(value) !== -1;
}
}
}
})
???????
1.7.3 子传父
方法:通过自定义事件来完成
自定义事件的流程:
- 在子组件中,通过 $emit() 来触发事件
- 在父组件中,通过 v-on (@)来监听子组件事件
<div id="app">
<cpn @itemClick="cpnClick"></cpn> // 监听到子组件事件:itemClick后触发cpnClick事件
</div>
<template id="childCpn">
<div>
<button v-for="item in cactegories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script>
const cpn = {
template: '#cpn',
data() {
return {
categories:[
{id: 'aaa', name: '1'},
{id: 'bbb', name: '2'},
{id: 'ccc', name: '3'},
{id: 'ddd', name: '4'}
]
}
},
methods: {
btnClick(item) {
this.$emit('item-click',item)
}
}
}
const app = new Vue({
el:"#app",
data: {
message: 'hello'
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick');
}
}
})
</script>
??????? 案例:实现两个按钮+1和-1,点击后修改counter。操作过程在子组件中完成,展示交给父组件,就需要把子组件中的counter传给父组件的某个属性,如total
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change">
</cpn>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script>
const cpn = {
template: '#cpn',
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
methods: {
num1Input(event) {
this.dnumber1 = event.target.value;
this.$emit('num1change',this.dnumber1)
this.dnumber2 = this.dnumber1 *100;
this.$emit('num2change',this.dnumber2)
}
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('num2change',this.dnumber2)
this.dnumber2 = this.dnumber2 /100;
this.$emit('num1change',this.dnumber1)
}
}
}
const cpn = {
template: '#cpn',
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
watch: {
dnumber1(newvalue) {
this.dnumber2 = newvalue * 100;
this.$emit('num1change',newvalue);
},
dnumber2(newvalue) {
this.dnumber1 = newvalue / 100;
this.$emit('num2change',newvalue);
}
}
}
const app = new Vue({
el:"#app",
data: {
num1:1,
num2:0
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
}
}
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
}
})
</script>
注意:不要直接去绑定num1,num2 来改变值,改变数据时写一个值
???????
1.8 父子组件的访问方式
- 父组件访问子组件:使用 $children 或 $refs
- 子组件访问父组件:使用 **
p
a
r
e
n
t
?
?
(
访
问
根
组
件
时
可
用
parent** (访问根组件时可用
parent??(访问根组件时可用root)
1.8.1 父组件访问子组件
this.$children 是一个数组类型,包含所有子组件对象
?
???????
1.8.2 子组件访问父组件
<template id="cpn">
<div>
<h2>hhh</h2>
<button @click="btnClick"></button>
</div>
</template>
<script>
const app = new Vue({
el:"#app",
data:{
message:'hhh'
}
components: {
cpn: {
template: '#cpn',
data() {
return {
name:'我是cpn组件的name'
}
},
components: {
ccpn: {
template: "#ccpn",
methods: {
btnClick() {
console.log(this.$parent);
console.log(this.$parent.name);
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
}
}
})
</script>
|