手把手教你Vue框架从入门到实战项目(三)

第三章 深入理解Vue组件

组件使用中的细节点

解析 DOM 模板时
    <div id="app">
        <table>
            <tbody>
                <row></row>
            </tbody>
        </table>
    </div>

    <script>
        Vue.component('row',{
            template:"<tr><td>this is a row</td></tr>"
        })

        new Vue({
            el:"#app"
        })
    </script>

我们会发现,执行上面的代码,虽然效果有了,但是这个自定义组件 <row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。

在这里插入图片描述

要解决上面的问题,Vue提供了is特性。把前面的代码改成如下就可以解决了。

    <div id="app">
        <table>
            <tbody>
                <!-- 正确的写法 -->
                <tr is="row"></tr>
            </tbody>
        </table>
    </div>

    <script>
        Vue.component('row',{
            template:"<tr><td>this is a row</td></tr>"
        })

        new Vue({
            el:"#app"
        })
    </script>

除了上面提到的HTML元素,还有其它的元素也得必须记住,在使用到这样元素的时候,也需要改成上面的写法。这些元素有<ul><ol><table><select>,它们对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

组件中的data必须是一个函数

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

下面的这段代码与前面的理论上是一样,但是其实这是错误的写法。因为Vue规定,在组件中data必须是一个函数。

    <div id="app">
        <table>
            <tbody>
                <!-- 正确的写法 -->
                <tr is="row"></tr>
            </tbody>
        </table>
    </div>

    <script>
        Vue.component('row',{
            //错误示范
            data:{
                content:'this is a row'
            },
            template:"<tr><td>{{content}}</td></tr>"
        })

        new Vue({
            el:"#app"
        })
    </script>

所以要解决上面的问题,只需将data改写成函数就可以了。

    <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:function(){
                return {
                    content:"this is a row"
                }
            },
            template:"<tr><td>{{content}}</td></tr>"
        })

        new Vue({
            el:"#app"
        })
    </script>

Vue 之所以这么设计,是因为子组件它不像根组件(new Vue),它只调用一次。而我们希望的是,被调用的地方它们都应该有自己的数据。通过一个函数来返回一个对象的目的就是让子组件都拥有独立的数据存储。这样就不会出现子组件相互影响的情况了。

Vue中关于ref引用

我们都知道Vue中不建议我们操作DOM,但是在处理一些极其复杂的动画效果,光使用Vue中的数据绑定是处理不好这样的情况的。所以在某些情况下我就就不得不操作DOM。那么要如何操作DOM呢?

我们需要使用ref这中引用的方式来进行DOM操作。

需求:点击页面上的内容,并打印输出。

思路:要实现起来只需要获得页面上的元素内容,就可以实现了。

如下的代码使用ref就可以获得页面上的元素内容。

<div id="app">
    <div ref='hello' @click="clickme">快点我看看效果</div>
</div>

<script>
    new Vue({
        el:"#app",
        methods:{
            clickme:function(){
                console.log(this.$refs.hello.innerHTML);//快点我看看效果
            }
        }
    })
</script>

但是有一个问题,如果把DIV换成我们自定义的组件又是获取到什么呢?其实获取的是组件的引用。

ref在组件里应用

需求:写一个计数器,点击页面上的内容1,且可以自加,同理也存在一个内容2。把内容1和内容2相加并显示出来。

思路:定义个组件,重复调用两次该组件。通过ref来获取组件上的内容。

<div id="app">
    <row ref="one" @change="add"></row>
    <row ref="two" @change="add"></row>
    <div>{{total}}</div>
</div>

<script>
    Vue.component('row',{
        data:function(){
            return {
                number: 0
            }
        },
        template:"<div @click='change'>{{number}}</div>",
        methods:{
            change:function(){
                this.number++;
                this.$emit('change');
            }
        }
    })

    new Vue({
        el:"#app",
        data:{
            total: 0
        },
        methods:{
            add:function(){
                this.total = this.$refs.one.number + this.$refs.two.number;
            }
        }
    })
</script>

父子组件间的传值

父组件如何向子组件传递数据呢?父组件通过属性的形式向子组件传递数据。

<div id="app">
    <!-- 提示:count的值是字符串类型还是数值类型取决于count前面有没有‘:’ -->
    <!-- 含义:父组件[<div id="app">]给子组件[<counter>]传递了一个count属性 -->
    <counter :count="0"></counter>
    <counter :count="0"></counter>
</div>

<script>
    //定义个局部组件
    var counter = {
        //子组件使用props接收传递过来的属性
        props: ['count'],
        template: "<div @click='change'>{{count}}</div>",
        methods: {
            change:function(){
                this.count++;
            }
        }
    }

    var vm = new Vue({
        el: "app",
        //定义的局部组件需要在Vue实例中进行注册才会有效
        components:{
            counter:counter
        }
    })
</script>

通过上面代码中,给template中的div绑定了一个点击事件并对传递过来的数值进行加一,发现也可以改变count的值。但是系统会提示警告。为什么会提示警告呢?这是因为 Vue 中有一个单向数据流的概念。

**单向数据流:**父组件可以向子组件传递任何值,但是子组件不能直接修改父组件传递过来的值。如果要修改,请使用传递事件的方法。

那么要解决以上的单向数据流的问题,其实也很简单。只需要在子组件中多写一个data属性就可以了。

<script>
    //定义个局部组件
    var counter = {
        //子组件使用props接收传递过来的属性
        props: ['count'],
        //前面已经说过,组件中的data属性必须是一个函数
        data:function(){
            return {
                number:this.count
            }
        },
        //这行也要改成{{number}}
        template: "<div @click='change'>{{number}}</div>",
        methods: {
            change:function(){
                this.number++;
            }
        }
    }
</script>

上面我们已经知道了父组件是如何给子组件传值的,那么子组件又是如何向父组件传值的呢?子组件通过事件还父组件传值。

<div id="app">
    <!-- 2.在这里对子组件的事件进行监听 -->
    <counter :count="0" @childchange="add"></counter>
    <counter :count="0" @childchange="add"></counter>
    <!-- 显示总和 -->
    <div>{{total}}</div>
</div>

<script>
    var counter = {
        props: ['count'],
        data:function(){
            return {
                number:this.count
            }
        },
        template: "<div @click='change'>{{number}}</div>",
        methods: {
            change:function(){
                this.number++;
                //1.当子组件被点击的时候,可以通过$emit()向外触发一个事件,且可以传递多个参数
                //坑:'childchange'  这个命名不能用驼峰写法
                this.$emit('childchange',1);//第二个参数表示每次增加了1
            }
        }
    }

    var vm = new Vue({
        el: "#app",
        data:{
            total: 0
        },
        components:{
            counter:counter
        },
        //3.最后在子组件接受到的事件写相应的功能,参数val接收$emit()第二个参数传递过来的值
        methods:{
            add:function(val){
                this.total += val;
            }
        }
    })
</script>

非父子组件间传值

什么是非父子组件?通过下面这张图我们知道,1与2,2与3 都是属性父子组件的关系。那么1与3、2与2、3与3就是属性非父子组件的关系了。前面我们已经知道了父子组件间是如何传值的了,那么非父子组件又是怎么样传值的呢?

在这里插入图片描述
非父子组件间传值,有两种方法。第一种借助Vue官方提供的Vuex框架。但是现在我们暂时先说一下第二种方法:通过(Bus\总线\发布订阅模式\观察者模式)。接下来我们来通过代码来说明。

需求:在页面上有两个文字内容,点击文字1使 文字2=文字1 或 点击文字2使 文字1=文字2

<div id="app">
    <child content='mike'></child>
    <child content='json'></child>
</div>
<script>

    //1.给Vue添加一个bus属性,作用在于只要调用了Vue实例还是组件,里面都有会bus这个属性。它们指向的都是同一个Vue
    Vue.prototype.bus = new Vue();

    Vue.component('child',{
        //3.因为子组件不允许直接修改父组件内容 需要接受并拷贝一份
        data:function(){
            return {
                selfcontent:this.content
            }
        },
        props:['content'],
        //2.要点击子组件就需要给它绑定一个点击事件
        template:"<p @click='clickme'>{{selfcontent}}</p>",
        methods:{
            clickme:function(){
                //4.因为第一步我们添加了一个bus,所以bus相当于是个vue,所以可以通过$emit()方法向外触发一个事件并把当前值传递出去。
                this.bus.$emit('change',this.selfcontent);
            }
        },
        //5.借助生命周期函数,当vue被挂载的时候我们就监听第四步的事件。
        mounted:function(){
            var _this = this;
            //注意:这里的function会使this指向改变
            this.bus.$on('change',function(msg){
                _this.selfcontent = msg;
            })
        }
    })

    var vm = new Vue({
        el:'#app'
    })
</script>

组件参数校验与非props特性

组件参数校验
<div id="app">
    <child content="你好" name="mike json" :age="20"></child>
</div>

<script>
    Vue.component("child",{
        props:{
            //只接受XX类型
            age: Number,

            //可接受多种XX类型  String、Number、Boolean、Array、Object、Date、Function、Symbol
            content: [Number,String,Object],

            //高级数据校验写法
            name:{
                type: String,
                required: true, //为真表示必须传值 即组件中不能没有content
                default: "如果required为假,显示这句",
                //自定义数据校验 value为表示接受到的值
                validator: function(value){
                    return (value.length > 5) //传的值长度大于5个字符 否则报错
                }
            }
        },
        template:"<p>{{content}}{{name}}{{age}}</p>"
    });

    new Vue({
        el:'#app'
    })
</script>
props特性与非props特性

props特性:父组件调用子组件并且向子组件通过属性传递了值,恰好在组件声明里面也接受了父组件传过来的属性。

props特点:1. 父组件向子组件传递的属性不会显示在DOM结构上。2. 传递过来属性,可以通过插值表达式或this.属性的形式获取属性的值。

非props特性:父组件调用子组件并且向子组件通过属性传递了值,但是在组件声明里面并没有接收。即没有声明props的属性。

非props特点:1.父组件向子组件传递的属性会显示在DOM结构上。2.如果template引用了父组件传递过来的属性,系统会报错。

给组件绑定原生事件

方法一:太啰嗦写法

这种写法在前面也写了几遍,我们会发现,需要在组件里写methods也要在 Vue 实例中写methods

<div id="app">
    <child @childclik="handleClick"></child>
</div>

<script>
    Vue.component("child",{
        //给组件绑定原生事件的第一种写法
        template:"<p @click='originClick'>clickme</p>",
        methods:{
            originClick:function(){
                this.$emit('childclik');
            }
        }
    });

    new Vue({
        el:'#app',
        methods:{
            handleClick:function(){
                console.log('childClik');
            }
        }
    })
</script>

方法二:使用.native修饰符

该方法可以让你少些许多代码,却一目了然。

<div id="app">
    <child @click.native="handleClick"></child>
</div>

<script>
    Vue.component("child",{
        //给组件绑定原生事件的第一种写法
        template:"<p>clickme</p>",
    });

    new Vue({
        el:'#app',
        methods:{
            handleClick:function(){
                console.log('childClik');
            }
        }
    })
</script>

在Vue中使用插槽(slot)

和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:

<div id="app">
    <child>
        <!-- 给标签定义一个slot唯一标识,否则会渲染多次-->
        <div class="header" slot="header">header</div>
        <div class="footer" slot="footer">footer</div>
    </child>
</div>

<script>
    //如果我们想让
    Vue.component('child',{
        //这里使用name来获取相应的内容  这种叫做具名插槽
        tempalte:`<div>
                        <slot name='header'></slot>
                        <div class="content">content</div>
                        <slot name='footer'></slot>
                  </div>`
    })

    new Vue({
        el:'#app'
    })
</script>

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<child>
    <template v-slot:header>
        <h1>Here might be a page title</h1>
    </template>
</child>

注意 v-slot 只能添加在 <template> 上,这一点和已经废弃的 slot 特性不同

作用域插槽

使用作用域插槽,子组件可以向父组件传数据,父组件要接收传递过来的数据必须使用<template> 并且使用v-slot来接收传递过来的数据。

<div id="app">
    <child>
        <template v-slot:default='slotProps'>
            <li>{{slotProps.item}}</li>
        </template>
    </child>
</div>

<script>
    Vue.component('child',{
        data:function(){
            return {
                list:[1,2,3,4,5,6]
            }
        },
        template:`<div>
                        <ul>
                            <slot v-for="item of list" :item=item></slot>
                        </ul>
                  </div>`
    })

    var vm=new Vue({
        el:'#app'
    })
</script>

动态组件

<component>是系统内置的标签,它指的就是一个动态组件。使用这个组件,可以轻松的实现来回切换的效果。

is是一个特殊特性,用于动态组件且基于 DOM 内模板的限制来工作。

<div id="app">
    <!-- 当 `type` 改变时,组件也跟着改变 -->
    <component :is='type'></component>
    <button @click="clickme">切换</button>
</div>

<script>
    Vue.component('child-one',{
        template:'<div>child-one</div>'
    })

    Vue.component('child-two',{
        template:'<div>child-two</div>'
    })

    var vm=new Vue({
        el:'#app',
        data:{
            type:'child-one'
        },
        methods:{
            clickme:function(){
                this.type = this.type === 'child-one' ? 'child-two' : 'child-one'
            }
        }
    })
</script>
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋承佑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值