慕课网Vue-去哪了4

第四章 深入理解Vue组件

一  组件使用的细节点

1.   使用is标签,解决标签渲染中的小bug

问题:创建一个table

<table>
      <tbody>
            <tr><td>1</td></tr>
            <tr><td>2</td></tr>
            <tr><td>3</td></tr>
      </tbody>
 </table>

审查元素:正常的表格逻辑关系

问题:希望table每一行的数据是一个子组件

<body>
    <div id="app">
        <table>
            <tbody>
                <row></row>
                <row></row>
                <row></row>
            </tbody>
        </table>
    </div>
    <script>
        Vue.component('row',{
            template:"<tr><td>this is row</td></tr>"
        })

        var app = new Vue({ 
           el:"#app", //组件的使用要把实例挂载到页面上
        })
    </script>
</body>

审查元素:数据虽然已经显示出来,但是页面上的元素排列出错了(tr 已经和table并列了)

在html5规范里,table 里面是tbody ,tbody里面是tr ,现在使用了子组件row,所以浏览器解析row就出了问题。

解决:table里面仍然用 tr 标签,用is标签把row组件的内容显示在tr 里面。

<!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>todolidt</title>
    <script src= './vue.js'></script>
</head>
<body>
    <div id="app">
        <table>
            <tbody>
                <tr is="row"></tr>
                <tr is="row"></tr>
                <tr is="row"></tr>
            </tbody>
        </table>
    </div>
    <script>
        Vue.component('row',{
            template:"<tr><td>this is row</td></tr>"
        })

        var app = new Vue({ 
           el:"#app", //组件的使用要把实例挂载到页面上
        })
    </script>
</body>
</html>

表示:虽然这写的是tr,实际上它是一个row组件。既保证tr 里面显示的是组件,又保证它符合h5编码规范,解决程序bug。

同样:ul li 有序列表 ,ol li无序列表,slect option 选择标签。

2.   在子组件定义data时候,data必须是一个函数而不是一个对象

<body>
    <div id="app">
        <table>
            <tbody>
                <tr is="row"></tr>
                <tr is="row"></tr>
                <tr is="row"></tr>
            </tbody>
        </table>
    </div>
    <script>
        Vue.component('row',{
            data(){ //函数、返回值是一个对象 data:function(){}
                 return {
                     content:"this is row"
                 }
            },
            template:"<tr><td>this is row</td></tr>"
        })

        var app = new Vue({ 
           el:"#app", //组件的使用要把实例挂载到页面上
           data:{ //对象

           }
        })
    </script>
</body>

因为:子组件不同于根组件只会被调用一次。每一个子组件都应该有自己对应的数据。

通过一个函数返回一个对象的目的,就是让每一个子组件都拥有一个独立的数据存储,不会出现多个子组件互相影响的情况。

3.   Vue中的 ref

vue不建议在代码里面去操作dom,但是在处理一些复杂的动画效果,不操作dom而靠vue数据绑定,

有的时候处理不了这样的情况,就必须去操作dom。通过ref引用的形式进行dom操作。

 方法: this.$refs.ref  从整个vue实例里的所有引用,找到一个引用名ref,ref对应的就是div的dom节点。

如果从组件标签里引用ref,ref获取到的是vue组件引用。

<div id="app">
       <div ref="hello" @click="handleClick"> hello world</div>
</div>
<script>
        var app = new Vue({ 
           el:"#app", 
           methods:{
            handleClick(){
                alert(this.$refs.hello.innerHTML)
            }
           }
        })
</script>

计数器功能:(计算点击的总次数)

<body>
    <div id="app">
        <counter ref="one" @change="handleChange"></counter>
        <counter ref="two" @change="handleChange"></counter>
        <div>{{total}}</div>
    </div>
    <script>
        Vue.component('counter',{
            data(){ 
                 return {
                     number:0
                 }
            },
            template:"<div @click='handleClick'>{{number}}</div>",
            methods:{
                handleClick(){
                    this.number++,
                    this.$emit('change')
                }
            }
        })

        var app = new Vue({ 
           el:"#app", //组件的使用要把实例挂载到页面上
           data:{ //对象
              total:0
           },
           methods:{
            handleChange(){
               // this.total++ //不使用ref 
// console.log(this.$refs.one)
this.total=this.$refs.one.number+this.$refs.two.number } } }) </script> </body>

总结:

 使用is标签,解决h5标签上的小bug。子组件定义data,data必须是函数。 ref 引用进行dom操作

二 父子组件的数据传递

1.  父组件向子组件传递数据

 父组件通过属性向子组件传递数据

 count= " 0 "  //传给子组件的是字符串

:count= " 0 "  //传给子组件的是数字, 它后面双引号里传递的是js表达式

 子组件用props接收数据,才可以使用它

==》在vue中父组件向子组件传值,通过属性形式来传递的。

2.  单向数据流:父组件可以向子组件传递参数,父组件可以任意修改,子组件不可以修改父组件传递的数据

解释:当子组件接收的count并不是一个基础类型的数据,而是一个引用、对象形式数据,子组件改变了数据,

有可能接收的引用型数据还被其他子组件使用。这样的话,当前子组件改变的数据不仅仅影响了自身,还可能对其他子组件造成影响

解决:定义一个data ,初始值为this.count

<body>
    <div id="app">
        <counter :count="0"></counter>
        <counter :count="1"></counter>
    </div>
    <script>
        var counter = { //局部组件
            props:['count'],
            data(){
                return {
                    number: this.count
                }
            },
            template:"<div @click='handleClick'>{{number}}</div>",
            methods:{
                handleClick(){
                    this.number ++ 
                }
            }

        }

        var app = new Vue({ 
           
           el:"#app", 
           components:{ //局部组件要在父组件注册
              counter:counter
           },
           data:{ //对象

           }
        })
    </script>
</body>

3. 子组件向父组件传递数据

子组件向父组件传值通过触发事件$emit ,父组件通过监听执行方法。

同样按照单向流规定。(能执行页面,会报错)

计算上讲两个number数据之和:

<body>
    <div id="app">
        <counter @inc="handleIncrease" :count="3"></counter>
        <counter @inc="handleIncrease" :count="2"></counter>
        <div>{{total}}</div>
    </div>
    <script>
        var counter = { //局部组件
            props:['count'],
            data(){
                return {
                    number: this.count
                }
            },
            template:"<div @click='handleClick'>{{number}}</div>",
            methods:{
                handleClick(){
                    this.number =this.number +2;
                    this.$emit('inc',2) 
                }
            }

        }

        var app = new Vue({ 
           
           el:"#app", 
           components:{ //局部组件要在父组件注册
              counter:counter
           },
           data:{ //对象
              total: 5
           },
           methods:{
            handleIncrease(inc){
                //alert(inc)
                this.total+=inc
            }
           }
        })
    </script>
</body>

三 组件参数校验与非props特性

1.  组件参数校验

父组件向子组件传递参数,子组件有权对该参数做一些约束。就是参数校验。

要求接收父组件的参数是字符串:

props 里面就可以不写数组,取而代之,写一个对象。

详解如下:

<!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>todolidt</title>
    <script src= './vue.js'></script>
</head>
<body>
    <div id="app">
        <child count="hello world"></child> 
        <!-- <child :count="123"></child>  数字 -->
        <!-- <child :count="'123'"></child>字符串-->
        <!-- <child count="{a:1}"></child>  对象 -->
    </div>
    <script>
        Vue.component('child',{
           // props:['count'],
           props:{
              //count: String    //子组件接收的count属性必须是string类型
              //count:Number  //传字符串,页面可以响应,会报错
              //count:[Number,String] //通过数组形式,子组件接收的count要么是数值要么是字符串
                    count:{ //count后面也可以跟一个对象
                        type: String,
                        required:false, //是否必传(ture/false),父组件一定要给子组件count值
                        default:'default value', //当count可传可不传时,如果没有传值,调用default默认值
                        validator:function(value){//validator配置项,形参value,字符串长度大于五否则报错
                           return (value.length>5)
                        }
                    }
             
            }, 
            template:'<div>{{count}}</div>'
        })
        var app = new Vue({ 
           
           el:"#app", 
           
        })
    </script>
</body>
</html>

2.  非props特性

props特性:父组件使用子组件时,通过属性向子组件传值,恰好子组件里面声明了对父组件传递过来的属性的接收。

=》父组件调用子组件时候传递了count,子组件在props里面声明了count。父子组件有一个对应关系。这种形式就是props特性

特点: 1. count= "123"  这个属性的传递是不会在dom标签上显示的

            2. 子组件接收了count后,在子组件中就可以通过{{插值表达式}}或者this.count 去取得count内容。

非props特性:父组件向子组件传递了一个属性,但是子组件并没有props声明接收内容

特点: 1.  报错,count没有被定义,无法使用。--  子组件使用了没有声明的count就会报错

            2.  如果使用的是非props特性,且子组件没有使用count,  那么这个属性就会显示在dom上(展示在子组件最外层的dom标签html属性里面)

四  给组件绑定原生事件

<body>
    <div id="root">
        <child @click="handleClick"></child>
    </div>
    <script>
        Vue.component('child',{
            template:'<div>Child</div>',
        })
        var app = new Vue({ 
          el:"#root",   
          methods:{
              handleClick:function(){
                  alert('click')
              }
          }
        })
    </script>
</body>

点击事件是没有执行的。也就是不会弹出click。

因为,当我们给一个组件绑定一个事件时,实际上这个事件绑定的是一个自定义事件。

也就是说我们真正鼠标点击时触发的事件,并不是我们绑定的click事件。

如果想触发这个自定义click事件:

1.  在子组件里,给div元素进行事件的绑定,这是真正的原生事件click,在子组件里methods中,写出方法,alert()。

这只能执行当前的click原生事件,外面的父组件上的click是无法打印的。(在子组件模板里div元素上绑定点击事件)

2.  想要触发自定义事件,只有通过子组件监听this.$emit('click')去触发自定义事件(类似向父组件传值,只是没有传递参数)

3.  为了避免这样麻烦的方法,有时候就想监听child组件的原生事件 

=》添加事件修饰符native 

代码第三行修改<child @click.native="handleClick"></child>

五  非父子组件间的传值

 1.   1和2之前的传值(父子组件)

 2.   1和3之间的传值(1先把数据传给2,2再传给3,反过来同理),十分麻烦

 3.   而3和3之间又怎么传值呢?

当我们的项目中,出现非常复杂的数据传递时,光靠Vue框架是解决不了这个问题的,于是我们需要引进一些其他的工具或者设计模式,

来帮我们解决Vue中复杂的组件传值问题。

非父子组件间的传值:两个组件之前进行传值,但是两个组件不具备父子关系(如1和3、3和3)

解决:1.  借助Vue官方提供的数据层框架,vuex (后面结合项目讲解)

           2.  发布订阅模式(又称为:总线机制/Bus/观察者模式)

本节主要讲解如何使用总线机制来解决非父子组件传值:

点击child兄弟组件内容跟着改变

<!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>非父子组件间传值(Bus/发布订阅模式/总线/观察者模式)</title>
    <script src= './vue.js'></script>
</head>
<body>
    <div id="root">
        <child content="Dell"></child>
        <child content="Lee"></child>
    </div>
    <script>
        Vue.prototype.bus= new Vue()
        //创建一个实例赋值给Vue.prototype.bus
        //Vue.prototype挂载了一个bus属性,这个属性指向一个vue实例
        //只要之后调用new Vue 或者创建组件,每一个组件上都有bus属性
        //因为每一个组件,或者一个vue实例,都是通过Vue这个类来创建的
        //我们在Vue类的prototype上挂载了一个bus属性
        //所以之后每一个通过这个类创建的对象(也就是vue实例上)上,都会有bus这个属性,都指向同一个vue实例
        Vue.component('child',{
            props:{
                content:String
            },
            data(){
                return {
                    selfContent:this.content
                }
            },
            template:'<div @click="handleClick">{{selfContent}}</div>',
            methods:{
              handleClick:function(){
                  //alert(this.content)
                  this.bus.$emit('change',this.selfContent)
                  //把我的内容传递给 另外一个组件this.bus.$emit()
                  //this.bus -- 这个实例上挂载的bus
                  //这个bus -- 又是一个vue实例,所以它就有$emit这个方法
                  // 现在就通过这个方法向外触发事件,并携带数据
              }
            },
            mounted(){ //实例挂载到页面上后执行的方法
                var this_ =this  //child组件
                this.bus.$on('change',function(msg){//让这个组件监听bus的触发事件
                     //alert(msg);弹出两次
        //在一个child里面触发事件的时候,其实这两个child都进行了同一个事件监听
                     this_.selfContent=msg  //content不可以直接修改,单向流规则
                     //this 指的是child组件的bus属性
                })
            }
        })
        var app = new Vue({ 
          el:"#root",        
        })
    </script>
</body>
</html>

 通过一个bus可以实现vue中两个非父子组件传值,当前的是兄弟节点,之后非父子非兄弟节点的使用也是一样的

 六 在Vue中使用插槽

 1.   插槽的使用场景

需求:子组件除了展示p标签之外。还要展示一段内容,这段内容并不是子组件决定的,而是父组件传递过来的。

以前:(通过属性父子组件传值)

 <div id="root">
        <child content='<p>Dell</p>'></child>
    </div>
    <script>
        Vue.component('child',{
            props:['content'],
            template:`<div>
                        <p>hello</p>
                        <div v-html="this.content"></div>
                       </div>`
            //ES6语法,允许换行
            // p元素转义 v-html ==  innerHTML
        })
        var app = new Vue({ 
          el:"#root",        
        })
    </script>

 1. 此时,<p>Dell</p>外层就多出一个div。 此时用template模板占位符 无法渲染。 (必须包裹一个div)

2. 如果用这种形式传递的内容很多时,虽然也能展示出来,但是在编译代码上变得很难阅读

===》当我们子组件的一部分内容,是根据父组件传递过来dom进行显示时,此时可以不用属性传值

===》vue提供了一个新的语法 插槽(slot)

<body>
    <div id="root">
        <child>
            <p>DELL</p> <!-- 插槽 -->
            <p slot="header">Header</p> <!-- 具名插槽 -->
            <div slot="footer">Footer</div><!-- 具名插槽 -->
            <p>DELL</p> <!-- 插槽 -->
        </child>
    </div>
    <script>
        Vue.component('child',{
            props:['content'],
            template:`<div>
                        <slot name="header"><h1>default header</h2></slot>
                        <p>hello</p>
                        <slot>默认内容</slot> 
                        <p>world</p>
                        <slot name="footer"></slot>
                       </div>`
        })
        /* 
        当不存在插槽时,显示默认内容
        <slot></slot> 用以显示没有名字的所有插槽
        <slot name="header">显示该名字的插槽,header可存在多个,也可显示多个
都可以有默认值
*/ var app = new Vue({ el:"#root", }) </script> </body>

七 Vue中的作用域插槽

子组件循环遍历显示

 

<body>
    <div id="root">
        <child></child>
    </div>
    <script>
        Vue.component('child',{
            data(){
                return {
                    list:[1,2,3,4]
                }
            },
            props:['content'],
            template:`<div>
                        <ul>
                            <li v-for="item of list">{{item}}</li>
                        </ul>
                       </div>`
        })
        var app = new Vue({ 
          el:"#root",        
        })
    </script>
</body>

 

需求:child组件有可能在很多地方调用,我希望不同地方调用child组件时候。循环列表的样式不是由child决定而是由父组件决定

==》

 

<body>
    <div id="root">
        <child>
            <template slot-scope="props">
                <li>{{props.item}}</li>
            </template> <!-- 作用域插槽固定写法 -->
        </child>
    </div>
    <script>
        Vue.component('child',{
            data(){
                return {
                    list:[1,2,3,4]
                }
            },
            template:`<div>
                        <ul>
                            <slot v-for="item of list" :item= item></slot>
                        </ul>
                       </div>`
        })
        var app = new Vue({ 
          el:"#root",        
        })
    </script>
</body>

 

子组件slot 做循环(传一个属性) ,外部做样式显示(使用属性)

父组件调用slot ,子组件接收slot 。

逻辑执行:首先,父组件调用子组件时,给子组件传了一个插槽,这个插槽叫做作用域插槽,作用域插槽必须以template开头结尾的内容。

同时,这个插槽要声明,我从子组件接收的数据要放在哪 (slot-scope)。

然后它要告诉子组件一个模板,用于给接收的数据以怎样的展示

个人总结父组件调用子组件时,传递插槽,子组件接收使用插槽。

                  父组件会创建一个模板信息,在子组件使用插槽给创建的数据做循环把结果传递给父组件后,父组件会用这个模板显示传递的结果。

使用场景:当子组件做循环或者某一部分dom结构应该由外部传递进来时,用作用域插槽。

使用作用域插槽,子组件可以向父组件的插槽里面传数据,父组件在template里用slot-scope接收数据。

什么是插槽,插槽的使用,什么是作用域插槽:

子组件除了展示自身的内容外。还要展示一些父组件传递的元素内容,这个过程就是插槽。

父组件调用子组件时,会传递一个插槽。内容包含dom元素。子组件就接收使用插槽,显示到页面上。

父组件调用子组件时,仍然会传递一个插槽(作用域)。内部包含dom元素(dom节点和内容),而dom元素内容是由子组件传递过来的。

子组件使用插槽时,会用当前的数据做遍历后传递给父元素,然后显示到页面上。这就是作用域插槽。

八 动态组件与v-once指令

1.  动态组件

需求:点击button,子组件切换显示

通过绑定点击事件

 

<body>
    <div id="root">
        <child-one v-if="type==='child-one'">
        </child-one>
        <child-two v-if="type==='child-two'">
        </child-two>
        <button @click="handleBtnClick">change</button>
    </div>
    <script>
        Vue.component('child-one',{
            template:`<div>child-one</div>`
        })
        Vue.component('child-two',{
            template:`<div>child-two</div>`
        })
        var app = new Vue({ 
          el:"#root",   
          data:{
              type:'child-one'
          },
          methods:{
            handleBtnClick(){
                this.type= this.type ==='child-one'?
                        'child-two':'child-one'
            }
          } 
        })
    </script>
</body>

 

通过动态组件:

<child-one v-if="type==='child-one'"></child-one>

<child-two  v-if="type==='child-two'"></child-two>    

=》

<component :is="type"></component>   //vue自带的标签component,指动态组件 它有一个is属性,绑定一个数据
<body>
    <div id="root">
       <!--  <child-one v-if="type==='child-one'">
        </child-one>
        <child-two v-if="type==='child-two'">
        </child-two> -->
        <component :is="type"></component>
        <!-- vue自带的标签component,指动态组件 
             它有一个is属性,绑定一个数据
        -->
        <button @click="handleBtnClick">change</button>
    </div>
    <script>
        Vue.component('child-one',{
            template:`<div>child-one</div>`
        })
        Vue.component('child-two',{
            template:`<div>child-two</div>`
        })
        var app = new Vue({ 
          el:"#root",   
          data:{
              type:'child-one'
          },
          methods:{
            handleBtnClick(){
                this.type= this.type ==='child-one'?
                        'child-two':'child-one'
            }
          } 
        })
    </script>
</body>
View Code

 

动态组件:根据is里面数据的变化,自动加载不同组件

2.  v-once指令

template:`<div v-once>child-one</div>`

使用v-if进行每一次切换时,实际上,Vue底层会判断这个组件已经不用了,取而代之用另一个组件,

就会把第一个组件销毁掉,去创建另一个组件(循环点击会频繁创建销毁)。

这种操作是耗费性能的,如果组件的内容每一次都一样,在组件上加 v-once指令。

当child-one第一次被渲染时,因为v-once这个指令,会直接放到内存里。

当点击切换时,并不需要重新创建child-one组件,而是从内存里直接拿出以前的child-one组件。

 

v-once 可以有效提供一些静态内容的展示效率。

 

另: vs code 区域块 注释 快捷键 alt + shift + a

posted @ 2022-03-08 23:54  以深  阅读(64)  评论(0编辑  收藏  举报
TOP