1. 重点提炼
- 组件
- 根组件
- new Vue()
- 可复用组件
- 全局组件
- Vue.component()
- 局部组件
- 组件配置项
- components: {组件名称:组件配置对象}
- 组件配置项
- 全局组件
- 根组件
- data 选项
- 组件私有数据
- 根组件的 data : 对象
- 可复用组件的 data : 一个返回对象的函数
- props
- 组件外部传入数据
- props 验证
- 非prop特性
- 继承
- 禁用继承
- 组件通信
- 父组件->子组件:props
- 子组件->富足见:event
- v-model
- model:选项
- prop
- event
- model:选项
- .sync
- 事件名称 (‘prop:update’, 数据)
2. 组件的注册
在 vue
中,我们可以通过 new Vue
来创建一个组件,不过通常它是作为整个应用的顶层根组件
存在的,我们还可以通过另外的方式来注册一个更为通用的组件(可复用功能性组件)。
3. Vue.component()
Vue.component('组件名称', {组件选项})
- 结构与指令和过滤器一样
组件名称
遵循自定义组件命名规范:全小写、连字符(虽然驼峰式一般也没问题),当作标签使用,为了满足html5
规范- 组件选项与
new Vue
选项配置基本一致(也有一些细节的不同)
创建可复用的组件分为
- 全局组件—整个应用任何位置都可以使用的
- 局部组件—只能中当前注册的组件中使用
3.1 全局组件与局部组件
通过 Vue.component
注册的组件,我们称为全局组件,因为它可以在任意范围内使用,我们还可以定义局部组件
new Vue({
...,
components: {
'组件名称': {组件选项}
}
})
在一个组件内部通过 components
选项注册的组件是局部组件,只能在当前 components
选项所在的组件内部使用
注意:局部注册的组件只能中当前注册的组件中使用,不能在它的子组件中使用
3.2 example01
3.2.1 example01-1
可复用组件中el
选项就是模板中的顶层元素 => <div id="app">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<new-component></new-component>
</div>
<script src="./js/vue.js"></script>
<script>
Vue.component('new-component', {
//el: '' // 就是模板中的顶层元素
template: `
<div>
Github
</div>
`,
});
let app = new Vue({
el: '#app'
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.82
Branch: branch03commit description:a0.82(example01-1——可复用组件的使用)
tag:a0.82
3.2.2 example01-2
同样template
也遵循一些规则,一个组件中顶层元素有且只能有1个。
template: `
<div>
Github
</div>
<div>
Github
</div>
`,
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.83
Branch: branch03commit description:a0.83(example01-2——1个组件中顶层元素有且只能有1个)
tag:a0.83
3.2.3 example01-3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<new-component></new-component>
</div>
<script src="./js/vue.js"></script>
<script>
// 全局组件 - 组件工厂函数
let newComponent = Vue.component('new-component', {
template: `
<div>
Github
</div>
`
});
let app = new Vue({
el: '#app'
});
console.log(app, newComponent);
</script>
</body>
</html>
newComponent
返回的是一个函数
全局组件 - 组件工厂函数
:主要用来构建当前组件的。
Vue.component
函数返回对象,我们可以在app
对象中看到,它和Vue对象
本质上没有太大区别,VueComponent
实际上也是一个组件,只是细节上的不同。实际有点类似,js
原生对象下的_ptoto_
属性,一环套一环。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.84
Branch: branch03commit description:a0.84(example01-3——打印根组件与可复用组件返回值)
tag:a0.84
3.2.4 example01-4
局部组件:注册在组件内部的组件,仅供内部使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<new-component></new-component>
<new-component1></new-component1>
</div>
<script src="./js/vue.js"></script>
<script>
let newComponent = Vue.component('new-component', {
template: `
<div>
Github
</div>
`,
components: {
'new-component1': {
template: `<div>1111</div>`
},
'new-component2': {
template: `<div>2222</div>`
}
}
});
let app = new Vue({
el: '#app'
});
</script>
</body>
</html>
全局使用内部组件就报错了。
提示:有一个未知的自定义元素。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.85
Branch: branch03commit description:a0.85(example01-4——全局使用内部组件就报错了)
tag:a0.85
3.2.5 example01-5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<new-component></new-component>
</div>
<script src="./js/vue.js"></script>
<script>
let newComponent = Vue.component('new-component', {
template: `
<div>
Github
<hr />
<new-component1></new-component1>
<new-component2></new-component2>
</div>
`,
components: {
'new-component1': {
template: `<div>1111</div>`
},
'new-component2': {
template: `<div>2222</div>`
}
}
});
let app = new Vue({
el: '#app'
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.86
Branch: branch03commit description:a0.86(example01-5——局部组件的使用)
tag:a0.86
4. data
在非 new Vue
的组件中,data
必须为函数,函数返回值必须是一个对象,作为组件的最终 data
可复用组件中的data
——和React
的state
一样,每一个组件都有其自身私有的状态,这里data
也一样,它是私有的,只能在当前的组件中使用。
4.1 example02
4.1.1 example02-1
我们在它的html模板
中调用data
,还有在js
当中访问data
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>app组件:{{a}}</p>
<new-component></new-component>
</div>
<script src="./js/vue.js"></script>
<script>
let newComponent = Vue.component('new-component', {
template: `
<div>
aaaaa - {{a}}
</div>
`
});
let app = new Vue({
el: '#app',
data: {
a: 1
}
});
console.log(app, newComponent);
</script>
</body>
</html>
报错,提示当前a
这个属性或者方法是没有定义的。为啥出这种问题呢?
这跟js函数
是不一样的,内部函数可访问函数外部的变量,在这里是行不通的。
可把每个组件理解为它们都是独立私有的空间(相当于C++、Java语言中类中privte性质一样),因此只能在组件的作用域内才能访问变量a
,并不会向下传递。( 如何向下传递一会再说明。)
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.87
Branch: branch03commit description:a0.87(example02-1——在
html模板
中调用data
,还有在js
当中访问data
)tag:a0.87
4.1.2 example02-2
在new-component
组件内部定义一个b
,看是否能在其内部的template
访问。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>app组件:{{a}}</p>
<new-component></new-component>
</div>
<script src="./js/vue.js"></script>
<script>
let newComponent = Vue.component('new-component', {
data: {
b: 100
},
template: `
<div>
aaaaa - {{b}}
</div>
`
});
let app = new Vue({
el: '#app',
data: {
a: 1
}
});
console.log(app, newComponent);
</script>
</body>
</html>
还是报错了,但是另外的一个错误了,警告data
选项必须是一个函数,并且必须返回一个per-instance
值,这个意思实际是 =>
可复用组件中,data
必须是一个函数,且该函数必须返回一个对象,该对象就是组件最终的 data
值。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.88
Branch: branch03commit description:a0.88(example02-2——在可复用组件内部定义一个变量,看能否在其内部的template访问)
tag:a0.88
4.1.3 example02-3
可复用组件中,data 必须是一个函数,且该函数必须返回一个对象,该对象就是组件最终的 data 值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>app组件:{{a}}</p>
<new-component></new-component>
</div>
<script src="./js/vue.js"></script>
<script>
let newComponent = Vue.component('new-component', {
data() {
return {
b: 100
}
},
template: `
<div>
aaaaa - {{b}}
</div>
`
});
let app = new Vue({
el: '#app',
data: {
a: 1
}
});
console.log(app, newComponent);
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.89
Branch: branch03commit description:a0.89(example02-3——可复用组件中,data 必须是一个函数)
tag:a0.89
vue
为什么需要这样做呢?为什么可复用组件的data
必须是一个返回对象的函数?
vue
中的html模板
最终会被解析成虚拟dom
,再根据虚拟dom
解析成html
,碰上普通的html标签
直接渲染即可,那碰上自定义组件呢?
自定义组件标签实际是一个函数。
4.1.4 example02-4
这个原理大致模拟一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let Vue = {};
class VueComponent {
constructor(options) {
this._opts = options;
this._data = this._opts.data;
}
}
let newComponent1 = new VueComponent({
name: 'new-component1',
data: {
a: 1
}
});
let newComponent2 = new VueComponent({
name: 'new-component2',
data: {
a: 1
}
});
// newComponent1._data 与 newComponent2._data 是否是同一个对象?
console.log(newComponent1, newComponent2);
newComponent2._data.a = 100;
console.log(newComponent1, newComponent2);
</script>
</body>
</html>
两者不是同一个对象,只是起初长得一样。
修改值了后其实两者是不干扰的。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.90
Branch: branch03commit description:a0.90(example02-4——原生js探究vue原理-new出的多个对象不是同一个对象,互相独立)
tag:a0.90
4.1.5 example02-5
component
工厂函数,对new
的行为进行了包装
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let Vue = {};
class VueComponent {
constructor(options) {
this._opts = options;
this._data = this._opts.data;
}
}
Vue.component = function(name, options) {
return new VueComponent({
name,
...options
});
};
let newComponent1 = Vue.component('new-component1', {
data: {
a: 1
}
});
console.log( newComponent1 )
</script>
</body>
</html>
原生Vue.component
返回一个函数,这里我们返回的是一个对象
,但这存在着一个复用的问题 => Vue
要复用newComponent1
,页面要使用两次,得调用两次<newComponent1> </newComponent1>
、<newComponent1> </newComponent1>
。
相反并不是用一次,Vue.component
出来一个。
而这里如果再想拎起炉灶,还需要再Vue.component
赋给一个新对象,最后在写成标签的形式,这样本质上就没得到复用了。
Vue
本就为了便捷,这样使用又使代码非常冗余了。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.91
Branch: branch03commit description:a0.91(example02-5——原生js探究vue原理-component 工厂函数,对new的行为进行了包装)
tag:a0.91
4.1.6 example02-6
所以实际上Vue.component
返回一个函数,这个函数返回一个新的对象。
<newComponent1> </newComponent1>
在页面中只有使用一次,即html模板
解析过程中,它就会被直接解析成函数调用,每次返回出不同的组件对象,这就达到了复用性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let Vue = {};
class VueComponent {
constructor(options) {
this._opts = options;
this._data = this._opts.data;
}
}
// component 工厂函数,对new的行为进行了包装
Vue.component = function(name, options) {
return function() {
return new VueComponent({
name,
...options
});
}
};
let newComponent1 = Vue.component('new-component1', {
data() {
return {
a: 1
}
}
});
let new1 = newComponent1();
let new2 = newComponent1();
console.log(new1, new2);
new1._data.a = 100;
console.log(new1, new2);
</script>
</body>
</html>
我们尝试修改一个组件的data
数据,发现除了复用组件,其组件内部的选项也被复用了,从而复用了option
配置项,没必要构建一堆组件,来一组创建对象。
它们都是用同一套option
配置项(配置项中的data
对象传的地址,始终是同一个对象,因为js
没有指针和引用的概念,小迪就建议大家理解为浅拷贝即可,如果会c++、java
这类语言,想成指针,始终指向一个对象)
这样就导致两个对象其实是共享这个data
的,这样问题就大了,容易出现一堆问题。
可能有的浏览器会直接优化成两次打印都一样了,可能浏览器动态打印了最新的数据,因此可以打断点一步一步执行,就可打印出真实结果了。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.92
Branch: branch03commit description:a0.92(example02-6——原生js探究vue原理-data是对象带来的问题)
tag:a0.92
4.1.7 example02-7
所以通过Vue.component
创建的组件,data
不能是对象,它必须是一个函数,在内部调用data
函数,它返回一个对象,然后把函数返回的对象返回给data
。如果每次通过标签调用该函数,data
函数都会返回一个新对象,这样每个组件的data
是不同的对象了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let Vue = {};
class VueComponent {
constructor(options) {
this._opts = options;
this._data = this._opts.data;
}
}
// component 工厂函数,对new的行为进行了包装
Vue.component = function(name, options) {
return function() {
return new VueComponent({
name,
...options,
data: options.data() // options.data()函数返回的对象赋值给data,成为一个新对象
});
}
};
let newComponent1 = Vue.component('new-component1', {
data() {
return {
a: 1
}
}
});
let new1 = newComponent1();
let new2 = newComponent1();
console.log(new1, new2);
new1._data.a = 100;
console.log(new1, new2);
</script>
</body>
</html>
这样data
就不共享,它们都是独立的对象了。这就是为啥Vue
中的Vue.component
方法配置项的data
属性必须是一个函数了,目的就是复用该组件的时候,能够产生不一样的data
数据。根组件中为啥不需要data
是函数呢?因为一个应用中只有一个根组件,不会产生复用问题,不会有多个根组件,因此就不会产生数据不独立的问题了。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.93
Branch: branch03commit description:a0.93(example02-7——原生js探究vue原理-解决data是对象带来的问题,探究真相)
tag:a0.93
5. props
组件中内部私有数据存储中组件 data
中,通过外部传入的数据,则通过 props<
选项接收
- 如果传入的
props
值为一个表达式,则必须使用v-bind
- 组件中的
data
和props
数据都可以通过组件实例进行直接访问 data
中的key
与props
中的key
不能冲突
5.1 example03
实现:计算圆面积
5.1.1 example03-1
html模板
中:r="n1"
=> 把父级的n1属性
值传递给子级props
下的r
属性。
props: ['r']
=> 当前这个组件接受的props
参数:r
=> 等同于当前组件下有r属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-circle :r="n1"></me-circle>
<hr />
<me-circle :r="n2"></me-circle>
</div>
<script src="./js/vue.js"></script>
<script>
let meCircle = Vue.component('me-circle', {
props: ['r'],
template: `<div>r: {{r}} -> {{3.14 * r * r}}</div>`
});
let app = new Vue({
el: '#app',
data: {
n1: 10,
n2: 100
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.94
Branch: branch03commit description:a0.94(example03-1——props数据进行传递)
tag:a0.94
5.1.2 example03-2
因为 props
和 data
中的数据访问一致,所以如果命名一致肯定会报错,因此命名的时候千万不要相同而冲突了。
let meCircle = Vue.component('me-circle', {
props: ['r'],
data() {
return {
r: 1
}
},
template: `<div>r: {{r}} -> {{3.14 * r * r}}</div>`
});
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.95
Branch: branch03commit description:a0.95(example03-2——props与data命名冲突)
tag:a0.95
建议data
属性命名不要仅以**_
开头,容易与vue对象的属性冲突**
可以是如下形式:
data() {
return {
me_r: 1
}
}
6. 组件通信
如有一些需求需要子组件去更改父组件的值怎么办?
注意:不要修改
props
传入的数据
父组件通过 props
传入数据到子组件内部,但是子组件内部不要修改外部传入的 props
,vue
提供了一种事件机制通知父级更新,父级中使用子组件的时候监听对应的事件绑定对应的处理函数即可。
6.1 example04
需求:假如有一个商品列表,我们需要把这个商品列表显示出来,类似购物车列表。
6.1.1 example04-1
简单展示商品列表
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list v-for="item of items" :key="item.id" :data="item"></me-list>
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{data.quantity}}</span>
<button>+</button>
</div>
`
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.96
Branch: branch03commit description:a0.96(example04-1——简单展示商品列表)
tag:a0.96
6.1.2 example04-2
点击+、-
改变商品数量,从而设置一个总数量。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list v-for="item of items" :key="item.id" :data="item"></me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{data.quantity}}</span>
<button>+</button>
</div>
`,
methods: {
}
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.97
Branch: branch03commit description:a0.97(example04-2——简单展示商品列表-总数)
tag:a0.97
6.1.2.1 note
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
计算数组元素相加后的总和
参数 | 描述 |
---|---|
function(total,currentValue, index,arr) | total 必需。初始值, 或者计算结束后的返回值。 currentValue 必需。当前元素 currentIndex 可选。当前元素的索引 arr 可选。当前元素所属的数组对象。 |
initialValue | 可选。传递给函数的初始值 |
6.1.3 example04-3
子组件操作该商品数量的加减
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list v-for="item of items" :key="item.id" :data="item"></me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{data.quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
// 加数量
increment() {
this.data.quantity++;
}
}
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.98
Branch: branch03commit description:a0.98(example04-3——传地址子级可修改父级的data)
tag:a0.98
6.1.3.1 小结
我们看以上运行是正常的,其实隐藏着很大的坑,现在传进来的数据是一个(item
)对象。
那为什么子组件修改该组件,父组件就会重新渲染视图?那父组件如何知道数据发生变化呢?
我们现在传进去的item
是一个对象,对象传递是传址
,这里实际是子级修改了父级的item对象
,父级监听到自己的数据变化了,肯定会重新渲染视图的。
这会产生很严重的问题!如=>可能导致多个模块共享一个对象,这边修改,影响了那边。
6.1.4 example04-4
我们尽量不要去传址,共用对象。
数量我们单独去传,即传进去的并不是是一个对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list
v-for="item of items"
:key="item.id"
:data="item"
:quantity="item.quantity"
></me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data', 'quantity'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
// 加数量
increment() {
this.quantity++;
}
}
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
}
});
</script>
</body>
</html>
报错了。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.99
Branch: branch03commit description:a0.99(example04-3——传值子级可修改父级的data被禁止)
tag:a0.99
6.1.4.1 重要提炼与总结
实际vue
是禁止修改传进去的数据的,vue
是不建议直接修改(建议只能读)props
传入的数据(但是不代表不能改 => 不能修改的原因与React
是一样的,这里就不再详细赘述了),因为传入的数据不仅仅是当前这个组件使用,可能其它组件也在用这个数据,为了保证数据操作的安全性。
如果在子组件中increment
方法中调用this.data = {...}
(直接赋值给对象),它是可以监控到并且报错,但是vue
中的坑
则是修改对象底下某个属性,它是监测不到的,因此不会报错。因此遵循vue
规范,不要这么去做。
最正确的方法 =>
通知父级自己去修改,即通知数据持有人去修改。
使用
vue
的事件通知机制,类似于React
的回调函数,其本质上是差不多的。在父级创建一个专门修改该数据的函数,传给父级允许修改父级数据的子级,由子级调用该回调函数,就间接修改了父级数据了。
6.2 $emit()
vue
为每个组件对象提供了一个内置方法 $emit
,它等同于自定义事件中的 new Event
,trigger
等
this.$emit('自定义事件名称', 事件数据)
-
事件数据就是中触发事件的同时携带传递的数据 -
event
-
父级在使用该组件的过程中,可以通过
@事件名称
来注册绑定事件函数 -
事件函数的第一个参数就是事件数据
6.2.1 example05
6.2.1.1 example05-1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list
v-for="item of items"
:key="item.id"
:data="item"
:quantity="item.quantity"
:fn="edit"
></me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data', 'quantity', 'fn'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
// 加数量
increment() {
this.fn();
}
}
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
},
edit(item, quantity) {
console.log('fn');
}
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.01
Branch: branch03commit description:a1.01(example05-1——往子级传递回调函数修改data报错)
tag:a1.01
6.2.1.2 example05-2
报错了,输出看看
methods: {
// 加数量
increment() {
console.log(this.fn);
}
}
我们发现,其实本质上能拿到函数的,只是vue
过滤了。
React
是将一个函数作为参数传进去,然后当想增加数量的时候,直接调用父级传进来的函数。
然而在vue
中,它禁止你传函数了。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.02
Branch: branch03commit description:a1.02(example05-2——探究往子级传递回调函数修改data报错的原因)
tag:a1.02
6.2.1.2.1 紧急纠错—纠正上述说法
小迪很抱歉,上面的代码写的有问题,并且分析错了,我们错把方法写在computed
里了,实际这里的edit
并不是函数,还是属性值,这里只是edit
属性的get
方法而已,所以我们没法把属性当做函数传递,你把它当做函数获取,肯定是找不到这个函数了。
实际上Vue
是可以通过回调函数进行子级修改父级data的!!!!!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list
v-for="item of items"
:key="item.id"
:data="item"
:quantity="item.quantity"
:fn="edit"
></me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data', 'quantity', 'fn'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
// 加数量
increment() {
this.fn(this.data, this.data.quantity + 1);
}
}
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
},
methods: {
edit(item, quantity) {
item.quantity = quantity;
}
}
});
</script>
</body>
</html>
额外观点
=> 小迪还是鼓励大家多犯错的,小迪就因为这样的低级错误,既复习了computed,又更深挖了vue回调机制。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.03
Branch: branch03commit description:a1.03(example05-2——探究往子级传递回调函数修改data报错的原因——纠正错误说法)
tag:a1.03
6.2.1.3 example05-3
原生js
中讲过,除了用回调,还可以用事件机制解决。
在vue
中每个组件下都有一个属性$emit()
,它等同于自定义事件中的 new Event
,trigger
等,它的第一个参数this.$emit('abc'
,定义事件名称,然后在父级用v-on:abc="edit"
监听事件,并设置事件处理函数,我们在点击+
的时候发送事件即可,就可以触发对应事件处理函数了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list
v-for="item of items"
:key="item.id"
:data="item"
:quantity="item.quantity"
v-on:abc="edit"
></me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['data', 'quantity', 'fn'],
template: `
<div>
<span>{{data.name}}</span>
<button>-</button>
<span>{{quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
// 加数量
increment() {
this.$emit('abc', this.data, this.data.quantity + 1);
}
}
});
let app = new Vue({
el: '#app',
data: {
// quantity 商品初始化的数量
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
},
methods: {
edit(item, quantity) {
item.quantity = quantity;
}
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.04
Branch: branch03commit description:a1.04(example05-3——vue事件机制实现子级修改父级data)
tag:a1.04
6.2.1.3.1 note
React
解决props
这种父子传递问题,用的是回调函数,而Vue
用的是事件机制,它封装了自定义事件的方法,我们直接去使用即可。
以上实际就是一个单向数据流。
6.3 小结
综上实现子级修改父级data
的方法:
- 回调函数
- vue事件机制
第一种是用原生的手段,我还是建议用事件机制更稳妥一些。(由于小迪水平暂时有限,无法深入探究两者的优缺点,还请高手赐教)
7. 组件双绑的实现
上面的例子实际就是一个单向数据流,组件双绑实际就是一个语法糖。
虽然并不推荐在组件内部修改 props
,但是,有的时候确实希望组件内部状态变化的时候改变 props
,我们可以通过子组件触发事件,父级监听事件来达到这个目的,不过过程会比较繁琐,vue
提供了一些操作来简化这个过程。
7.1 v-model
v-model
是 vue
提供的一个用于实现数据双向绑定的指令,用来简化 props 到 data
,data 到 props
的操作流程。
7.1.1 model 选项
prop
指定要绑定的属性,默认是 value
event
指定要绑定触发的事件,默认是 input
事件
7.1.2 example06
上面的案例数据是单向的,数据往子级传,如果要修改数据,通过事件机制传播到父级,然后再由父级修改的。
双绑其实就是障眼法,即一种语法糖。
其实上面的案例实际就实现了双绑,已经实现数据双向流了,只是我们的v-mode
,如果子级修改数据不需要事件机制,而是子级直接修改。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list
v-for="item of items"
:key="item.id"
:name="item.name"
v-model="item.quantity"
>
</me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['name', 'quantity'],
// v-model的值 赋值给 prop指定的属性值
// event abc 当你触发abc事件,第二个参数值将自动去更新在 v-model当中所绑定的值
model: {
prop: 'quantity',
event: 'abc'
},
template: `
<div>
<span>{{name}}</span>
<button>-</button>
<span>{{quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
increment() {
this.$emit('abc', this.quantity + 1);
}
}
});
let app = new Vue({
el: '#app',
data: {
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.05
Branch: branch03commit description:a1.05(example06—v-model)
tag:a1.05
7.1.2.1 深度解析
v-model
伪代码
:quantity = 'item.quantity'
@abc = 'function(v){item.quantity+v}'
v-model
如何识别呢?外界传进去的值,到底赋给了内部哪个属性呢?
这个由model
配置项中的prop
属性指定内部对应的props
。
event: ‘abc’
=> 当触发的事件是abc
的话,其第二个参数(this.$emit('abc', this.quantity + 1)
)的值将会自动更新v-model
中所绑定的值(v-model="item.quantity"
)
以上就是关联关系了。
即v-model="item.quantity"
赋值给了内部的谁去用了?内部触发什么事件可以修改外部绑定的值?这之间的两种关系,是通过model配置项
告知v-model
的,这样就可以同步更新数据了。
实际上与上面的事件机制没什么区别,只是少了一句v-on:abc="edit"
代码,也不需要在父级写一个修改属性值的回调函数了,这里v-model
的内部把这些工作都做了,只是封装起来了而已。
7.1.3 注意事项
注意:model
中的prop
和event
,prop
默认传递给value
,event
默认是input
事件,针对不同组件即v-model绑定到不同类型的组件上面,如input
组件时遵循此规则,如checkbox、radio、select、prop
默认传递给checked
或selected
属性,event
默认是change
事件。
7.1.4 极为重要的注意事项
注意:官方建议避免使用v-model
,原因在于如果封装一个组件,不告诉怎么使用,外面的人是根本不知道它的实现细节的(不看源码),不知道v-model
的隐藏细节,可能导致一些奇怪的问题。所以使用大量v-model
,测试起来会非常麻烦,出了问题很难定位。不过用事件机制解决,又麻烦,Vue
提供了两者的优点而集成的.sync
,我推荐使用它。
7.2 .sync
通过 v-model
来进行双向绑定,会给状态维护带来一定的问题,因为修改比较隐蔽,同时只能处理一个 prop
的绑定,我们还可以通过另外一种方式来达到这个目的。
传递参数的时候需要加上 .sync
=> :quantity.sync="item.quantity"
实际跟v-model
的概念是一样的,子级内部quantity
属性发生变化的时候,能够同步到父级的item.quantity
上来。
7.2.1 update:[prop]
这里事件名称要使用 update
加上 prop
名称 的格式
this.$emit('update:quantity', this.quantity + 1);
=> 同步到父级的item.quantity
时,触发该事件,更新父级的data
。
7.2.2 example07
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<me-list
v-for="item of items"
:key="item.id"
:name="item.name"
:quantity.sync="item.quantity"
>
</me-list>
<hr>
总数:{{count}}
</div>
<script src="./js/vue.js"></script>
<script>
let meList = Vue.component('me-list', {
props: ['name', 'quantity'],
template: `
<div>
<span>{{name}}</span>
<button>-</button>
<span>{{quantity}}</span>
<button @click="increment">+</button>
</div>
`,
methods: {
increment() {
this.$emit('update:quantity', this.quantity + 1);
}
}
});
let app = new Vue({
el: '#app',
data: {
items: [
{id: 1, name: 'iphonex', quantity: 1},
{id: 2, name: 'imac', quantity: 1},
{id: 3, name: 'ipad', quantity: 1}
]
},
computed: {
count() {
return this.items.reduce( (n, item) => {
return n + item.quantity;
}, 0 );
}
}
});
</script>
</body>
</html>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.06
Branch: branch03commit description:a1.06(example07—.sync的使用)
tag:a1.06
7.2.3 深入探究
:quantity.sync="item.quantity"
意思是把"item.quantity"
传递给内部props
的quantity
(父传子),同时内部quantity
发生变化的时候,可把值同步到"item.quantity"
。
内部事件特殊的起名 => update:quantity
(要修改的属性),quantity
就是需要修改的属性。.sync
代表当更新子级的quantity
,发现了此修饰符触发事件,就主动修改父级的item.quantity
(将this.quantity + 1
赋值过去)
通过这种方式,能明显看出来是影响的内部哪个属性,内部的哪个属性更新后会同步到外部来,而v-model
却明显看不出来(除非查看model
属性下的prop
和event
配置项)。
同时省略掉事件监听与事件函数。因此.sync
结合了v-model
和v-model
两者的优点,而摒弃了它们的缺点。它能够方便子级同步更新父级的data
,同时能够直观地感受到内部是什么属性,与外界什么属性进行绑定的。
所以建议使用这种方式。
8. 小结&杂谈
以上方法这些写法都是可以的,有时候觉得使用起来还不错,但是太多的api
(给我们安排得明明白白的,并不完全是一件好事),因为很多时候,需要靠自己去推测(写着写着就忘了)。
Vue
用起来很方便,内部细节是不知道的,都需要自己去推测。
而内部的语法糖根据js
的用法去推测,又不是那么不一致,即不能根据常理去推测,内部细节不去看源码根本理不清楚。
vue
虽然用起来爽,但内部封装的api
实在是太多太多了,过于繁琐,有的时候写的过程中出了奇怪的问题,推测根本解决不了问题,就得爬源码。而且规则很多,也很容易掉坑了出不来,因此对于有js
或者其他语言开发的底蕴,小迪还是更喜欢React
,尽管没有vue
用起来爽,但是坑往往被挖的很深,并且需要记忆的东西很多,必须强行记忆。
不过多于小迪刚初来乍到的vue新手,势必被割一波韭菜,但又不是真正意味的割韭菜,掉的坑多了,后面就成高手了,小迪建议学完vue基本使用,一定要往源码上深挖。
(后续待补充)