Vue.js-组件 原

使用组件

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>

1、<app>组件不知道它会收到什么内容。这是由<app>的父组件决定的 2、<app>组件很可能有它自己的模板 为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板,这个过程被称为内容分发 Vue.js实现了一个内容分发API,使用特殊的<slot>元素作为原始内容的插槽

编译作用域 在深入内容分发API之前,我们先明确内容在哪个作用域里编译,假定模板为: <child-component>{{message}}</child-component> message应该绑定到父组件的数据 组件作用域简单的说是: 父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译

(1)单个slot 除非子组件的模板包含至少一个<slot>插口,否则父组件的内容将会被丢弃。当子组件的模板只有一个没有属性的slot时,父组件整个内容片段将插入到slot所在的DOM位置,并替换掉slot标签本身 最初在<slot>标签中的任何内容都被视为备用内容,备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容才显示备用内容

<body class="">
    <div id="example">
        <h2>父组件标题</h2>
        <my-component>
            <p>这是一些初始内容</p>
            <p>这是另一些初始内容</p>
        </my-component>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("my-component", {
        template: `
          <div>
          <h2>子组件标题</h2>
          <slot>没有分发内容时显示</slot>
          </div>
        `
    })
    var app = new Vue({
        el: "#example",
    })
    //子模板要用div包裹
    </script>
</body>

最后渲染为

<div>
  <h1>我是父组件的标题</h1>
  <div>
    <h2>我是子组件的标题</h2>
    <p>这是一些初始内容</p>
    <p>这是更多的初始内容</p>
  </div>
</div>

(2)具名Slot <slot>元素可以用一个特殊的属性name来配合如何分发内容。多个slot可以有不同的名字,具名slot将匹配内容片段中有对应slot特性的元素,仍然可以有一个匿名slot,它是默认slot,作为找不到匹配的内容片段的备用插槽,如果没有默认的slot,这些找不到匹配的内容

<body class="">
    <div id="example">
        <my-component>
            <h2 slot="header">这是一个标题</h2>
            <h2 slot="footer">这是底部</h2>
            <h3>其它内容</h3>
        </my-component>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("my-component", {
        template: `
         <div class="container">
          <header>
          <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
          <slot name="footer"></slot>
          </footer>
         </div>
        `
    })
    var app = new Vue({
        el: "#example",
    })
    //子模板要用div包裹,子组件的slot 的name属性与父组件slot属性对应
    </script>
</body>

(3)作用域插槽 2.1.0新增 作用域插槽是一种特殊类型的插槽,用作使用一个(能够传递数据到)可重用模板替换已渲染元素 在子组件中,只需将数据传递到插槽,就像你将props传递给组件一样

<body class="">
    <div id="example">
        <div class="parent">
            <child>
                <template scope="props">
                    <span>this is from parent</span>
                    <br>
                    <span>{{props.text}}</span>
                </template>
            </child>
        </div>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("child", {
        template: `
         <div class="child">
            <div>child div</div>
            <slot text="hello from child"></slot>
         </div>
        `
    })
    var app = new Vue({
        el: "#example",
    })
    //可以通过模板中的scope属性获得子组件里面的text的值,父模板与子组件的正常HTML内容都会保留
    </script>
</body>

最后解析成下面 

div id="example">
        <div class="parent">
            <div class="child">
                <div>child div</div>
                <span>this is from parent</span>
                <br> 
                <span>hello from child</span></div>
        </div>
</div>

作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表的每一项

<body class="">
    <div id="example">
        <div class="parent">
            <child>
                <template slot="item" scope="props">
                    <li>{{props.tex}}</li>
                </template>
            </child>
        </div>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("child", {
        template: `
         <ul>
         <slot name="item" v-for="item in items" v-bind:tex="item.text"></slot>
         </ul>
        `,
        data: function() {
            return {
                items: [
                    { text: "text1" },
                    { text: "text2" },
                    { text: "text3" },
                ]
            }
        }
    })
    var app = new Vue({
        el: "#example",
    })
    
    </script>
</body>

动态组件 通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,我们让多个组件可以使用同一个挂载点,并动态切换:

var vm = new Vue({   el: '#example',   data: {     currentView: 'home'   },   components: {     home: { /* ... */ },     posts: { /* ... */ },     archive: { /* ... */ }   } })

<component v-bind:is="currentView">   <!-- 组件在 vm.currentview 变化时改变! --> </component>

也可以直接绑定到组件对象上: var Home = {   template: '<p>Welcome home!</p>' } var vm = new Vue({   el: '#example',   data: {     currentView: Home   } })

keep-alive 如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染,为此可以添加一个keep-alive指令参数 <keep-alive>   <component :is="currentView">     <!-- 非活动组件将被缓存! -->   </component> </keep-alive>

编写可复用的组件 Vue组件的API来自三部分-props,events 和slots; Props允许外部环境传递数据给组件 Events允许从外部环境在组件内触发自定义事件 Slots允许外部环境将额外的内容组合在组件中

内联模板 如果子组件有inline-template特性,组件将把它的内容当做它的模板,而不是把它当作分发内容,这让模板更灵活

<body class="">
    <div id="example">
        <my-component inline-template>
            <div>
                <p>These are compiled as the component's own template.</p>
                <p>Not parent's transclusion content.</p>
            </div>
        </my-component>
    </div>
    <script src="js/vue.js"></script>
    <script>
    Vue.component("my-component",{
        
    })
    var app = new Vue({
        el: "#example",
    })
    </script>
</body>

渲染成
<div id="example">
        <div>
            <p>These are compiled as the component's own template.</p>
            <p>Not parent's transclusion content.</p>
        </div>
</div>

X-Templates 另一种定义模板的方式是在JavaScript标签里使用text/x-template类型,并且指定一个ID.例如 <script type="text/x-template" id="hello-world-template">   <p>hello </p> </script> Vue.component("hello-world",{ template:"#hello-world-template" }) 这在有很多模板或者小的应用中有用,否则应该避免使用,因为它将模板和组件的其他定义隔离了

对低开销的静态组件使用v-once 尽管在Vue中渲染HTML很快,不过当组件中包含大量静态内容时,可以考虑使用v-once将渲染结果缓存起来,就像这样 Vue.component('terms-of-service', {   template: '\     <div v-once>\       <h1>Terms of Service</h1>\       ... a lot of static content ...\     </div>\   ' })

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券