Vue-指令与传值

991 阅读3分钟

1.数据劫持

vue的双向数据绑定原理:vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的

Object.defineProperty() ---实现数据劫持

其中有getter()和setter方法,当读取属性值时,就会触发getter()方法

在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会来触发这个函数;

示例:

<body>
    <button id="btn">修改msg</button>
    <script>
        let obj={}
        let txt="你好世界"

        Object.defineProperty(obj,"msg",{
            get(){
                //返回msg的数据
                return txt
            },
            set(val){
                txt = val
                
            }
        })
        console.log(obj.msg);

        //点击修改msg的值
        btn.onclick = function(){
            obj.msg = "hello world!" //一旦修改msg的值,就会自动触发setter,并且获取到一个新的值
            console.log(obj.msg);
        }
    </script>
</body>
</html>

2.对象属性的配置

writable:true, //设置某个key值可被修改

configurable:true, //设置某个key值可被删除

enumerable:true //设置当前对象能否枚举所有属性

示例:

let obj = {
    msg:"你好世界"
}
console.log(obj.msg);
obj.msg="hello world"
console.log(obj.msg);
//obj.属性值能获取value值,也能直接修改,删除数据

原理:

let obj = {}
Object.defineProperty(obj,"msg",{
    value:"你好世界" , //设置value值---与get(){ return "你好世界"}相同,但get,set方法同时出现
    writable:true, //设置某个key值可被修改
    configurable:true, //设置某个key值可被删除 
    enumerable:true //设置当前对象能否枚举所有属性 
})
console.log(obj.msg);
//修改value值
obj.msg = "hello world"
console.log(obj.msg);  
*  //删除
delete obj.msg
console.log(obj.msg);  //undefined */
//枚举(Object.keys(),for in)一个对象的属性
console.log(Object.keys(obj));  //['msg']

3.自定义指令

Vue.directive("自定义属性",{})

函数:

inserted :被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

bind : 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

update:更新

函数对应的参数:

el:指令所绑定的元素,可以用来直接操作 DOM 。

binding:当前元素的属性

3.1全局自定义属性

示例:

<!DOCTYPE html>
<html lang="zh_cn">

<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></title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background-color: antiquewhite;
        }
    </style>
</head>

<body>
    <div id="app">
        <button id="btn" @click="flag=!flag">切换</button>
        <div class="box" v-myshow="flag"></div>
    </div>
</body>

</html>
<script src="./lib/vue.js"></script>
<script>
    //全局自定义属性
    Vue.directive("myshow",{
        inserted(el,binding){
            // el:当前元素
            // binding:当前元素的属性
            console.log(el,binding);
            /* if(binding.value){
                el.style.display = "block"
            }else{
                el.style.display = "none"
            } */
            el.style.display = binding.value ? "block" : "none"
        },
        //更新
        update(el,binding){
            el.style.display = binding.value ? "block" : "none"
        }
    })
    new Vue({
        el: "#app",
        data: {
            flag: false
        }
    })
</script>

3.2局部自定义指令

示例:

new Vue({
    el: "#app",
    data: {
        flag: false
    },
    directives: {
        myshow: {
            inserted(el, binding) {
                el.style.display = binding.value ? "block" : "none"
            },
            //更新
            update(el, binding) {
                el.style.display = binding.value ? "block" : "none"
            }
        }
    }
})

3.3 案例-自动聚焦

<!DOCTYPE html>
<html lang="zh_cn">
<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></title>
    <style>
        /*修改自动聚焦时,聚焦的外边框*/
        input:focus{
            outline: 3px solid pink;
        }
    </style>
</head>
<body>
<div id="app">
    <input type="text" v-focus>
</div>
</body>
</html>
<script src="./lib/vue.js"></script>
<script>
new Vue({
    el: "#app",
    data: {
    
    },
    directives:{
        focus:{
            inserted(el){
                //focus()用于为元素设置焦点
                el.focus()
            }
        }
    }
})
</script>

4.组件

4.1全局组件

component:组件 template:模板

示例:

<body>
    <div id="app">
        <my-list></my-list>
        <my-list />
    </div>
</body>

</html>
<script src="./lib/vue.js"></script>
<script>
    //全局组件
    Vue.component('my-list',{
        template:`<ul>
                        <li>
                            <span>百度一下你就知道</span>
                            <a href="http://baidu.com">链接</a>
                        </li>
                    </ul>`
    })
    new Vue({
        el: "#app",
        data: {

        }
    })
</script>

4.1.1模板抽离

写在()标签中--全局任意地方

示例:

<body>
    <div id="app">
        <my-list></my-list>
        <my-list />
    </div>
</body>

<template id="list">
    <ul>
        <li>
            <span>百度一下你就知道</span>
            <a href="http://baidu.com">链接</a>
        </li>
    </ul>
</template>

</html>
<script src="./lib/vue.js"></script>
<script>
    Vue.component('my-list', {
        template: "#list"
    })
    new Vue({
        el: "#app",
        data: {

        }
    })
</script>

4.1.2组件无法调用实例中的data

示例:

<body>
    <div id="app">
        <my-list></my-list>
        <my-list />
    </div>
</body>

<template id="list">
    <ul>
        <li>
            <span>{{msg}}</span>
            <a href="http://baidu.com">链接</a>
        </li>
    </ul>
</template>

</html>
<script src="./lib/vue.js"></script>
<script>
    Vue.component('my-list', {
        template: "#list"
    })
    new Vue({
        el: "#app",
        data: {
            msg:"你好"
        }
    }) 
    //报错--"msg" is not defined
</script>

解决方法

组件中data必须是函数

多次调用同一个组件,我们就需要对这个组件的数据进行维护,相当于我们希望给这个组件的数据套上一个作用域,防止被外界的数据影响

ES5中只有函数才有作用域

<body>
    <div id="app">
        <my-list></my-list>
        <my-list />
    </div>
</body>

<template id="list">
    <ul>
        <li>
            <span>{{msg}}</span>
            <a href="http://baidu.com">链接</a>
        </li>
    </ul>
</template>

</html>
<script src="./lib/vue.js"></script>
<script>
    Vue.component('my-list', {
        template: "#list",
        data() { //解决方法:组件中data必须是函数
            return {
                msg: "你好"
            }
        }
    })
    new Vue({
        el: "#app",
        data:{

        }
    })

</script>

4.1.3组件中的方法

<body>
    <div id="app">
        <my-list></my-list>
        <my-list />
    </div>
</body>

<template id="list">
    <ul>
        <li>
            <span>{{msg}}</span>
            <a href="http://baidu.com">链接</a>
            <button @click="btnClick">按钮</button>
        </li>
    </ul>
</template>

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

    Vue.component('my-list', {
        template: "#list",
        data() { 
            return {
                msg: "你好"
            }
        },
        methods:{
            btnClick(){
                console.log(123);
            }
        }
    })
    new Vue({
        el: "#app",
        data:{

        }
    })

</script>

4.2局部组件

 new Vue({
     el: "#app",
     data: {
     },
     components: {
         'my-list': {
             template: "#list",
             data() {
                 return {
                     msg: "你好"
                 }
             },
             
         }
     }
 })

5.父子组件间的通讯

5.1父传子

在组件中,使用选项props来声明需要从父级接收到的数据。

props的值有两种方式:

  1. 字符串数组,数组中的字符串就是传递时的名称。
  2. 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。

示例:

<!DOCTYPE html>
<html lang="zh_cn">

<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></title>
</head>

<body>
    <div id="app">
        <!-- 双标签 -->
        <my-list :con="content" :myherf="link"></my-list>
        <!-- 单标签-- -->
        <my-list />
        
    </div>
</body>
    
<template id="list">
    <ul>
        <li>
            <span>{{msg}}</span>
            <a :href="myherf">{{con}}</a>
            <button @click="btnClick">按钮</button>
        </li>
    </ul>
</template>

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

    new Vue({
        el: "#app",
        data: {
            content:'跳转到百度',
            link:"http://baidu.com"
        },
       
        components: {
            'my-list': {
                //props以数组的形式接收时,是简写的形式 ---单标签无法接收
                // props:["con","myherf"],
                props:{
                    con:{
                        type:String,
                        default:"跳转到新浪"
                    },
                    myherf:{
                        type:String,
                        default:"http://sina.com.cn"
                    }
                },
                template: "#list",
                data() {
                    return {
                        msg: "你好"
                    }
                },
                methods: {
                    btnClick() {
                        this.msg="呦呵"
                    }
                }
            }
        }
    })

</script>

5.2子传父

父组件向子组件传递数据,通过自定义事件

示例:

<!DOCTYPE html>
<html lang="zh_cn">

<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></title>
</head>

<body>
    <div id="app">
        <!-- 双标签 -->
        <my-list :con="content" :myherf="link" v-on:changecontent="changeContent" @changelink="changeLink"></my-list>


    </div>
</body>

<template id="list">
    <ul>
        <li>
            <span>{{msg}}</span>
            <a :href="myherf">{{con}}</a>
            <button @click="btnClick">按钮</button>
        </li>
    </ul>
</template>

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

    new Vue({
        el: "#app",
        data: {
            content: '跳转到百度',
            link: "http://baidu.com"
        },
        methods: {
            changeContent(val) {
                this.content = val
            },
            changeLink(val){
                this.link = val
            }
        },
        components: {
            'my-list': {
                props: ["con", "myherf"],

                template: "#list",
                data() {
                    return {
                        msg: "你好"
                    }
                },
                methods: {
                    btnClick() {
                        // 把myherf和con的值改了
                        /* this.con="跳转到叩丁狼"
                        this.myherf = "http://wolfcode.cn" //错误 */
                        //虽然修改成功,但是vue中对数据的维护要求非常严格,原始数组在哪个组件中,就必须在那一个组件里修改

                        //子组件中不能直接修改props,因为vue是单向数据流

                        //解决方法
                        //this.$emit(事件名称,传递的参数)  --事件名称不能使用驼峰命名法

                        this.$emit("changecontent", "跳转到叩丁狼")
                        this.$emit("changelink", "http://wolfcode.cn")
                    }
                }
            }
        }
    })

</script>

5.3组件对象的抽离

<!DOCTYPE html>
<html lang="zh_cn">

<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></title>
</head>

<body>
    <div id="app">
        <!-- 双标签 -->
        <my-list :con="content" :myherf="link" v-on:changecontent="changeContent" @changelink="changeLink"></my-list>


    </div>
</body>

<template id="list">
    <ul>
        <li>
            <span>{{msg}}</span>
            <a :href="myherf">{{con}}</a>
            <button @click="btnClick">按钮</button>
        </li>
    </ul>
</template>

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

    var MyList = {
                props: ["con", "myherf"],

                template: "#list",
                data() {
                    return {
                        msg: "你好"
                    }
                },
                methods: {
                    btnClick() {                      
                        this.$emit("changecontent", "跳转到叩丁狼")
                        this.$emit("changelink", "http://wolfcode.cn")
                    }
                }
            }
    new Vue({
        el: "#app",
        data: {
            content: '跳转到百度',
            link: "http://baidu.com"
        },
        methods: {
            changeContent(val) {
                this.content = val
            },
            changeLink(val){
                this.link = val
            }
        },
        components: {
            // 'my-list': MyList
            MyList:{}
        }
    })

</script>

5.4案例

点击登录,弹出登录框

点击 X或者其他灰色界面,登录框消失

<!DOCTYPE html>
<html lang="zh_cn">

<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></title>
    <style>
        * {
            margin: 0;
            padding: 0;
            list-style: none;
        }

        .model {
            width: 100%;
            height: 100%;
            /* background-color: rgba(0, 0, 0, .3); */
            position: fixed;
            top: 0;
            left: 0;
        }

        .content {
            width: 600px;
            height: 400px;
            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            margin: auto;
            background-color: #fff;
        }
        .bg{
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, .3);
        }
        .del {
            /* float: right; */
            width: 30px;
            height: 30px;
            cursor: pointer;
            box-sizing: border-box;
            font-size: 20px;
            color: #999;
            /* border: 1px solid #ccc; */
            display: flex;
            justify-content: center;
            align-items: center;
            position: absolute;
            right: 0;
        }
    </style>
</head>

<body>
    <div id="app">
        <button @click="flag=true">登录</button>
        <my-model :flag="flag" @closemodel="flag=false"></my-model>
    </div>
</body>
<template id="model">
    <div class="model" v-show='flag'>
        <div class="bg" @click="closeModel"><6/div>
        <div class="content">
            <div class="del" id="del" @click="closeModel">X</div>
        </div>
    </div>
</template>

</html>
<script src="./lib/vue.js"></script>
<script>
    new Vue({
        el: "#app",
        data: {
            flag: false
        },
        //component:组件  template:模板
        components: {
            MyModel: {
                template: "#model",
                props: ['flag'],
                methods: {
                    closeModel() {
                        // 让父组件关闭静态框
                        this.$emit("closemodel")
                    }
                }
            },

        },
        methods: {}
    })
</script>

6.插槽

slot --封装组件

抽取共性,保留不同

  1. 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
  2. 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
  3. 是搜索框,还是文字,还是菜单。由调用者自己来决定。

6.1匿名插槽

<!DOCTYPE html>
<html lang="zh_cn">
<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></title>
    <style>
        .nav{
            width: 400px;
            height: 40px;
            background-color: aquamarine;
            display: flex;
            justify-content: space-between;
            line-height: 40px;
        }
        section{
            flex: 1;
            background-color: pink;
            text-align: center;
            
        }
    </style>
</head>
<body>
<div id="app">
    <my-nav>123123</my-nav>
</div>
</body>
</html>
<template id="mynav">
    <div class="nav">
        <span>&lt;</span>
        <section>
            <slot></slot>  <!--根据自己的需求,决定插槽中插入什么内容-->
        </section>
        <span>...</span>
    </div>
</template>
<script src="./lib/vue.js"></script>
<script>
new Vue({
    el: "#app",
    data: {
    
    },
    components:{
        MyNav:{
            template:"#mynav"
        }
    }
})
</script>

6.2具名插槽

想让谁显示出来,修改后面slot的name属性

<body>
    <div id="app">
        <my-nav :slotname="slotname">
            <input type="text" slot='ipt'>
            <h3 slot="title">标题</h3>
            <div slot="list">
                <a href="">百度</a>
                <a href="">新浪</a>
                <a href="">微博</a>
            </div>
        </my-nav>
    </div>
</body>

</html>
<template id="mynav">
    <div class="nav">
        <span>&lt;</span>
        <section>
            <slot :name="slotname"></slot> 
        </section>
        <span>...</span>
    </div>
</template>
<script src="./lib/vue.js"></script>
<script>
   let vm = new Vue({
        el: "#app",
        data: {
            slotname:'ipt'
        },
        components: {
            MyNav: {
                template: "#mynav",
                props:["slotname"]
            }
        }
    })
</script>

6.3作用域插槽

默认情况下,父组件使用子组件,插槽数据默认是拿父组件的数据,而不是子组件拿数据。

作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。

008i3skNgy1gvaeno23krj60zk0lcafg02.jpg

6.3.1作用域插槽的多种写法

	<div id="app">
        <my-nav>
           <!-- 方法一 -->
            <!-- <h3 slot="title" slot-scope="scope">{{scope.msg}}</h3> -->

            <!-- 方法二 -->
            <!-- <template slot="title" slot-scope="scope">
                <h3>{{scope.msg}}</h3> -->
            </template>
            
            <!-- 方法三 -->
            <template v-slot:title="scope">
                <h3>{{scope.msg}}</h3>
            </template>

        </my-nav>
    </div>