初识Vue.js03

306 阅读9分钟

讲一讲对象

什么是Vue的双向数据绑定原理?

1.Vue的双向数据绑定是通过数据劫持和"发布-订阅者"模式实现的。

2.当要获取一个对象时,要调用Object.defineProperty()中的get()方法,当检测到属性更新时,调用 Object.defineProperty()中的set()方法。

讲一讲 Object 中的 Object.defineProperty()

    // Object.defineProperty('obj(操作的对象)','prop(操作的对象属性)',{});
    /* Object.defineProperty(obj,'name',{
            value: '',  // 设置将要赋值给 prop 的值
            writable: boolean, // 设置该属性是否可以被修改
            configurable: boolean, // 设置该属性是否可以配置,即可否被删除
            enumerable: boolean, // 设置该属性是否可被枚举,即可否给循环遍历
        }) 
        */
        // 修改(删除、增加)的属性要加引号,因为属性是以字符串的形式传递进来的
        // var myObj = {};
        // myObj.name = "张三",
        
        var myObj = {};
        Object.defineProperty(myObj,'name',{
            value: "李四",
            enumerable: true,
        });
        Object.defineProperty(myObj,'age',{
            value: 22,
            enumerable: true,
        });

        var arr = [];
        // for(key in myObj){
        //     arr.push(key);
        // }

        console.log(myObj);
        console.log(arr);

        console.log(Object.keys(myObj));  // 获取某个对象中的键

getters(get)方法 和 setters(set)方法

    var obj = {};
    var str = "张三";
    Object.defineProperty(obj, "name", {
        get(){  // 读取属性
            return str;
        },
        set(val){  // set() 方法应该接受一个参数(val),用来接收修改后的值
            str = val;
        }
    })
    
    // 这个时候如果想要修改 obj.name 的值,就需要使用到 Object.defineProperty()
    中的 set() 方法
    str = "李四"
    
    console.log(obj.name);  // 张三 ---> 李四

自定义指令

自定义全局指令

  • 当我们有多个标签需要使用到同样的指令的时候,我们可以将指令提取出来,设置为全局指令,以供重复使用,就不用在每一个Vue实例中都写同一个指令。
// 书写全局指令的格式 
    Vue.directive("指令的名称",{
        // 插入一个指令数据
        inserted(el,binding,vnode){
            console.log(el);  // 真实DOM
            console.log(binding);  // 就是我们从Vue实例的data里面传递到视图上的
            变量数据
            console.log(vnode);  // 虚拟DOM
        }
    })
    <div id="app">
        <div id="app">
        <h3 v-demo="flag">你好,叩丁狼</h3>
    </div>
    <script>
        Vue.directive('demo', {
            inserted(el, binding, vnode){
                // console.log(el);  // 真实DOM
                // console.log(binding.value);  
                // 就是我们从Vue实例的data里面传递到视图上的变量数据
                // console.log(vnode); // 虚拟DOM

                if(binding.value){
                    el.style.display = "block";
                }else{
                    el.style.display = "none";
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                flag: true,
            }
        })
    </script>
    </div>
  • 但是这样子写有一点不好,就是每次都需要手动去切换 flag 的值,那么我们可以设置一个 button 按钮,通过点击 button 来控制显示和隐藏。
  • 注意:要注意一点,我们前面只写了 inserted 来插入父节点中去,却没有实时更新数据,所以我们还需要添加一个 update 方法来实时更新数据
    <div id="app">
        <div id="app">
        <h3 v-demo="flag">你好,叩丁狼</h3>
        // 给按钮添加一个点击事件,并且实时更新最新的数据,只要每次点击按钮,
        就给 flag 取反
        <button @click="flag = !flag">按钮</button>
    </div>
    <script>
        Vue.directive('demo', {
            inserted(el, binding, vnode){
                if(binding.value){
                    el.style.display = "block";
                }else{
                    el.style.display = "none";
                }
            },
            // 实时更新数据
            update(el, binding, vnode){
                if(binding.value){
                    el.style.display = "block";
                }else{
                    el.style.display = "none";
                }
            },
        })
        var vm = new Vue({
            el: "#app",
            data: {
                flag: true,
            }
        })
    </script>
    </div>

自定义局部指令

  • 局部指令其实就是在父组件 data 的同级下定义一个 directives 方法,将我们的自定义指令写在 directives 中,这种我们称之为局部指令注册。
    <div id="app">
        <h3 v-myDemo="flag">你好,叩丁狼</h3>
        <button @click="flag = !flag">按钮</button>
    </div>
    <script>
        var vm = new Vue({
            el: "#app",
            data: {
                flag: true,
            },
            directives: {
                myDemo: {
                    inserted(el, binding, vnode) {
                        // console.log(el);  // 真实DOM
                        // console.log(binding.value);  
                        // 就是我们从实例Vue的data里面传递到标签上的变量数据
                        // console.log(vnode); // 虚拟DOM

                        if (binding.value) {
                            el.style.display = "block";
                        } else {
                            el.style.display = "none";
                        }
                    },
                    // 我们在 inserted 当中只是插入了数据,而没有更新的功能
                    update(el, binding, vnode) {
                        if (binding.value) {
                            el.style.display = "block";
                        } else {
                            el.style.display = "none";
                        }
                    }
                }
            }
        })
    </script>

组件化

什么是组件化?

  • 面对复杂问题的解决方式,可以将大问题分割成为多个容易解决的小问题,逐一解决之后再重新拼接成一个整体。

    • 我们在遇到复杂的业务逻辑和页面结构的时候,如果我们将全部的代码都放在同一个页面里面,这样的代码既不整洁,也不利于我们后期的维护和迭代更新。
    • 如果我们可以将这个整体分割为多个不同的功能模块,每一个模块独立的完成不同的功能,然后再将多个功能模块拼接成一个整体,这样子的代码既整洁、易看,也有利于我们后期的维护和迭代更新
  • Vue的组件化思想?

    • 组件化是Vue的一个重要思想
    • 其实可以将我们平时所做的页面(或者看到的页面)抽象为一颗组件树,每个页面都可以分割为不同的区块,各各区块里面又可以分割为多个区块,每个区块执行的功能都不一样,最后将这个功能区块拼接成一个整体,就形成了我们一个完整的网页。

全局组件

  • 我们通过 Vue.component('组件名称',{}) 这个方法注册出来的组件都是全局组件,全局组件注册之后可以供所有Vue实例出来的的对象使用。
    <script>
        Vue.component('table_el',{
            template: '<h3>Hello World</h3>'
        })
    </script>

局部组件

  • 我们通过 Vue.component('组件名称',{}) 创建出来的组件都称之为全局组件,但是全局组件往往是不够理想了的,相信很多人有这样一个疑问,全局组件只要定义一个,所有 Vue 实例化的对象都可以使用,为什么会说不够理想呢?

    • 这是因为全局注册的组件,在打包时会将组件代码也一并打包到项目中去,这样子会平白增加项目的大小,没办法按需导入。
  • 借由全局注册的不完善性引出了 ---> 局部注册,局部注册只能在当前 Vue 实例中使用,避免了像全局组件那样被打包进项目中去,可以按需导入。

<body>
    <div id="app">
        <my-comp></my-comp>
    </div>
</body>
<template id="app2">
    <div>
        <h3>你好,我是组件</h3>
        <h4>你好,我是组件</h4>
        <button>按钮</button>
    </div>
</template>

</html>
<script>
    new Vue({
        el: "#app",
        data: {},
        // 注册局部组件
        components: {
            'my-comp': {
                template: '#app2',
            }
        }
    })
</script>

注意:全局组件和局部组件注册的时候,template 里面组件标签的根元素只能有一个,如果根元素超过一个,会报如下错误,意思大概是 组件模板应该正确的被包含在一个根元素下,如果一定要有多个标签,那么必须在最外层包裹一个标签,就不会报错

- Component template should contain exactly one root element
  • 错误书写格式如下:

  • 正确书写格式如下:

组件可以访问 Vue 实例数据吗?

  • 按正常来讲,组件是不能访问父组件中的data数据的。(后面会讲到如何去获取父组件中的data数据)
  • 回到我们之前的问题,如果直接去访问父组件中的data数据的话,会报错,提示说该属性为定义。

  • 那么如果组件是要使用data给自己定义属性并且保存数据。那么应该怎么做呢?

    • 首先组件对象中也有data属性(methods、directives等属性(后面会用到))
    • 如果要使用data属性那么它一定要是一个函数,并且返回值是一个对象,数据就存储在对象中(留给大家一个思考问题,问什么组件中的data属性一定要是一个函数呢?后面为大家解答,请大家可以思考一下)
<body>
    <div id="app">
        <my-comp></my-comp>
        <my-comp1></my-comp1>
    </div>
</body>

<template id="temp">
    <div>
        <h3>{{name}}</h3>
        <button @click="name++">加1</button>
    </div>
</template>

<template id="temp1">
    <div>
        <h3>{{name}}</h3>
        <button @click="name++">加1</button>
    </div>
</template>

</html>
<script>
    new Vue({
        el: "#app",
        data: {
            name: '李四'
        },
        // 注册组件
        // 组件中data必须是函数
        components: {
            'my-comp': {
                template: '#temp',
                data() {
                    return {
                        name: "andy"
                    }
                }
            },
            'my-comp1': {
                template: '#temp1',
                data() {
                    return {
                        name: "jack"
                    }
                }
            }
        }
    })
  • 回到之前遗留的问题,为什么组件的data属性必须是一个函数?(重点)

    • 因为如果data属性不是一个函数,那么所以的数据属性就都会指向同一个内存,只要组件A修改了数据时,组件B的数据也被修改了。
  • 现在组件是可以访问data属性了,但是还是没有解决我们最初的问题,就是组件访问 父组件中的data属性,组件现在访问的data属性是它自己的data属性。现在为大家讲解组件怎么访问父组件中的data属性。

父子组件间的通信

  • 父级向子级传递:其实父级是可以向子级传递data属性里面的数据的,只是子级没有定义一个属性来接收父级传递的数据 --- > 在子级中,我们使用 props 来声明需要从父级接收的数据

  • props 的值有两种形式:

    • 第一种是字符串数组,数组中的字符串就是传递时的名称,我们就是通过这个名称来给组件渲染视图的
        components: {
            'table_el': {
                template: '<div>{{name}} {{age}}</div>',
                props: ['name', 'age'],
            }
        }
    
    • 第二种是对象格式(这种格式也是比较复杂的),我们接收的每一个属性都是一个对象,需要给它设置数据类型(type)、以及默认值(default)

      • 可以设置的数据类型(type):( String,Number,Boolean,Array, Object,Date,Function,Symbol )
      • 默认值(default):默认值就是假设数据请求的时候网络出现卡顿、断网等突发情况,Vue 会先将默认值暂时的渲染到视图上,待数据请求回来后,再将真正的数据渲染到视图上。
        components: {
            'table_el': {
                template: '<div>{{name}} {{age}}</div>',
                props: [
                    name: {
                        type: String,
                        default: '张三',
                    },
                    age: {
                        type: Number,
                        default: 24,
                    }
                ],
            }
        }