文档章节

Vue.js-组件

tianyawhl
 tianyawhl
发布于 2017/09/01 11:40
字数 3957
阅读 16
收藏 0

使用组件

1、注册

之前说过,我们可以通过以下方式创建一个Vue实例
new Vue({
  el: '#some-element',
  // 选项
})

(1)要注册一个全局组件,你可以使用Vue.component(tagName,options) 例如:
Vue.component("my-component",{
//选项
})

对应自定义标签名,Vue.js不强制要求遵循W3C规则

组件在注册之后,便可以在父实例的模块中以自定义元素
<my-component></my-component>的形式使用。要确保在初始化根实例之前注册了组件

<body class="">
    <div id="example-1">
        <my-component></my-component>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("my-component", {
        template: `<div>a custom component</div>`
    })
    var app = new Vue({
        el: "#example-1",
    })
    </script>
</body>
渲染为
<div id="example">
  <div>A custom component!</div>
</div>

(2)局部注册
不必在全局注册每个组件,通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用

<body class="">
    <div id="example-1">
        <my-component></my-component>
    </div>
    <script src="js/vue.js"></script>
    <script>
    var child = {
        template: `<div>aa custom component</div>`
    }
    var app = new Vue({
        el: "#example-1",
        components: {
            //<my-component> 将只在父模板可用
            "my-component": child
        }
    })
    </script>
</body>

这种封装也适用于其它可注册的Vue功能,如指令

2、is属性

当使用DOM作为模板时,你会受到HTML的一些限制,因为Vue只有在浏览器解析和标准化HTML后才能获取模板内容,尤其像一些元素<ul>,<ol>,<table>,<select>限制了能被它包裹的元素,而一些像option这样的元素只能出现在某些其它元素内部,在自定义组件中使用这些受限制的元素时会导致一些问题,例如
<table>
  <my-row>...</my-row>
</table>
在渲染的时候会导致错误。变通的方法是使用特殊的is属性,如下例子:

<body class="">
    <div id="example-1">
        <table>
            <tr is="my-row"></tr>
        </table>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("my-row", {
        template: `<tr><td>11</td></tr> `
    })
    var app = new Vue({
        el: "#example-1"
    })
    </script>
</body>

注意如果您使用来自以下来源之一的字符串模板,这些限制将不适用
1、<script type="text/x-template">
<!DOCTYPE html>
<html>
    <body>
        <div id="app">
            <my-component></my-component>
        </div>

        <-- 注意:使用<script>标签时,type指定为text/x-template,意在告诉浏览器这不是一段js脚本,浏览器在解析HTML文档时会忽略<script>标签内定义的内容。-->

        <script type="text/x-template" id="myComponent">//注意 type 和id。
            <div>This is a component!</div>
        </script>
    </body>
    <script src="js/vue.js"></script>
    <script>
        //全局注册组件
        Vue.component('my-component',{
            template: '#myComponent'
        })

        new Vue({
            el: '#app'
        })

    </script>
</html>

2、JavaScript 内联模版字符串
        <template id="myComponent">
            <div>This is a component!</div>
        </template>

3、.vue 组件,创建.vue后缀的文件,如组件Hello.vue,放到components文件夹中,在使用的页面进行引用

3、data必须是函数

通过Vue构造器传入的各种选项大多数可以在组件里用,data是个例外,它必须是函数

<body class="">
    <div id="example-1">
        <simple-counter></simple-counter>
        <simple-counter></simple-counter>
        <simple-counter></simple-counter>
    </div>
    <script src="js/vue.js"></script>
    <script>
    var data={counter:0}
    Vue.component("simple-counter",{
        template:`
        <button v-on:click="counter+=1">{{counter}}</button>
        `,
        data:function(){
            return data
        }
    })
    var app = new Vue({
        el: "#example-1"
    })
    </script>
</body>

由于这3个组件共享了同一个data,因此增加一个counter会影响所有组件,我们可以通过为每个组件返回全新的data对象来解决这个问题

<body class="">
    <div id="example-1">
        <simple-counter></simple-counter>
        <simple-counter></simple-counter>
        <simple-counter></simple-counter>
    </div>
    <script src="js/vue.js"></script>
    <script>
    
    Vue.component("simple-counter",{
        template:`
        <button v-on:click="counter+=1">{{counter}}</button>
        `,
        data:function(){
            return {
                counter:0
            }
        }
    })
    var app = new Vue({
        el: "#example-1"
    })
    </script>
</body>

现在每个counter都有它自己内部的状态了

4、构成组件

组件意味着协同工作,通常父子组件会是这样的关系:组件A在它的模板中使用了组件B。它们之间必须需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件,然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性
在Vue中,父子组件的关系可以总结为props down,events up.父组件通过props向下传递数据给子组件,子组件通过events给父组件发送信息

使用Prop传递数据
组件实例的作用域是孤立的,这意味着不能(也不应该)在子组件的模板内直接引用父组件的数据,要让子组件使用父组件的数据,需要通过子组件的props选项,子组件要显式的用props选项声明它期待获得的数据

<body class="">
    <div id="example-1">
        <message v-bind:message="data"></message>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("message", {
        props: ["message"],
        template: `
        <span>{{message}}</span>
        `,
    })
    var app = new Vue({
        el: "#example-1",
        data: {
            data: "hello props"
        }
    })
    </script>
</body>

camelCase vs.kebab-case
HTML特性是不区分大小写的,所以,当使用的不是字符串模板,camelCased(驼峰式)命名的prop需要父组件属性名需要转换为相应的kebab-case(短横线隔开式)命名

<body class="">
    <div id="example-1">
        <message v-bind:my-message="data"></message>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("message", {
        props: ["myMessage"],
        template: `
        <span>{{myMessage}}</span>
        `,
    })
    var app = new Vue({
        el: "#example-1",
        data: {
            data: "hello props MY MESSAGE"
        }
    })
    </script>
</body>

如果你使用字符串模板,则没有这些限制
字面量语法与动态语法
初学者常犯的一个错误是使用字面量语法传递数值
//传递了一个字符串“1”
<comp some-prop="1"></comp>
因为它是一个字面prop,它的值是字符串“1”,而不是number,如果想传递一个实际的number,需要使用v-bind从而让它的值被当作JavaScript表达式计算
<!-- 传递实际的 number -->
<comp v-bind:some-prop="1"></comp>

单向数据流
prop是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来,另外每次父组件更新时,子组件的所有prop都会更新为最新值,这意味着你不应该在子组件内部改变prop.如果你这么做了,Vue会在控制台给出警告
为什么我们会有修改prop中的数据的冲动呢,通常有2种原因
1、prop作为初始值传入后,子组件想把它当做局部数据来用
2、prop作为初始值传入,由子组件处理成其它数据输出
对于这2种原因,正确的应对方式是:
(1)定义一个局部变量,并用prop的值初始化它

<body class="">
    <div id="example-1">
        <message v-bind:message="initialCounter"></message>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("message", {
        props: ["message"],
        data: function() {
            return { counter: this.message }
        },
        template: `
        <span>{{counter}}</span>
        `,
    })
    var app = new Vue({
        el: "#example-1",
        data: {
            initialCounter: 1
        }
    })
    </script>
</body>

(2)定义一个计算属性,处理prop的值并返回

<body class="">
    <div id="example-1">
        <message v-bind:message="initialCounter"></message>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("message", {
        props: ["message"],
        computed:{
            newmessage:function(){
                return this.message.trim().toLowerCase()
            }
        },
        template: `
        <span>{{newmessage}}</span>
        `,
    })
    var app = new Vue({
        el: "#example-1",
        data: {
            initialCounter: "THIS IS THE SINGLE DATA SAMPLE"
        }
    })
    </script>
</body>

注意在JavaScript中对象和数组是引用类型,指向同一个内存空间,如果prop是一个对象或数组,在子组件内部改变它会影响父组件的状态

自定义事件
我们知道,父组件是使用props传递数据给子组件,但子组件怎么跟父组件通信呢,这个时候Vue的自定义事件系统就派上用场了,使用v-on绑定自定义事件
每个Vue实例都实现了事件接口(Events interface)即使用$on(eventName)监听事件
使用$emit(eventName)触发事件
Vue的事件系统分离自浏览器的EventTarget API尽管它们的运行类似,但是$on ,$emit不是addEventListener和dispatchEvent的别名
另外,父组件可以在使用子组件的地方直接用v-on来监听子组件触发的事件
不能用$on侦听子组件释放的事件,而必须在模板里直接使用v-on绑定,就像以下的例子

<body class="">
    <div id="example">
        <div>{{total}}</div>
        <my-component v-on:crement="crementTotal"></my-component>
        <my-component v-on:crement="crementTotal"></my-component>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("my-component", {
        template: `
           <button v-on:click="incrementCounter">{{counter}}</button>
        `,
        data: function() {
            return {
                counter: 0
            }
        },
        methods: {
            incrementCounter: function() {
                this.counter += 1
                this.$emit("crement")

            }
        }
    })
    var app = new Vue({
        el: "#example",
        data: {
            total: 0
        },
        methods: {
            crementTotal: function() {
                this.total += 1
            }
        }
    })
    </script>
</body>

自定义事件不能用驼峰来命名
本例中,子组件已经和它的外部完全解耦,链接桥梁是自定义事件crement

给组件绑定原生事件
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用.native修饰v-on 例如
<my-component v-on:click.native="doTheThing"></my-component>

在一些情况下,我们可能需要对一个prop进行双向绑定,事实上这正是Vue1.x中的,由于破坏了单向数据流的假设,我们在2.0版本中移除.sync
但在实际应用中.sync还是有其适用之处,从2.3.0起我们重新引用了.sync 修饰符
完整实例代码如下:

<body class="">
    <div id="example">
        <div>{{bar}}</div>
        <comp v-bind:foo.sync="bar"></comp>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("comp", {
        props: ["foo"],
        template: `
        <button v-on:click="changeMessage">{{foo}}</button>
        `,
        methods: {
            changeMessage: function() {
                this.$emit('update:foo', "changedM")
            }
        }
    })
    var app = new Vue({
        el: "#example",
        data: {
            bar: "initial message"
        }
    })
    </script>
</body>

非父子组件通信
如果2个组件不是父子组件,也需要通信,在简单的场景下,可以使用一个空的Vue实例作为中央事件总线

<body class="">
    <div id="app">
        <c1></c1>
        <c2></c2>
    </div>
    <script src="js/vue.js"></script>
    <script>
    var Bus = new Vue();
    Vue.component('c1', {
        template: '<div>{{msg}}</div>',
        data: function() {
            return { msg: 'Hello World!' }
        },
        created() {
            var self = this
            Bus.$on('setMsg', function(content) {
                self.msg = content;
            });
        },
    });
    Vue.component('c2', {
        template: '<button @click="sendEvent">Say Hi</button>',
        methods: {
            sendEvent() {
                Bus.$emit('setMsg', 'Hi Vue!!');
            }
        }
    });
    var app = new Vue({
        el: '#app'
    })
    </script>
</body>

在复杂的情况下,我们应该考虑使用专门的状态管理模式

5、使用Slot分发内容

在使用组件时,我们常常要像这样组合它们
<app>
  <app-header></app-header>
  <app-footer></app-footer>
</app><