1-Vue及组件入门类比React-附实例代码(20.8.10)

1. 重点提炼

  • 组件系统

    • 根组件
    • new Vue()
    • 可复用组件
  • 用户界面(UI)

  • 组件选项

    • template
    • el
  • 延迟挂载

    • $mount()
  • UI

    • data 选项
    • 模板语法
    • 大胡子 {{表达式}} - 插值表达式

2. vue.js

实际VueReact都十分相似,都是用来构建界面的,即构建UI的。

vue.js 是一套用于构建用户界面(可理解为模板引擎)的渐进式框架(vuereact共同点都是用于构建用户界面,即构建ui,但react其实也是渐进式框架)

2.1 渐进式(诱导式)

刚开始很简单好用,用起来很方便,越往深里坑越多!

第一个圆是声明式渲染,可理解为模板引擎。但用着会感觉光用这个还不够,因为界面(UI)复用性是非常高的,除了UI以外,还包括用户的响应。

所以就发现需要另外一个东西解决这个问题,随着而来就接触了组件系统(第二个圆圈),用组件系统就可以利用声明式UI创建出来的页面达到更大化的复用,继而更方便去管理。

需求越来越复杂后,页面越来越多,多页面之间如何进行切换呢?即接触到(第三个圆圈客户端路由(系统)了,这样就能实现多页面跳转式的app了。

当然做完这些还不够,不可能总是在页面上写很多死的东西(数据),通常情况下需要操作各自各样动态变化的数据来更新我们写的ui,与用户进行交互,随着数据的越来越庞大,以及应用越来越复杂,所以即需要更好得一种方式去管理和维护这些数据,即接触到(第四个圆圈可伸缩式的状态管理器了。

做完以上功能,会发现项目非常庞大了,此时不可能用原生的方式来构建我们写的应用,随之而来的是需要我们创建和使用一系列的各种各样的工具,比如解析、打包、优化、发布,帮助我们做很多与应用逻辑无关的运维方面的事情,即(第五个圆圈自动化构建系统

其实在开发过程中,会发现还会用到很多很多其他的系统或组件等,在这个应用基础上进行包装,这样进行一层一层的扩展,慢慢地应用就做出来了,因此这就是渐进式(诱导式)框架的概念。

2.2 Vue 核心

  • 声明式(UI)渲染(也可理解为模板引擎
  • (可复用)组件(系统):会使声明式UI得到更大化地复用,更方便地管理

3. 引入

我们还是先通过 <script> 的方式来引入 vue

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

其实Vue核心本质上就两个内容:

1、声明式渲染 2、组件 => 并且它俩也不可分割。

我们看一下例子声明式渲染:

React是构建组件化,通过组件来包装可复用的ui,然后在ui内部,通过声明式渲染的语法,如在React当中使用JSX语法来声明我们在页面当中想看到的页面内容(ui的结构及样式)。那么vue也是一样的,也是基于组件,在组件当中通过一种模板语法的东西来实现对当前UI的构建。

ReactDom下有一个render方法,它把封装好的组件挂载到指定的页面DOM当中,然后进行渲染。组件内部是通过JSX语法来实现UI的绘制的,即UI的结构定制的。

实际在Vue中也是一样的概念,在Vue中组件划分为根组件和可复用的功能组件。

4. 组件

vue 的核心功能之一就是组件

components

4.1 组件基本分类

  • 根组件(页面当中最顶层封装的组件)
  • 可复用的功能组件(在页面当中可以多次利用的组件)

4.2 根组件的创建

通过 vue 提供的构造函数可以实例化出来一个根组件实例对象,Vue中接收一个对象,对象中的属性是用来构建组件的一些配置选项。

组件最为重要的就是组件渲染的结构,在React当中,如何定义这个结构呢?函数组件,即其返回值,而类组件,则是render方法的返回值。在Vue中也提供了一种方式存放组件渲染的结构,即UI => 通过template属性。Vue挂载页面的方式 => vue对象下的$mount方法,传入指定元素,实际传一个选择器进去即可。

let app = new Vue(创建组件所需要的一些配置选项);

4.2.1 example01

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
</div>

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


    /**
     * 组件
     * 声明式渲染语法
     *
     */

    // Vue实例(代表一个组件)
    let app = new Vue({
        template: `
            <div>
                CSDN
            </div>
        `
    });

    console.log(app);

    app.$mount('#app');

</script>

</body>
</html>

image-20200727234344757

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

commit description:a0.01(example01-vue初始化)

tag:a0.01

4.3 可复用的功能组件

通过 Vue 提供的静态方法 component 窗口可复用的功能组件

let component1 = Vue.component(创建组件所需要的一些配置选项)

组件配置选项:https://cn.vuejs.org/v2/api/

5. 组件内容渲染

渲染一个组件的内容可以通过两种方式来进行

  • template 选项
  • render 选项(函数)

5.1 template

type : string

组件的模板结构(HTML),模板内容会被 vue 进行渲染,生成最终的 HTML 内容,并替换占位(挂载)元素

5.2 el

type : string | Element

提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement实例,

  • 该选项只对 new 创建的实例有效
  • 如果提供 el ,但是没有提供 template ,则 el 的内容讲作为 template

5.2.1 example02

vue 提供了很多简化式写法 =>

有人喜欢react,喜欢的是它的直接(它没做过多的东西,即封装更多地东西,和学js没太多区别,除了jsx以外,其实几乎与原生一致,所以如果js掌握非常好的话,学习React很容易上手,没有太多关注的新东西)。

有人喜欢Vue是喜欢它的语法糖,隐藏了很多的细节(封装了很多便捷性操作,操作非常方便,用起来很舒服,即可能一句话做了很多事情,所以又很多人不喜欢它这种过于委婉,它隐藏了很多细节,封装了很多api,不去看手册和文档,是不知道怎么用的,完全不是js了)

5.2.1.1 example02-1

除此之外还可这样挂载:

内部有el选项,就会立即进行挂载,实际就是在实例化的时候,Vue发现有el选项,就去直接调用$mount方法了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
</div>

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


    /**
     * 组件
     * 声明式渲染语法
     *
     *
     * react
     *      直接
     * vue
     *      方便 - 语法糖,隐藏了很多的细节
     */


    let app = new Vue({
            el: '#app',
            template: `
                <div>
                    11111
                </div>
            `
        });

</script>

</body>
</html>

显示效果一样。

image-20200728000415319

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

commit description:a0.02(example02-vue的el属性demo)

tag:a0.02

5.2.1.2 example02-2
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    CSDN
</div>

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


    /**
     * 组件
     * 声明式渲染语法
     *
     *
     * react
     *      直接
     * vue
     *      方便 - 语法糖,隐藏了很多的细节
     */


    let app = new Vue({
        el: '#app'
    });


</script>

</body>
</html>

效果还是一样,但本质还是有区别的,如果实例化的时候发现vue对象里有el属性,而没有template属性,它会把el属性对应的元素的html作为template属性值,即:

如果配置中有el没有template,它会把el里面这个选项的元素对应的标签整个显示在页面当中!替换如下:

<div id="app">
    CSDN
</div>

image-20200727234344757

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

commit description:a0.03(example02-2-vue的el属性demo)

tag:a0.03

5.2.1.3 example02-3

我们看看,是template的优先级高?还是html的优先级高?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    CSDN
</div>

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


    /**
     * 组件
     * 声明式渲染语法
     *
     *
     * react
     *      直接
     * vue
     *      方便 - 语法糖,隐藏了很多的细节
     */

    let app = new Vue({
        el: '#app',
        template: `
            <div>
                11111
            </div>
        `
    });


</script>

</body>
</html>

我们会发现templete的优先级会更高一些。

image-20200728000415319

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

commit description:a0.04(example02-3-vue的template属性和html的优先级)

tag:a0.04

5.2.1.4 example02-4

el:千万注意不能把 bodyhtml 元素作为挂载点

    let app = new Vue({
        el: 'body',
        template: `
            <div>
                11111
            </div>
        `
    });

这种情形下Vue没帮助我们进行渲染,报错也提示了不能把htmlbody作为应用的挂载点。

原因在于我们的这里模板最终实现的是替换功能。

image-20200621113131813

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

commit description:a0.05(example02-4-el:千万注意不能把 body 和 html 元素作为挂载点)

tag:a0.05

5.2.1.5 example02-5

template模板最终实现的是替换功能

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    CSDN
</div>

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


    /**
     * 组件
     * 声明式渲染语法
     *
     *
     * react
     *      直接
     * vue
     *      方便 - 语法糖,隐藏了很多的细节
     */

    let app = new Vue({
        el: '#app',
        template: `
            <div z="1">
                11111
            </div>
        `
    });


</script>

</body>
</html>

其实可以看出来,它会把解析后的模板,完全替换掉当前的元素,而不是元素里面的内容。如果这个时候把body作为挂载点,必然会出问题。

image-20200621113548742

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

commit description:a0.06(example02-5-template模板最终实现的是替换功能)

tag:a0.06

5.2.1.6 example02-6

template:注意每一个组件的根元素(节点)有且只能有一个

    let app = new Vue({
        el: '#app',
        template: `<div>111111</div><div>vue</div>`
    });

当然报错也提示了,每一个组件的根元素(节点)有且只能有一个,后面说明的v-ifv-else-if用来生成的单一节点是排除在外的。

image-20200621114107069

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

commit description:a0.07(example02-6-template:注意每一个组件的根元素(节点)有且只能有一个)

tag:a0.07

除此之外,vue也提供了另外一种渲染方式即render函数,它可以直接渲染虚拟dom,它有两个作用,我们往下走。

5.3 render

vue渲染过程:

html模板 => (解析成)VDOM(虚拟DOM对象) => DOM元素

这里为啥不直接把模板直接转换成DOM元素呢?

这里的原因和react是一样的,目的是我们后续进行dom更新的过程中,可进行(DOM)优化。

其实vue还提供另外一种渲染方式,和React很像,即render函数。

可直接通过该函数,对虚拟dom直接进行渲染,它的作用(好处)有两个。它相较于template的好处是,它提供一个createElement方法,通过该方法可直接传 VNode (虚拟dom对象),直接创建真实dom节点(其实就是把html模板这个步骤省略掉了),优先级高于 eltemplate,因此它有更高的性能。

html模板当中,它还限定了一些语法,即不能直接使用js进行编程,不能在里面写逻辑,它没有提供这个功能。但是它提供了很多指令,必然它也会有一些的局限性。

因此如果使用render方法,它是一个js函数,在其内部可以无所顾忌的使用js当中的各种指令,所有它有更强大的编程能力(相对于html模板)。

type : (createElement: () => VNode) => VNode

发挥 JavaScript 最大的编程能力,直接创建 VNode (虚拟dom对象),优先级高于 eltemplate,好处如下:

  • 有更强大的编程能力
  • 有更高的性能

https://cn.vuejs.org/v2/guide/render-function.html

5.4 使用 $mount 方法延迟 Vue 实例的挂载

Vue 实例没有 el 选项的时候,它会处于一种 未挂载 的状态,我们可以通过组件 Vue 实例对象的 $mount 方法来手动挂载,通过该方式,我们也可以达到延迟 Vue 实例的挂载的目的

6. 组件中的数据(状态)

我们知道光有一个模板,是不够的,大多数模板引擎都能做到,我们前端除了渲染页面,还需要操作大量的数据,有后端发来的数据,也有自己产生的数据,由一堆丰富的数据构建出了各种各样丰富的app。死的app是没有任何意义的,能够交互的app才是十分重要的。

6.1 data

组件内部使用的数据,data 是一个对象,data 中的值可以中模板中直接访问

  • Vue 实例组件(根组件)的 data 是一个对象
  • 可复用功能组件的 data 必须是一个函数,且该函数必须返回一个对象。因为复用性,避免多个组件实例引用同一个对象。换句话说,组件中使用的数据必须是一个对象,但是可复用组件的这个数据对象必须通过函数返回

6.2 data 的访问

肯定是在应用相关的UI界面中进行访问,跟React一样,要么在函数内部或类的其他方法去调用数据,然后写在render函数的jsx中,即可显示在界面上了。

data 数据可以直接通过组件实例对象访问,也可以通过实例对象下的 $data 属性进行访问

组件实例对象下有很多的以 $ 开头的属性,这些都是实例对象内置的一些属性和方法,vue 为了区分数据与内置属性方法,内置的属性和方法默认都是以 $ 开始的,所以我们中数据中应该避免使用 $ 开头的数据

7. 模板语法

vue 使用了基于 html 的模板语法,使用声明式的方式把实例中的数据(data)与 DOM 进行绑定,data 中的数据在模板中可以直接使用

7.1 Mustache(双大括号,大胡子) 语法

vue 中,我们是通过一对双大括号把实例中的数据渲染到模板内容中

7.1.1 插值表达式

{{}}JSX是一对大括号,这是两对大括号,可理解jsx语法是小胡子,这里是大胡子)中,我们可以放置表达式值

{{表达式}}
new Vue({
  el: '#app',
  data: {
    title: 'vue 框架'
  },
  template: `<div>{{title}}</div>`
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
 
    <div id="app">
      {{title}}
      </div>
 
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 
    <script>
        let app = new Vue({
            el: '#app',
            data: {
              title: 'vue 框架'
            }
        });
    </script>
</body>
</html>

7.1.2 example03

7.1.2.1 example03-1
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<div id="app">
<!--  {{表达式}}自动绑定到vue实例当中  -->
    {{title}}
</div>
 
<script src="./js/vue.js"></script>
<script>
 
 
 
    let app = new Vue({
        el: '#app',
        data: {
            title: 'GitHub',
        }
    });
 
 
</script>
 
</body>
</html>

image-20200621122241872

我们看一下vue对象:

console.log(app);

image-20200621123739673

data在内部(循环遍历)会转化成__data

image-20200621123750594

同时看到以下标记,可以得出它是被代理(拦截)了。(和原生defineProperty类似)

image-20200621124401152

其次在vue实例下方添加title属性

image-20200621124312487

同时还有一个$data

image-20200621124716384

因此根据以上,我们知道了可以通过很多方式访问这个数据 =>

app.title
app.$data.title
...

我们仔细观察这些vue属性,会发现规律,要不就是$开头,要不就是下划线__开头。

我们在data属性配置的数据会在vue对象中转成对应属性。

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

commit description:a0.08(example03-1-组件中data的访问)

tag:a0.08

7.1.2.2 example03-2
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<div id="app">
<!--  {{表达式}}自动绑定到vue实例当中  -->
    {{_title}}
</div>
 
<script src="./js/vue.js"></script>
<script>
 
 
 
    let app = new Vue({
        el: '#app',
        data: {
            _title: 'GitHub'
        }
    });
 
    /**
     * 注意
     *  在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称
     */
    console.log(app);
 
</script>
 
</body>
</html>

注意:在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称,因为它防止我们定义的属性把其默认属性覆盖了。

image-20200621125456636

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

commit description:a0.09(example03-2-组件中data的访问的注意事项)

tag:a0.09

7.2 数据到视图的更新

vue 会把 data 中的数据挂载到实例属性下,同时对它们进行主动的监听拦截,当数据发生变化的时候,重新渲染模板。我们可以通过实例对象对数据进行修改

app.title = '1111';
// or
app.$data.title = '1111';

7.2.1 demo

我们运行上例后修改title,发现页面也重新渲染了,这就是响应式。

因为在vue当中拦截了数据,对数据进行了defineProperty处理,所以当数据发生改变,页面会重新渲染整个组件中与数据关联的部分,这就数据到视图的更新

7.3 检测变化的注意事项

vue3 之前,数据的监听是通过 Object.defineProperty 方法(它有很多坑)来实现的,但是该方法只能监听拦截单个数据,对于对象新增属性无法监听拦截。所以,对于数据对象中新增的属性,我们需要调用 vue 提供的方法来进行处理。

7.4 扩展

通过 Object.defineProperty 监听拦截中存在一些问题

  • 属性新增属性
  • 数组方法:push、pop、shift、unshift、splice、sort、reverse
  • 数组新增值:[]
  • 数组 length 属性

以上的操作中并不会触发监听拦截

vue 对数组中的 pushpop 等方法进行重新包装,所以在 vue 中调用这些方法,可以对数组的修改进行监听拦截

[https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E5%BC%82%E6%96%B9%E6%B3%95-mutation-method](

7.4.1 属性新增属性

它是对对象属性的拦截而不是对对象的拦截,所以如果是已经被拦截过的属性,它的变化会使视图更新的,而对于没有拦截新增的属性它是不能使视图更新的。

7.4.1.1 example04
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<div id="app">
<!--  {{表达式}}自动绑定到vue实例当中  -->
    {{title}}
    <hr>
    <p>用户名:{{user.username}}</p>
    <p>年龄:{{user.age}}</p>
    <hr>
</div>
 
<script src="./js/vue.js"></script>
<script>
 
 
 
    let app = new Vue({
        el: '#app',
        data: {
            title: 'GitHub',
            user: {
                username: '6xiaodi'
            }
        }
    });
 
    /**
     * 注意
     *  在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称
     */
    console.log(app);
 
 
</script>
 
</body>
</html>

image-20200621135157385

我们可以发现,模板里写了{{user.age}},刚开始没有该属性,后面我们新增了这个属性,为啥显示不出来呢?这就是Object.defineProperty带来的一个问题。

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

commit description:a0.10(example04-属性新增属性带来隐患)

tag:a0.10

7.4.1.1.1 深入原理

研究一下原因:研究原生代码——扩展-defineProperty

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
 
<script>
 
    let data = {
        username: '小明',
        gender: '男'
    };
 
    Object.defineProperty( data, 'username', {
        get() {
 
        },
        set() {
            console.log('被修改了,开始更新视图');
        }
    } );
</script>
 
</body>
</html>

我们修改username属性,很容易理解它进入set方法,如果是vue,则进入视图更新逻辑。

image-20200621140140292

如果新增一个没经defineProperty处理的属性,是没有意义的,不会调用set方法,因此后续我们通过.操作新增的属性,是没有经过defineProperty拦截的,因此vue是不会进入视图更新逻辑的。

image-20200621140531092

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

commit description:a0.11(属性新增属性带来隐患——深入原理-演示属性新增)

tag:a0.11

7.4.1.1.1.1 封装

那怎么办呢?希望后续增加的属性,希望也可有这种效果。因此这就需要封装了。

    let data = {
        username: '小明',
        gender: '男'
    };
 
    for (let k in data) {
        if (data.hasOwnProperty(k)) {
            dp(data, k);
        }
    }
 
 
    function dp(obj, key) {
        Object.defineProperty( obj, key, {
            get() {
 
            },
            set() {
                console.log('被修改了,开始更新视图');
            }
        } );
    }

这样一上来就可默认将所有属性经过defineProperty处理了。

image-20200621141053654

但是对于新增的属性还是没有效果。

image-20200621141325930

我们调用dp方法才可以,之后就可以触发了。

image-20200621141745355

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

commit description:a0.12(属性新增属性带来隐患——深入原理-演示封装属性新增)

tag:a0.12

7.4.1.1.2 vue.set

vue中提供了一种静态方法,我们往下看!

Vue.setapp.$set,它俩是等同的,没有任何区别。

image-20200621142229998

这个时候同步修改age,视图也会更新。

image-20200621142700386

7.5 其余扩展

以下的概念和属性新增属性的情况是一样的,因为数组方法内部和新增值方法、数组length 都是属于后续新增的数据,数组的第n个这个索引其实就是对象的key,它一上来只会对数组已有的数据进行处理。

因此数组本质上也是改变不了的,那怎么解决呢?

但在vue里看起来数组貌似没问题,我们来看看:

  • 数组方法:push、pop、shift、unshift、splice、sort、reverse
  • 数组新增值:[]
  • 数组 length 属性

以上的操作中并不会触发监听拦截

7.5.1 example05

7.5.1.1 example05-1
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<div id="app">
<!--  {{表达式}}自动绑定到vue实例当中  -->
    {{title}}
    <hr>
    <p>用户名:{{user.username}}</p>
    <p>年龄:{{user.age}}</p>
    <hr>
    {{arr}}
</div>
 
<script src="./js/vue.js"></script>
<script>
 
 
 
    let app = new Vue({
        el: '#app',
        data: {
            title: 'GitHub',
            user: {
                username: '6xiaodi'
            },
            arr: ['a', 'b']
        }
    });
 
    /**
     * 注意
     *  在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称
     */
    console.log(app);
 
 
</script>
 
</body>
</html>

我们发现了很奇怪的问题,修改数组中已存在的数据,页面不刷新;

而我们新增(push)数据,却刷新了页面,并且把之前修改数组中已存在的数据,也更新进去了。

这是为什么呢?

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

commit description:a0.13(example05-1——属性新增属性-数组)

tag:a0.13

7.5.1.2 example05-2

我们去看看原生js的代码。

  let data = {
        username: '小明',
        gender: '男'
    };
 
    let arr = ['a', 'b'];
 
    for (let k in data) {
        if (data.hasOwnProperty(k)) {
            dp(data, k);
        }
    }
 
    for (let k in arr) {
        if (arr.hasOwnProperty(k)) {
            dp(arr, k);
        }
    }
 
    function dp(obj, key) {
        Object.defineProperty( obj, key, {
            get() {
 
            },
            set() {
                console.log('被修改了,开始更新视图');
            }
        } );
    }

数组和对象属性的原生操作没啥区别!就算我们调用push(新增数组),也不会触发set方法的。

vue是如何实现的呢?

image-20200621144441946

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

commit description:a0.14(example05-2——属性新增属性-数组-对比原生)

tag:a0.14

7.5.1.3 example05-3

简单原理实现:重写数组push方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
 
<script>
 
    let data = {
        username: '小明',
        gender: '男'
    };
 
    let arr = ['a', 'b'];
 
    Array.prototype.push = function(val) {
        let k = this.length;
        dp(this, k);
        arr[k] = val;
    };
 
    for (let k in data) {
        if (data.hasOwnProperty(k)) {
            dp(data, k);
        }
    }
 
    for (let k in arr) {
        if (arr.hasOwnProperty(k)) {
            dp(arr, k);
        }
    }
 
    function dp(obj, key) {
        Object.defineProperty( obj, key, {
            get() {
 
            },
            set() {
                console.log('被修改了,开始更新视图');
            }
        } );
    }
 
</script>
 
</body>
</html>

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

commit description:a0.15(example05-3——属性新增属性-js原理实现)

tag:a0.15

image-20200621145231635

vue 对数组中的 pushpop 等方法进行重新包装,所以在 vue 中调用这些方法,可以对数组的修改进行监听拦截

https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E5%BC%82%E6%96%B9%E6%B3%95-mutation-method

7.5.2 小结

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可以使用 splice

vm.items.splice(newLength)

7.6 使用 Vue.set 方法添加新数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
 
    <div id="app">
        <p>title: {{title}}</p>
        <p>user.username: {{user.username}}</p>
        <p>user.gender: {{user.gender}}</p>
    </div>
 
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                title: 'vue 框架',
                user: {
                    username: 'xiaomi'
                }
            }
        });
    </script>
</body>
</html>

在模板中,我们使用了一个不存在的数据 user.gender

如果我们通过 app.user.gender = '男'的方式来新增,是不会被 vue 拦截监听处理的,我们需要使用

// 这两方法是等同的,没有任何区别
Vue.set(app.user, 'gender', '男');
// 实例.$set 是 Vue.set 的别名
app.$set(app.user, 'gender', '男');

这样的方式,set 方法给 app.user 添加 gender 属性的同时,对它进行了 defineProperty



(后续待补充)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值