TMaize Blog

Vue组件学习

全局组件

所有的vue实例里面都可以使用

注意: 2.0之后template最外层只有一个结点

Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead

注意: data必须是函数

正确
template="<div><div></div><div></div></div>"
错误(2.0之前也可以)
template="<div></div><div></div>"

组件写法1

Vue.component('my-component', {
    template: '<div @click="num++"></div>',
    data() {
        return {
            num: 0
        }
    }
});

组件写法2

var myComponent = Vue.extend({
    template: '<div @click="num++"></div>',
    data() {
        return {
            num: 0
        }
    }
});

Vue.component('my-component', myComponent);

组件使用模版

在组件里面用template: '<div @click="num++"></div>'是不利于书写的

方式1

<script type="x-template" id="myComponent">
    <div @click="num++"></div>
</script>

template: '#myComponent'

方式2

<template id="myComponent">
    <div @click="num++"></div>
</template>

template: '#myComponent'

动态组件

<div id="app">
    <input type="button" @click="cpName='my-component-1'" value="使用my-component-1渲染">
    <input type="button" @click="cpName='my-component-2'" value="使用my-component-2渲染">
    <component :is="cpName"></component>
</div>

<script>
    Vue.component('my-component-1', {
        template: '<div>我是组件1</div>'
    });
    Vue.component('my-component-2', {
        template: '<div>我是组件2</div>'
    });
    var vm = new Vue({
        el: '#app',
        data: {
            cpName: 'my-component-1'
        }
    });
</script>

注意,组件切换是是重新渲染,数据不会保留

如果希望保留数据,可以使用keep-alive

<keep-alive>
    <component :is="currentView">
    <!-- 非活动组件将被缓存! -->
    </component>
</keep-alive>

父子组件

注意子组件必须在父组件的模板中使用而不是嵌套

错误写法
<father><son></son></father>
正确写法

父组件的模板
<div>
...
<son></son>
</div>
<div id="app">
    <father></father>
</div>

<script>
    Vue.component('father', {
        template: '<div>我是父组件<son></son></div>',
        components: {
            'son': {
                template: '<div>我是子组件</div>'
            }
        }
    });

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

父子组建通信

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中

点击父组件,子组件绑定了父组件的数据也会随之改变

但是点击子组件,子组件改变,父组件不改变,点击一下父组件,子组件会和父组件同步。这样试图用子组件来改变父组件的数据是不行的,子组件的数据虽然变化了,但是会被父组件的变化给覆盖,而且控制台还会有警告子组件的值有会被覆盖的风险

01.png

Vue.component('father', {
    data: function () {
        return {
            fNum: 1
        }
    },
    template: '<div><div @click="fNum++">我是父组件</div><son :sNum="fNum"></son></div>',
    components: {
        'son': {
            props: ['sNum'],
            template: '<div @click="sNum++">我是子组件</div>',
        }
    }
});

有时候,子组件只是需要父组件给他一个初始值,不想让父组件来改变他,可以定义一个局部变量,并用 prop 的值初始化它

或者需要数据进行处理,然后跟随父组件变化,可以使用计算属性

Vue.component('father', {
    data: function () {
        return {
            fNum: 1
        }
    },
    template: '<div><div @click="fNum++">我是父组件</div><son :sNum="fNum"></son></div>',
    components: {
        'son': {
            props: ['sNum'],
            data() {
                return {
                    sNumCopy: this.sNum
                }
            },
            computed: {
                fNumM2() {
                    return this.sNum * 2;
                }
            },
            template: '<div @click="sNumCopy++">我是子组件,</div>',
        }
    }
});

prop数据的绑定和固定数据

<son :sNum="fNum" sNum2="fNum"></son>
props: ['sNum',sNum2]

sNum是父组件数据的绑定

sNum2只是获得了一个字符传,里面的东西统统都是字符串,而不是表达式,可以理解为组件的静态属性吧

上面说到子组件无法直接改变父组件的值,有时候又确实要改变的可以使用emit来通知父组件自己改变了,同时传递给父组件自己改变后的值,然后父组件可以接受这个值,然后进行改变

子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,因为父组件可能会关心这些事件

Vue.component('father', {
    data: function () {
        return {
            fNum: 1
        }
    },
    methods: {
        fFunc(a) {
            this.fNum = a;
        }
    },
    template: '<div><div @click="fNum++">我是父组件</div><son @notice="fFunc" :sNum="fNum"></son></div>',
    components: {
        'son': {
            props: ['sNum'],
            data() {
                return {
                    sNumCopy: this.sNum
                }
            },
            template: '<div @click="sClick">我是子组件</div>',
            methods: {
                sClick() {
                    this.sNumCopy++;
                    this.$emit('notice', this.sNumCopy);
                }
            },
        }
    }
});

sync语法糖

有时候只是简单的实现下数据同步,并不对数据做处理,而父组件还要为了这个么简单的一个功能写一个函数来处理,实在是太麻烦

其实是有sync这个语法糖的,同时很直观的能看到这个数据是双向绑定的

<comp :foo.sync="bar"></comp>
会被vue自动扩展为
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件
this.$emit('update:foo', newValue)

案例如下

Vue.component('father', {
    data: function () {
        return {
            fNum: 1
        }
    },
    template: '<div><div @click="fNum++">我是父组件</div><son :sNum.sync="fNum"></son></div>',
    components: {
        'son': {
            props: ['sNum'],
            template: '<div @click="sUpdate">我是子组件</div>',
            methods: {
                sUpdate() {
                    this.sNum++;
                    this.$emit('update:sNum', this.sNum)
                }
            }
        }
    }
});

不同组件之间通信

不同组件之间的数据共享还好办,data返回同一个Object就行了

之间通信的话就需要借助一个中间人,

<div id="app">
    <comp1></comp1>
    <comp2></comp2>
</div>

<script>
    var hub = new Vue(); //创建事件中心

    Vue.component('comp1', {
        data() {
            return {
                num: 1
            }
        },
        methods: {
            comp2Change() {
                hub.$emit('comp2Change');
            }
        },
        mounted: function () {
            hub.$on('comp1Change', function () {
                this.num++;
            }.bind(this))
        },
        template: '<div @click="comp2Change"></div>'
    });
    Vue.component('comp2', {
        data() {
            return {
                num: 1
            }
        },
        methods: {
            comp1Change() {
                hub.$emit('comp1Change');
            }
        },
        mounted: function () {
            hub.$on('comp1Change', function () {
                this.num++;
            }.bind(this))
        },
        mounted: function () {
            hub.$on('comp2Change', function () {
                this.num++;
            }.bind(this))
        },
        template: '<div @click="comp1Change"></div>'
    });
    var vm = new Vue({
        el: '#app'
    });
</script>

插槽

引用组件时候,在组件标签内写的内容是会被覆盖的,即无效

<div id="app">
    <comp>
        <span>World</span>
    </comp>
</div>

<script>
    Vue.component('comp', {
        template: '<div>Hello</div>'
    });
    var vm = new Vue({
        el: '#app'
    });
</script>

可以通过slot来将标签内的内容传递到组件模板中

<div id="app">
    <comp>
        <span>World</span>
    </comp>
</div>

<script>
    Vue.component('comp', {
        template: '<div>Hello<slot></slot></div>'
    });
    var vm = new Vue({
        el: '#app'
    });
</script>

当然,把标签内的作为一个整体作为一个slot还是不太自由,可以为slot起名字,根据名字引用

直接slot代表的是所有没有名字的slot

<div id="app">
    <comp>
        <span slot='s1'>World</span>
        <span slot='s2'>!!!</span>
    </comp>
</div>

<script>
    Vue.component('comp', {
        data() {
            return {
                name: " Ming"
            }
        },
        template: '<div>Hello<slot name="s2"></slot><slot name="s1"></slot></div>'
    });
    var vm = new Vue({
        el: '#app'
    });
</script>