Bootstrap

Vue 学习 第三篇 定义组件 + 组件之间的通信

一、 组件

  html: 组件就是一段可以被复用的结构代码

  css: 组件就是一段可以被复用的样式代码

  js: 组件就是一段可以被复用的功能代码

  vue: 组件是一个包含独立的结构,样式和脚本的代码。我们可以复用。

使用组件分成三步

  第一步 在模板中使用组件

    组件名称字母小写,横线分割单词。注意:首字母不区分大小写。

  第二步 在脚本中,定义组件类:Vue.extend({})

    参数对象跟new Vue时候传递的参数对象是一样的 。

    例如:可以传递data, compted, methods, watch 等属性。

    这些属性的功能都是一样的 ,但是有些属性的写法是特殊的。

      data属性:

        是一个函数,返回值是绑定的数据。

        this指向组件实例(由于该方法执行完毕,才能绑定数据,因此当前的this无法访问模型数据)

       template 定义模板的,有两种用法

        第一种:属性值是模板字符串(将组件的模板写在脚本中了)

        第二种:属性值是css选择器,此时会将页面中对应元素的内容作为组件的模板。

         html定义模板有两种方式:

          1 通过script模板标签定义

          2 通过template标签定义(vue建议)

        注意:模板中最外层有且只有一个根元素。

    第三步 注册组件,有两种方式

     1 全局注册

      Vue.component(name, Comp):全局注册的组件可以在任何组件中使用。

     2 局部注册
      components: { name: Comp }:局部注册的组件只能在当前组件中使用
         name表示组件名称,要使用驼峰式命名(首字母不区分大小写)
        Comp表示组件类

    注意:组件是完成独立的,彼此之间数据,方法等不会共享。

    父子组件

      由于组件类继承了Vue类,因此组件实例化对象可以看成是vue实例化对象,反之vue实例化对象也可以看成是组件实例化对象

    在vue实例中使用组件,我们就可以将

       vue实例看成是父组件

      将自定义的组件看成是子组件。

00 组件的实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="msg">
        <p @click="taggle">{{msg}}---{{dealMsg}}</p>
        <hr>
        <!-- 第一组件第一步 -->
        <home></home>
        <hr>
        <!-- <Home></Home> -->
    </div>

    <!-- 模板视图渲染的 -->

    <!-- 第一种方式 -->
    <!-- 这种方式 不可行  会导致 元素渲染两遍 -->
    <!-- <div id="tpl">
        <h2>第一种模板定义的方式</h2>
    </div> -->

    <!-- 第二种方式 -->
    <!-- 缺点:语义化不好 -->
    <!-- <script type="text/template" id="tpl">
        <h2>第二种模板定义的方式</h2>
    </script> -->

    <!-- 第三种方式  推荐使用 -->
    <!-- 这种方式需要注意 最外层有且只有一个根元素 -->
    <template id="tpl">
        <div>
            <h2>第三种模板的定义方式</h2>
            <h3>第二条数据</h3>

            <!-- 在子组件中不能访问 父组件的 msg  和 dealMsg  需要在自己的数据的定义 -->

            <h4 @click="childTaggle">子组件自定义的数据 ---{{msg}} ---- {{dealMsg}}</h4>
        </div>
    </template>


    <script src="./dist/00.js"></script>
    
</body>
</html>

00.js



import Vue from 'vue';

// 定义组件第二部
let Home = Vue.extend({
    // 定义模板
    // template: '<h1>Home</h1>'

    // 模板渲染视图有三种方式 建议使用第三种
    template:'#tpl',

    // 定义自己的数据
    data(){
        return{
            msg:"child hello"
        }
    },

    // 定义计算属性数据 
    computed:{
        dealMsg(){
            return this.msg.toUpperCase()
        }
    },

    // 定义自己的方法
    methods:{
        childTaggle(){
            console.log(arguments,"子组件的方法");
        }
    }
})

// 第三部注册组件
// 全局注册组件
Vue.component('home', Home)

let app = new Vue({
    el: "#app",
    data: {
        msg: "hello"
    },
    // 局部注册组件
    // components:{Home,Home},
    components:{Home},
    computed: {
        dealMsg() {
            return this.msg.toUpperCase()
        }
    },
    methods: {
        taggle() {
            console.log(arguments,"父组件的方法");
        }
    }
})

效果图
在这里插入图片描述

二、动态组件

我们目前使用的组件都是静态的:一个元素对应一个组件类

想使用动态组件(一个元素可以对应多个组件类),可以通过component组件实现

  通过is属性定义渲染哪个组件类,

    默认是属性值是字符串

    我们可以通过v-bind指令动态绑定

01 动态组件.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="msg">
        <h1 @click="toggle">{{msg}} --- {{dealMsg}}</h1>
        <hr>

        <!-- 引入组件的方式 -->
        <!-- <Home></Home>
        <List></List> -->

        <!-- 组件的显示方式 -->
        <!-- <component is="home"></component>
        <component is="list"></component> -->

        <!-- 通过数据的显示方式 -->
        <!-- <component :is="page"></component> -->


        <!-- 通过按钮的方式显示组件 -->
        <button @click="page = 'home'">首页</button>
        <button @click="page = 'list'">列表页</button>

        <component :is="page"></component>

    </div>


    <!-- 定义第一个子组件  Home -->
    <template id="oneHome">
        <div> 
            <h2 @click="oneToggle">Home 组件 ---{{msg}} --- {{oneChild}}</h2>
        </div>
    </template>

    <!-- 定义第二个子组件  List -->
    <template id="towList">
        <div>
            <h2>List 组件 --- {{msg}} --- {{TowMsg}}</h2>
        </div>
    </template>

    
    <script src="./dist/01.js"></script>
</body>
</html>

01.js


import Vue from 'vue';

// 定义第一个子组件
let Home = Vue.extend({
    template:'#oneHome',
    data(){
        return{
            msg:'one hello'
        }
    },
    computed:{
        oneChild(){
            return this.msg.toUpperCase()
        }
    },
    methods:{
        oneToggle(){
            console.log("子组件",this);
        }
    }
})


// 定义第二个组件
let List = Vue.extend({
    template:'#towList',
    data(){
        return{
            msg:"towHello"
        }
    },
    computed:{
        TowMsg(){
           return this.msg.toUpperCase();
        }
    }
})

let app = new Vue({
    el:"#app",
    data:{
        msg:"hello",
        page:'home'
    },
    // 注册组件
    components:{
        'home':Home,
        'list':List
    },
    computed:{
        dealMsg(){
            return this.msg.toUpperCase()
        }
    },
    methods:{
        toggle(){
            console.log("父组件",this);
        }
    }
})

效果图
在这里插入图片描述

三、 组件的生命周期

对于一个组件来说,存在三个过程:创建,存在和销毁。因此vue为组件定义生命周期来说明这三个过程。

创建期:
  当我们创建组件的时候(如在页面中使用),组件将执行创建期的方法,

     beforeCreate 组件创建前,此时数据,事件等还没有初始化

     created 组件创建后,此时组件已经绑定了数据,事件等;

     beforeMount 组件构建前,此时确定了组件的模板,以及渲染的容器元素等

     mounted 组件构建后,此时组件上树了

  一个组件在一生中只能创建一次,因此创建期的方法只能执行一次。

  一旦组件的模型数据发生改变,组件将会执行存在期的方法

存在期:

  当组件创建完成,将进入存在期,共分两个阶段:

    beforeUpdate 组件更新前,此时数据改变了,视图尚未更新

    updated 组件更新后,此时数据和视图都更新了

  在一个组件中,我们可以不停的改变模型数据,因此存在期的方法会不停的执行

  存在期的方法执行完毕,只是说一次更新的结束,存在期仍然继续。

销毁期:

  当组件从页面中删除(执行了$destroy方法),组件将进入销毁期,执行销毁期的方法,共分两个阶段:

     beforeDestroy 组件销毁期,此时组件中的数据等还有监听器。

     destroyed 组件销毁后,此时组件的数据所具有的监听器以及子组件等被销毁了。

  一旦组件被销毁,我们就再也无法使用组件了,想使用只能创建新的组件了。

  以上周期方法this都指向组件实例,没有参数,
在这里插入图片描述
在这里插入图片描述
02 组件的生命周期

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <div id="app">
        <h1>{{msg}}</h1>
        <hr>

        <button @click="page = 'home'">首页</button>
        <button @click="page = 'list'">列表页</button>

        <component :is="page"></component>
    </div>

    <!-- 首页 -->
    <template id="home">
        <div>
            <h2>Home 页展示</h2>
            <input type="text" v-model="msg">
        </div>
    </template>

    <!-- 列表页 -->
    <template id="list">
        <div>
            <h2>List 页的展示</h2>
            <input type="text" v-model="msg">
        </div>
    </template>


    <script src="./dist/02.js"></script>
</body>

</html>

02 .js



import Vue from 'vue';

// 定义 Home 子组件
let Home = Vue.extend({
    template:'#home',
    data(){
        return{
            msg:'home hello'
        }
    },

    // 组件的生命周期
    // 创建期
    // 创建前
    beforeCreate(){
        console.log('111','beforeCreate',this.msg,this,arguments);
    },
    // 创建后
    created() {
        console.log('222','create',this.msg,this,arguments);
    },

    // 构建期
    // 构建前
    beforeMount(){
        console.log('333','beforeMount',this,arguments);
    },
    // 构建后
    mounted() {
        console.log('444','mounted',this,arguments);
    },

    // 存在期
    // 更新前
    beforeUpdate(){
        console.log('555','beforeUpdate',this,arguments);
    },
    // 更新后
    updated(){
        console.log('666','updated',this,arguments);
    },

    // 销毁期
    // 销毁前
    beforeDestroy() {
        console.log('777','beforeDestroy',this,arguments);
    },
    // 销毁后
    destroyed(){
        console.log('888','directives',this,arguments);
    }

})

// 定义 List 子组件
let List = Vue.extend({
    template:'#list',

    data(){
        return{
            msg:"list hello"
        }
    }
})

let app = new Vue({
    el:"#app",

    data:{
        msg:"父组件",
        page:"home"
    },
    // 注册组件
    components:{
        'home':Home,
        'list':List
    }
})

效果图
在这里插入图片描述

四、keep-alive

  当组件从页面中删除,组件将执行销毁期的方法,如果从页面中移除的时候,不想将组件销毁,可以使用keep-alive组件。

  该组件会将从页面中移除的组件暂存到内存中,没有被销毁,再次显示的时候,直接从内存中读取。

  由于组件没有被销毁,因此不会执行销毁期的方法。为了表示显示和隐藏的过程,keep-alive组件为内部的组件拓展了两个方法:

    activated 组件被激活,可以在页面中看到

    deactivated 组件被禁用,从也页面中移除了

  注意:由于组件被缓存到内存中,因此没有被销毁,保留最后一个状态,因此当组件再次显示的时候,会显示被保留的最后一个状态

03 keep-alive.html

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <div id="app">
        <h1>{{msg}}</h1>
        <hr>

        <button @click="page = 'home'">首页</button>
        <button @click="page = 'list'">列表页</button>

        <!-- keep-alive 实现数据的缓存 -->
        <keep-alive>
            <component :is="page"></component>
        </keep-alive>
    </div>

    <!-- 首页 -->
    <template id="home">
        <div>
            <h2>Home 页展示</h2>
            <input type="text" v-model="msg">
        </div>
    </template>

    <!-- 列表页 -->
    <template id="list">
        <div>
            <h2>List 页的展示</h2>
        </div>
    </template>


    <script src="./dist/03.js"></script>
</body>

</html>

03 keep-alive.js



import Vue from 'vue';

// 定义 Home 子组件
let Home = Vue.extend({
    template: '#home',
    data() {
        return {
            msg: 'home hello'
        }
    },

    // 组件被激活
    activated() {
        console.log("1111", this.msg, this, arguments);
    },
    // 组件被禁用
    deactivated() {
        console.log("2222", this.msg, this, arguments);
    }

})

// 定义 List 子组件
let List = Vue.extend({
    template: '#list'
})

let app = new Vue({
    el: "#app",

    data: {
        msg: "父组件",
        page: "home"
    },
    // 注册组件
    components: {
        'home': Home,
        'list': List
    }
})

效果图
在这里插入图片描述

五、组件通信

  组件是一个完整独立的个体,彼此之间的数据,方法等不会共享,想在组件之间共享数据,我们可以使用组件通信的技术。

通常有三个方向:

    父组件向子组件通信

    子组件向父组件通信

    兄弟组件间通信。

5.1 父组件向子组件通信

  父组件向子组件通信就是将父组件的数据,方法传递给子组件。

需要两步

  第一步 在父组件模板中,为子组件元素传递数据。

    属性值默认是字符串,想传递变量或者方法,要使用v-bind指令动态传递。

    命名规范:字母小写,横线分割单词

  第二步 在子组件中,接收数据或者方法。

    在props属性中接收,有两种接收方式

    第一种 属性值是数组,每一个成员代表一个接收的属性名称。

    第二种 属性值是对象

      key 表示接收的属性名称

      value 有三种方式

        可以是类型的构造函数:Number, String等
        可以是数组,每一个成员代表一种类型(定义多种类型)
        可以是对象:

          type:表示类型的构造函数,

          required:是否是必须的,

          default:默认值,属性值可以是数据,属性值还可以是方法,返回值就是默认的数据。

          validator:校验方法,在方法中,校验数据是否合法。

  注意:接收属性的时候,属性命名规范:驼峰式命名。

  props中接收的数据与模型中的数据一样,都添加给实例化对象自身,并设置了特性。

  因此我们既可以在JS中使用,也可以在模板中使用

04 父组件向子组件通信.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>{{msg}}</h1>
        <input type="text" v-model="msg">
        <hr>
        <home data="dazhaxie" num="100" :data-msg="msg"></home>
        <hr>
        <home data="dazhaxie" num="100"></home>
    </div>

    <script src="./dist/04.js"></script>
    
</body>
</html>

04.js


import Vue from 'vue';

// 定义子组件
let Home = Vue.extend({
    // 接收数据
    // 接收的数据,添加给实例化对象自身并设置特性,可以在模板中直接使用
    // props:['data','num','dataMsg'],
    // 添加接收数据的约束
    props:{
        data:String,
        // num:Number, // 如果不是 Number 类型会报错
        num:[Number,String], // 可以是多种类型的
        // dataMsg:String,
        // 更多的限制
        dataMsg:{
            type:String,

            // 当组件第二条没有传递 dataMag 数据时的限制
            // 设置为必须有数据的
            // required:true,
            // 如果没有传递 给默认值
            // default:'abc',

            //  也可以返回一个默认值
            default(){
                return 'hello abc'
            },

            // 也可以添加校验
            // 限制动态长度最少为 5 
            validator(value){
                // console.log(this,arguments);
                return value.length >=5 
            }
        }
    },
    template:`
        <div>
            <h2>子组件  home --- {{data}} --- {{num}} --- {{dataMsg}}</h2>
        </div>
    `,
    // created(){
    //     console.log(this,typeof this.num); // num 是 string类型的
    // }

})

let app = new Vue({
    el:"#app",

    data:{
        msg:'hello'
    },
    components:{
        'home':Home
    }
})

效果图
在这里插入图片描述

5.2 $parent

  子组件中还可以通过$parent属性访问父组件,因此就可以间接的获取父组件中的数据。

  但是工作中,不建议这么使用,因为这种方式与父组件耦合并且无法校验数据,

  所以我们要使用父组件向子组件传递属性的方式实现通信

5.3 自定义事件

  vue也实现了观察者模式,提供了订阅消息,发布消息,注销消息等方法。

   $on(type, fn) 订阅消息方法

    type:消息名称,

    fn: 消息回调函数,参数是由 $emit 方法传递的

  $emit(type, …args) 发布消息方法

     type:消息名称

     …args:从第二个参数开始,表示传递的数据

   $off(type, fn) 注销消息方法

    type:消息名称

    fn:消息回调函数

  组件是一个完整独立的个体,因此彼此之间数据不会共享,所以发布消息与订阅消息必须是同一个组件

5.4 子组件向父组件通信

子组件向父组件通信的实现

  在父组件中,订阅消息,

  在子组件中,通过this.$parent访问父组件,发布消息并传递数据

  在父组件中,接收并存储数据

  在通信过程中,我们访问了$parent,因此产生了耦合,所以在工作中,这种方式不常用。

子组件向父组件通信有两种常用方式:

    第一种:模拟DOM事件

    第二种:传递属性方法

05 子向父$parent + 发送事件(不建议).html

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">

        <h1>{{msg}}</h1>
        <input type="text" v-model="msg">
        <hr>

        <home data="dazhaxie" num="100" :msg="msg"></home>
    </div>

    <script src="./dist/05.js"></script>

</body>

</html>

05 .js



import Vue from 'vue';

// 定义子组件
let Home = Vue.extend({
    props:['data','num','msg'],
    template:`
        <div>
            <h2>child {{data}} --- {{num}} --- {{msg}}</h2>
            <h3>{{$parent.msg}}</h3>
        </div>
    `,
    created(){
        // console.log(this,this.$parent.msg);

        // 子组件向父组件发送数据
        setTimeout(()=>{
            // 发布消息
            // 通过子组件发布消息
            // this.$emit('ickt',200,500,false)
            // 通过父组件发布
            // $parent 在通信过程中,访问了$parent ,因此产生了 耦合,所以在工作中,这种方式不常用
            this.$parent.$emit('dzx','from child',200,100)
        },1000)
    },
})


let app = new Vue({
    el:"#app",

    data:{
        msg:'hello'
    },
    components:{
        'home':Home
    },
    // 父组件自行传递订阅消息
    created(){
        // 订阅消息
        this.$on('dzx',(msg,...args)=>{
            // console.log(args);
            // console.log(msg);
            // 父组件接收子组件传递过来的数据
            this.msg=msg
        })
        // 发送消息
        // this.$emit('dzx',100,200,true)
    }
})

效果图
在这里插入图片描述

5.4.1 子向父 模拟 DOM 事件

  绑定DOM事件:v-on:click=”fn”

  模拟DOM事件:v-on:demo=”fn” demo事件名称就是自定义事件名称

子组件向父组件通信

    1在父组件模板中,为子组件元素绑定自定义事件。

      消息名称:字母小写横线分割单词。

      注意:绑定事件的时候,不要添加参数集合

        如果没有添加参数集合,可以接收所有的数据

        如果添加了参数集合,不能接收数据,即使传递了$event也只能接收一条数据

    2 在子组件中,通过$emit发布消息,并传递子组件中的数据。

      消息名称:字母小写横线分割单词。注意:这是唯一一个不需要做驼峰式命名转换的地方。

    3 在父组件事件回调函数中,接收数据并存储数据。

06 子向父模拟 DOM 事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="app">
        <h1>parent ----{{  msg  }}</h1>
        <hr>
        <!-- 引入子组件 -->
        <!-- 第一步  绑定自定义事件 -->
        <!-- 谁的模板使用的数据就是谁的 -->
        <home @da-zha-xie="childToggle"></home>

        <!-- 不要添加参数集合,否则接收的数不完整 -->
        <!-- <home @da-zha-xie="childToggle($event,200,'dazha')"></home> -->
    </div>

    <script src="./dist/06.js"></script>
    
</body>
</html>

06 .js



import Vue  from 'vue';

// 定义子组件
let Home = Vue.extend({
    template:`
        <div>
            <input type="text" v-model="msg">
            <h2>child ---- {{ msg }}</h2>
        </div>
    `,
    data(){
        return{
            msg :"hello child "
        }
    },
    // 监听数据的改变
    watch:{
        // 第二次发布更新的数据
        msg(value){
            this.$emit('da-zha-xie',value)
        }
    },
    created(){
        // 第一次 发布静态消息
        this.$emit('da-zha-xie',this.msg,100,200)
    }

})

let app = new Vue({
    el:"#app",

    data:{
        msg:"hello parent"
    },
    // 注册子组件
    components:{
        'home':Home,
    },
    // 定义事件
    methods:{
        childToggle(msg){
            // 此时的 this 指向父组件
            // console.log(this,arguments);

            // 更新数据
            this.msg = msg
        }
    }
})

效果图
在这里插入图片描述

5.4.2 子向父 传递属性方法

  可以向子组件传递父组件方法,让子组件执行父组件方法并传递数据的形式,来实现通信。

    第一步 在父组件模板中,为子组件传递父组件中的方法。

    第二步 在子组件中,接收方法,执行方法并传递数据,

    第三步 在父组件的方法中,接收数据,并存储数据。

07 子向父 传递属性的方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="app">
        <h1>parent ---- {{msg}}</h1>
        <hr>
        <!-- 谁的模板使用的数据就是谁的 -->
        <!-- 第一步 向子组件传递方法 -->
        <home :da-zha-xie="childToggle"></home>
    </div>
    <script src="./dist/07.js"></script>
    
</body>
</html>

07 .js



import Vue from 'vue';

// 定义子组件
let Home = Vue.extend({
    //第二部  子组件接收数据
    props:['daZhaXie'],
    template:`
        <div>
            <input type="text" v-model="msg">
            <h2>child ---- {{msg}}</h2>
        </div>
    `,
    data(){
        return{
            msg:"child msg"
        }
    },
    // 第四步 监听数据 并 发送数据
    watch:{
        msg(value){
            // 第一种方法
            // this.daZhaXie(value)
            // 第二种方法
            this.daZhaXie(this.msg)
        }
    },
    created(){
        // 第三 部 传递静态数据 
        this.daZhaXie(this.msg,100,200)
    }
})

let app = new Vue({
    el:"#app",

    data:{
        msg:"parnent hello"
    },
    // 注册子组件
    components:{
        'home':Home
    },
    // 定义事件
    methods:{
        childToggle(msg){
            console.log(this,arguments);

            // 第 五 步更新数据
            this.msg = msg 
        }
    }
})

效果图
在这里插入图片描述

5.5 兄弟组件间通信

  两个兄弟组件之间没有必然的联系,但是它们都与父组件有联系,因此要实现兄弟组件间通信就是综合

  使用以上两种通信方式。

    父组件向子组件通信

    子组件向父组件通信

流程:

    1 将一个子组件的数据传递给父组件。

    2 父组件存储数据,再将新的数据传递给另一个子组件。

  对于不相干的两个组件之间的通信,后面会学习VueX来解决。
08 兄弟组件之间的通信.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="app">
        <h1>parnet-----{{ msg }}</h1>
        <hr>
        <!-- 第一种 模拟 DOM 事件 通信 -->
        <!-- <Home @demo="homeToggle"></Home> -->

        <!-- 第二种 传递方法 通信 -->
        <Home :da-zha-xie="homeToggle"></Home>

        <hr>
        <!--  将数据传递给兄弟组件 -->
        <list :msg="msg"></list>
    </div>

    <script src="./dist/08.js"></script>
    
</body>
</html>

08 .js



import Vue  from 'vue';

// 定义第一个子组件
let  Home =Vue.extend({
    // 第二种接收方法
    props:['daZhaXie'],
    template:`
        <div>
            <input type="text" v-model="msg" >
            <h2>home ---- {{msg}}</h2>
        </div>
    `,
    data(){
        return {
            msg:"home chile"
        }
    },
    created(){
        // 第一种 事件 传递事件的第一次传递 静态
        // this.$emit('demo',this.msg)

        // 第二种 执行方法
        this.daZhaXie(this.msg)
    },

    watch:{
        msg(value){
            //第一种  传递事件的第二次传递 动态
            // this.$emit('demo',value)

            // 第二种 执行方法
            this.daZhaXie(this.msg)
        }
    }
})

// 定义第二个子组件
let List =Vue.extend({
    // 接收数据
    props:['msg'],
    template:`
        <h3>listChild --- {{msg}}</h3>
    `,

})

let app = new Vue({
    el:"#app",
    data:{
        msg:"parent hello"
    },
    // 注册子组件
    components:{
        'home':Home,
        'list':List
    },
    // 定义事件
    methods:{
        homeToggle(msg){
            console.log(msg,"打印的 msg 数据");
            this.msg = msg
        }
    }
})

效果图
在这里插入图片描述

六、 数据双向绑定的实现

  vue为简化数据双向绑定,提供了v-model指令。数据双向绑定有两个方向

  一个方向是数据由模型进入视图

    通过为元素的value属性绑定数据实现的。

  一个方向是数据由视图进入模型

    通过为元素订阅input事件实现的

  v-model指令可以看成是语法糖指令,简化了数据双向绑定的实现。

  对于组件实现数据双向绑定与input元素是一样的。在组件中,

  通过value属性接收父组件传递的数据(父组件向子组件通信)

  通过input事件向父组件传递数据(子组件向父组件通信)

09 v-model 指令实现数据通信.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <!-- <input type="text" v-model="msg"> -->
        <h1> hello --- {{msg}}</h1>

        <!-- 数据由模型进入视图 -->
        <!-- <input type="text" :value="msg"> -->

        <!-- 数据由视图进入模型 -->
        <!-- <input type="text" @input="updateValue"> -->


        <!-- 对以上方法综合使用 -->
        <!-- <input type="text" :value="msg" @input="updateValue"> -->

        <!-- 简写的方式 -->
        <!-- 因为这是 一个 js 环境 所以可以这么写 -->
        <input type="text" :value="msg" @input=" e => this.msg = e.target.value">

        <hr>
        <home v-model="msg" ></home>
    </div>

    <script src="./dist/09.js"></script>

</body>
</html>

09 .js


import Vue from 'vue';

// 定义子组件
let Home = Vue.extend({
    props:['value'],
    template:`
        <div>
            <h1>home ----- {{value}}</h1>
            <input type="text" :value="value" @input=" e => this.$emit('input',e.target.value) ">
            <!-- 内部使用 v-model 不能将数据传递给外部 -->
            <!-- <input type="text" v-model="value"> -->
        </div>
    `
})

let app = new Vue({
    el: "#app",

    data: {
        msg: " World"
    },
    components:{
        'home':Home
    },
    // 方法
    methods:{
        // updateValue(e){
        //     // 更新数据
        //     this.msg = e.target.value;
        // }
    }
})

效果图
在这里插入图片描述

七、 ref

  想在脚本中,获取模板中的元素或者组件,可以使用ref属性。
分成两步
    第一步 为元素或者组件添加ref属性。属性值代表它的名称

    第二步 在脚本中,通过this.$refs属性,即可获取对应的元素或者组件。

      为元素设置ref,获取的是源生的DOM对象,要使用源生的API操作
      对组件设置ref,获取的是组件实例化对象。

10 ref.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="app">
        <h1>parent-----{{msg}}</h1>
        <h2 ref="title">{{msg}}</h2>
        <hr>
        <home ref="demo"></home>
    </div>

    <script src="./dist/10.js"></script>
    
</body>
</html>

10 .js



import Vue from 'vue';

// 定义子组件
let Home = Vue.extend({
    template:`
        <div>
            <h5>home child</h5>
        </div>
    `
})

let app = new Vue({
    el:"#app",

    data:{
        msg:"hello world"
    },
    // 注册组件
    components:{
        'home':Home
    },
    mounted(){
        console.log(this.$refs);
        this.$refs.title.style.color="red";
        console.log(this.$refs.title);
    }
})

效果图
在这里插入图片描述

八、 插槽

  让我们在组件的模板中,使用组件元素内部的其它元素(内容)。

使用插槽分成两步:

  第一步 为组件元素内部的子元素设置slot属性,定义插槽名称

  第二步 在组件模板中,通过slot组件,插入内容。

    通过name属性定义插槽名称,如果没有定义name属性,将插入默认的的元素。

如果不想引入slot属性所在的元素,我们可以使用template模板元素。

  此时slot属性要改成v-slot指令,

    使用方式: v-slot:name

    v-slot指令语法糖:# 可以写成 #name

注意:我们不能在普通的元素上使用v-slot指令。

11 slot + v-clot 插槽.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>app-page ----{{msg}}</h1>
        <home>
            <!-- <div slot="header">
                <h5>header page</h5>
            </div>
            <div>
                <h5>body page</h5>
            </div>
            <div slot="footer">
                <h5>footer page</h5>
            </div> -->

            <!-- 使用  template  -->
            <!-- template 建议我们将 slot 属性 改成 v-slot 指令 -->
            <!-- <template slot="header">
                <h5>header page</h5>
            </template> -->
            <template v-slot:header>
                <h5>header page</h5>
            </template>
            
            <template>
                <h5>body page</h5>
            </template>
            <!-- v-slot 的语法糖是  # -->
            <template #footer>
                <h5>footer page</h5>
            </template>
        </home>

    </div>
    <template id="tpl">
        <div>
            <slot name="header"></slot>
            <h3>home page</h3>
            <!-- 没有name  引入默认的 -->
            <slot></slot>
            <h3>{{msg}}</h3>
            <slot name="footer"></slot>
        </div>
    </template>
    
    <script src="./dist/11.js"></script>
</body>
</html>

11 .js


import Vue from 'vue';

let Home = Vue.extend({
    template:"#tpl",
    data(){
        return{
            msg:"dazhaxie"
        }
    }
})

let app = new Vue({
    el:"#app",

    data:{
        msg:"hello World"
    },
    components:{
        'home':Home
    }
})

效果图
在这里插入图片描述

九、组件与语法糖

语法糖

  : => v-bind:

  @ => v-on:

  # => v-slot:

组件

   component, slot, transition, transtion-group, keep-alive

十、 插槽作用域

  插槽作用域就是说,我们在组件元素内部的子元素中,使用的数据存储在哪里。

    在父组件模板中,组件元素内部的子元素仍然使用父组件中的数据。

  插槽作用域技术就是让我们在组件元素内部的子元素中,使用子组件中的数据。

共分两步

  第一步 在子组件模板中,为slot组件传递属性数据。

    命名规范:字母小写,横线分割单词。

    想动态传递数据,要使用v-bind指令。

  第二步 在子组件元素内,通过v-slot指令定义数据的命名空间

    命名空间中的数据名称:要使用驼峰式命名。

12 插槽作用域.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <div id="app">
        <h1>app-page ------ {{msg}}</h1>

        <home>
            <template v-slot:header>
                <div>header page</div>
            </template>
            <template>
                <div>body page</div>
            </template>
            <template #footer="homeData">
                <div>footer page --- {{msg}}</div>
                <hr>
                <div>home--{{homeData.color}} ---- {{homeData.num}} --- {{homeData.homeMsg}}</div>
                <hr>
                {{homeData}}
            </template>
        </home>
    </div>

    <template id="tpl">
        <div>
            <slot name="header"></slot>
            <hr>
            <h6>home-page ---- {{msg}}</h6>
            <slot></slot>
            <hr>
            <slot name="footer" color="blue" :num="100" :home-msg="msg"></slot>
        </div>
    </template>

    <script src="./dist/12.js"></script>
    
</body>
</html>

12 .js



import Vue from 'vue';

let Home = Vue.extend({
    template:'#tpl',
    data(){
        return{
            msg :"home-dazhaxie"
        }
    }
})

let app = new Vue({
    el:"#app",

    data:{
        msg:"parent-dazhaxie"
    },
    components:{
        'home':Home
    }
})

效果图

在这里插入图片描述

;