Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)

文章目录

  • 1. 重点提炼
  • 2. 组件的注册
  • 3. Vue.component()
    • 3.1 全局组件与局部组件
    • 3.2 example01
      • 3.2.1 example01-1
      • 3.2.2 example01-2
      • 3.2.3 example01-3
      • 3.2.4 example01-4
      • 3.2.5 example01-5
  • 4. data
    • 4.1 example02
      • 4.1.1 example02-1
      • 4.1.2 example02-2
      • 4.1.3 example02-3
      • 4.1.4 example02-4
      • 4.1.5 example02-5
      • 4.1.6 example02-6
      • 4.1.7 example02-7
  • 5. props
    • 5.1 example03
      • 5.1.1 example03-1
      • 5.1.2 example03-2
  • 6. 组件通信
    • 6.1 example04
      • 6.1.1 example04-1
      • 6.1.2 example04-2
        • 6.1.2.1 note
      • 6.1.3 example04-3
        • 6.1.3.1 小结
      • 6.1.4 example04-4
        • 6.1.4.1 重要提炼与总结
    • 6.2 $emit()
      • 6.2.1 example05
        • 6.2.1.1 example05-1
        • 6.2.1.2 example05-2
          • 6.2.1.2.1 紧急纠错—纠正上述说法
        • 6.2.1.3 example05-3
          • 6.2.1.3.1 note
    • 6.3 小结
  • 7. 组件双绑的实现
    • 7.1 v-model
      • 7.1.1 model 选项
      • 7.1.2 example06
        • 7.1.2.1 深度解析
      • 7.1.3 注意事项
      • 7.1.4 极为重要的注意事项
    • 7.2 .sync
      • 7.2.1 update:[prop]
      • 7.2.2 example07
      • 7.2.3 深入探究
  • 8. 小结&杂谈

1. 重点提炼

  • 组件
    • 根组件
      • new Vue()
    • 可复用组件
      • 全局组件
        • Vue.component()
      • 局部组件
        • 组件配置项
          • components: {组件名称:组件配置对象}
  • data 选项
    • 组件私有数据
    • 根组件的 data : 对象
    • 可复用组件的 data : 一个返回对象的函数
  • props
    • 组件外部传入数据
    • props 验证
    • 非prop特性
      • 继承
      • 禁用继承
  • 组件通信
    • 父组件->子组件:props
    • 子组件->富足见:event
  • v-model
    • model:选项
      • prop
      • event
  • .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 选项就是模板中的顶层元素 =>


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
 
    <div id="app">
        <new-component>new-component>
    div>
 
    <script src="./js/vue.js">script>
 
    <script>
        Vue.component('new-component', {
            //el: ''  // 就是模板中的顶层元素
            template: `
                
Github
`
, }); let app = new Vue({ el: '#app' });
script> body> html>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第1张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.82
Branch: branch03

commit description:a0.82(example01-1——可复用组件的使用)

tag:a0.82

3.2.2 example01-2

同样template也遵循一些规则,一个组件中顶层元素有且只能有1个。

          template: `
                
Github
Github
`
,

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第2张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.83
Branch: branch03

commit description:a0.83(example01-2——1个组件中顶层元素有且只能有1个)

tag:a0.83

3.2.3 example01-3


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>

<div id="app">
    <new-component>new-component>
div>

<script src="./js/vue.js">script>

<script>

    // 全局组件 - 组件工厂函数
    let newComponent = Vue.component('new-component', {

        template: `
                
Github
`
}); let app = new Vue({ el: '#app' }); console.log(app, newComponent);
script> body> html>

newComponent返回的是一个函数

全局组件 - 组件工厂函数:主要用来构建当前组件的。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第3张图片

Vue.component函数返回对象,我们可以在app对象中看到,它和Vue对象本质上没有太大区别,VueComponent实际上也是一个组件,只是细节上的不同。实际有点类似,js原生对象下的_ptoto_属性,一环套一环。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第4张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.84
Branch: branch03

commit description:a0.84(example01-3——打印根组件与可复用组件返回值)

tag:a0.84

3.2.4 example01-4

局部组件:注册在组件内部的组件,仅供内部使用


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
Github
`
, components: { 'new-component1': { template: `
1111
`
}, 'new-component2': { template: `
2222
`
} } }); let app = new Vue({ el: '#app' });
script> body> html>

全局使用内部组件就报错了。

提示:有一个未知的自定义元素。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第5张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.85
Branch: branch03

commit description:a0.85(example01-4——全局使用内部组件就报错了)

tag:a0.85

3.2.5 example01-5


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>

<div id="app">
    <new-component>new-component>
div>

<script src="./js/vue.js">script>

<script>

    let newComponent = Vue.component('new-component', {
        template: `
            
Github
`
, components: { 'new-component1': { template: `
1111
`
}, 'new-component2': { template: `
2222
`
} } }); let app = new Vue({ el: '#app' });
script> body> html>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第6张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.86
Branch: branch03

commit description:a0.86(example01-5——局部组件的使用)

tag:a0.86

4. data

在非 new Vue 的组件中,data 必须为函数,函数返回值必须是一个对象,作为组件的最终 data

可复用组件中的data——和Reactstate一样,每一个组件都有其自身私有的状态,这里data也一样,它是私有的,只能在当前的组件中使用。

4.1 example02

4.1.1 example02-1

我们在它的html模板中调用data,还有在js当中访问data


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
aaaaa - {{a}}
`
}); let app = new Vue({ el: '#app', data: { a: 1 } }); console.log(app, newComponent);
script> body> html>

报错,提示当前a这个属性或者方法是没有定义的。为啥出这种问题呢?

这跟js函数是不一样的,内部函数可访问函数外部的变量,在这里是行不通的。

可把每个组件理解为它们都是独立私有的空间(相当于C++、Java语言中类中privte性质一样),因此只能在组件的作用域内才能访问变量a,并不会向下传递。( 如何向下传递一会再说明。)

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第7张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.87
Branch: branch03

commit description:a0.87(example02-1——在html模板中调用data,还有在js当中访问data

tag:a0.87

4.1.2 example02-2

new-component组件内部定义一个b,看是否能在其内部的template访问。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
aaaaa - {{b}}
`
}); let app = new Vue({ el: '#app', data: { a: 1 } }); console.log(app, newComponent);
script> body> html>

还是报错了,但是另外的一个错误了,警告data选项必须是一个函数,并且必须返回一个per-instance值,这个意思实际是 =>

可复用组件中,data必须是一个函数,且该函数必须返回一个对象,该对象就是组件最终的 data值。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第8张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.88
Branch: branch03

commit description:a0.88(example02-2——在可复用组件内部定义一个变量,看能否在其内部的template访问)

tag:a0.88

4.1.3 example02-3

可复用组件中,data 必须是一个函数,且该函数必须返回一个对象,该对象就是组件最终的 data 值。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
aaaaa - {{b}}
`
}); let app = new Vue({ el: '#app', data: { a: 1 } }); console.log(app, newComponent);
script> body> html>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第9张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.89
Branch: branch03

commit description:a0.89(example02-3——可复用组件中,data 必须是一个函数)

tag:a0.89

vue为什么需要这样做呢?为什么可复用组件的data必须是一个返回对象的函数?

vue中的html模板最终会被解析成虚拟dom,再根据虚拟dom解析成html,碰上普通的html标签直接渲染即可,那碰上自定义组件呢?

自定义组件标签实际是一个函数。

4.1.4 example02-4

这个原理大致模拟一下:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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>

两者不是同一个对象,只是起初长得一样。

修改值了后其实两者是不干扰的。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第10张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.90
Branch: branch03

commit description:a0.90(example02-4——原生js探究vue原理-new出的多个对象不是同一个对象,互相独立)

tag:a0.90

4.1.5 example02-5

component工厂函数,对new的行为进行了包装


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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,页面要使用两次,得调用两次

相反并不是用一次,Vue.component出来一个。

而这里如果再想拎起炉灶,还需要再Vue.component赋给一个新对象,最后在写成标签的形式,这样本质上就没得到复用了。

Vue本就为了便捷,这样使用又使代码非常冗余了。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第11张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.91
Branch: branch03

commit description:a0.91(example02-5——原生js探究vue原理-component 工厂函数,对new的行为进行了包装)

tag:a0.91

4.1.6 example02-6

所以实际上Vue.component返回一个函数,这个函数返回一个新的对象。

在页面中只有使用一次,即html模板解析过程中,它就会被直接解析成函数调用,每次返回出不同的组件对象,这就达到了复用性。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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的,这样问题就大了,容易出现一堆问题。

可能有的浏览器会直接优化成两次打印都一样了,可能浏览器动态打印了最新的数据,因此可以打断点一步一步执行,就可打印出真实结果了。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第12张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.92
Branch: branch03

commit description:a0.92(example02-6——原生js探究vue原理-data是对象带来的问题)

tag:a0.92

4.1.7 example02-7

所以通过Vue.component创建的组件,data不能是对象,它必须是一个函数,在内部调用data函数,它返回一个对象,然后把函数返回的对象返回给data。如果每次通过标签调用该函数,data函数都会返回一个新对象,这样每个组件的data是不同的对象了。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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是函数呢?因为一个应用中只有一个根组件,不会产生复用问题,不会有多个根组件,因此就不会产生数据不独立的问题了。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第13张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.93
Branch: branch03

commit description:a0.93(example02-7——原生js探究vue原理-解决data是对象带来的问题,探究真相)

tag:a0.93

5. props

组件中内部私有数据存储中组件 data 中,通过外部传入的数据,则通过 props< 选项接收

  • 如果传入的 props 值为一个表达式,则必须使用 v-bind
  • 组件中的 dataprops 数据都可以通过组件实例进行直接访问
  • data 中的 keyprops 中的 key 不能冲突

5.1 example03

实现:计算圆面积

5.1.1 example03-1

html模板:r="n1" => 把父级的n1属性值传递给子级props下的r属性。

props: ['r'] => 当前这个组件接受的props参数:r => 等同于当前组件下有r属性


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
r: {{r}} -> {{3.14 * r * r}}
`
}); let app = new Vue({ el: '#app', data: { n1: 10, n2: 100 } });
script> body> html>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第14张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.94
Branch: branch03

commit description:a0.94(example03-1——props数据进行传递)

tag:a0.94

5.1.2 example03-2

因为 propsdata中的数据访问一致,所以如果命名一致肯定会报错,因此命名的时候千万不要相同而冲突了。

        let meCircle = Vue.component('me-circle', {

            props: ['r'],
            data() {
                return {
                    r: 1
                }
            },
            template: `
r: {{r}} -> {{3.14 * r * r}}
`
});

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第15张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.95
Branch: branch03

commit description:a0.95(example03-2——props与data命名冲突)

tag:a0.95

建议data属性命名不要仅以**_开头,容易与vue对象的属性冲突**

可以是如下形式:

 data() {
                return {
                    me_r: 1
                }
        }

6. 组件通信

如有一些需求需要子组件去更改父组件的值怎么办?

注意:不要修改 props 传入的数据

父组件通过 props 传入数据到子组件内部,但是子组件内部不要修改外部传入的 propsvue 提供了一种事件机制通知父级更新,父级中使用子组件的时候监听对应的事件绑定对应的处理函数即可。

6.1 example04

需求:假如有一个商品列表,我们需要把这个商品列表显示出来,类似购物车列表。

6.1.1 example04-1

简单展示商品列表


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
            
{{data.name}} {{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} ] } });
script> body> html>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第16张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.96
Branch: branch03

commit description:a0.96(example04-1——简单展示商品列表)

tag:a0.96

6.1.2 example04-2

点击+、-改变商品数量,从而设置一个总数量。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
{{data.name}} {{data.quantity}}
`
, 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>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第17张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.97
Branch: branch03

commit 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

子组件操作该商品数量的加减


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
            
{{data.name}} {{data.quantity}}
`
, 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>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第18张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.98
Branch: branch03

commit description:a0.98(example04-3——传地址子级可修改父级的data)

tag:a0.98

6.1.3.1 小结

我们看以上运行是正常的,其实隐藏着很大的坑,现在传进来的数据是一个(item)对象。

那为什么子组件修改该组件,父组件就会重新渲染视图?那父组件如何知道数据发生变化呢?

我们现在传进去的item是一个对象,对象传递是传址,这里实际是子级修改了父级的item对象,父级监听到自己的数据变化了,肯定会重新渲染视图的。

这会产生很严重的问题!如=>可能导致多个模块共享一个对象,这边修改,影响了那边。

6.1.4 example04-4

我们尽量不要去传址,共用对象。

数量我们单独去传,即传进去的并不是是一个对象。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
            
{{data.name}} {{quantity}}
`
, 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>

报错了。
Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第19张图片

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第20张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.99
Branch: branch03

commit 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


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
            
{{data.name}} {{quantity}}
`
, 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>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第21张图片

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第22张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.01
Branch: branch03

commit description:a1.01(example05-1——往子级传递回调函数修改data报错)

tag:a1.01

6.2.1.2 example05-2

报错了,输出看看

            methods: {
                // 加数量
                increment() {
                    console.log(this.fn);
                }
            }

我们发现,其实本质上能拿到函数的,只是vue过滤了。

React是将一个函数作为参数传进去,然后当想增加数量的时候,直接调用父级传进来的函数。

然而在vue中,它禁止你传函数了。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第23张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.02
Branch: branch03

commit description:a1.02(example05-2——探究往子级传递回调函数修改data报错的原因)

tag:a1.02

6.2.1.2.1 紧急纠错—纠正上述说法

小迪很抱歉,上面的代码写的有问题,并且分析错了,我们错把方法写在computed里了,实际这里的edit并不是函数,还是属性值,这里只是edit属性的get方法而已,所以我们没法把属性当做函数传递,你把它当做函数获取,肯定是找不到这个函数了。

实际上Vue是可以通过回调函数进行子级修改父级data的!!!!!!


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
            
{{data.name}} {{quantity}}
`
, 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回调机制。

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第24张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.03
Branch: branch03

commit 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"监听事件,并设置事件处理函数,我们在点击+的时候发送事件即可,就可以触发对应事件处理函数了。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
            
{{data.name}} {{quantity}}
`
, 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>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第25张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.04
Branch: branch03

commit 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-modelvue 提供的一个用于实现数据双向绑定的指令,用来简化 props 到 datadata 到 props 的操作流程。

7.1.1 model 选项

prop 指定要绑定的属性,默认是 value

event 指定要绑定触发的事件,默认是 input 事件

7.1.2 example06

上面的案例数据是单向的,数据往子级传,如果要修改数据,通过事件机制传播到父级,然后再由父级修改的。

双绑其实就是障眼法,即一种语法糖。

其实上面的案例实际就实现了双绑,已经实现数据双向流了,只是我们的v-mode,如果子级修改数据不需要事件机制,而是子级直接修改。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
{{name}} {{quantity}}
`
, 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>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第26张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.05
Branch: branch03

commit 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中的propeventprop默认传递给valueevent默认是input事件,针对不同组件即v-model绑定到不同类型的组件上面,如input组件时遵循此规则,如checkbox、radio、select、prop默认传递给checkedselected属性,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


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
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: `
                
{{name}} {{quantity}}
`
, 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>

Vue 0基础学习路线-对比React(7)—— 图解深度详述Vue组件使用及组件的data、组件的props和组件通信、v-model和.sync及详细案例(附详细案例代码解析过程及版本迭代过程)_第27张图片

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.06
Branch: branch03

commit description:a1.06(example07—.sync的使用)

tag:a1.06

7.2.3 深入探究

:quantity.sync="item.quantity" 意思是把"item.quantity"传递给内部propsquantity(父传子),同时内部quantity发生变化的时候,可把值同步到"item.quantity"

内部事件特殊的起名 => update:quantity(要修改的属性),quantity就是需要修改的属性。.sync代表当更新子级的quantity,发现了此修饰符触发事件,就主动修改父级的item.quantity(将this.quantity + 1赋值过去)

通过这种方式,能明显看出来是影响的内部哪个属性,内部的哪个属性更新后会同步到外部来,而v-model却明显看不出来(除非查看model属性下的propevent配置项)。

同时省略掉事件监听与事件函数。因此.sync结合了v-modelv-model两者的优点,而摒弃了它们的缺点。它能够方便子级同步更新父级的data,同时能够直观地感受到内部是什么属性,与外界什么属性进行绑定的。

所以建议使用这种方式。

8. 小结&杂谈

以上方法这些写法都是可以的,有时候觉得使用起来还不错,但是太多的api(给我们安排得明明白白的,并不完全是一件好事),因为很多时候,需要靠自己去推测(写着写着就忘了)。

Vue用起来很方便,内部细节是不知道的,都需要自己去推测。

而内部的语法糖根据js的用法去推测,又不是那么不一致,即不能根据常理去推测,内部细节不去看源码根本理不清楚。

vue虽然用起来爽,但内部封装的api实在是太多太多了,过于繁琐,有的时候写的过程中出了奇怪的问题,推测根本解决不了问题,就得爬源码。而且规则很多,也很容易掉坑了出不来,因此对于有js或者其他语言开发的底蕴,小迪还是更喜欢React,尽管没有vue用起来爽,但是坑往往被挖的很深,并且需要记忆的东西很多,必须强行记忆。

不过多于小迪刚初来乍到的vue新手,势必被割一波韭菜,但又不是真正意味的割韭菜,掉的坑多了,后面就成高手了,小迪建议学完vue基本使用,一定要往源码上深挖。



(后续待补充)

你可能感兴趣的:(#,Vue基础,vue,组件通信,.sync,v-model,props/data)