Vue.js-组件
Vue.js-组件
tianyawhl 发表于6个月前
Vue.js-组件
  • 发表于 6个月前
  • 阅读 13
  • 收藏 0
  • 点赞 0
  • 评论 0

新睿云服务器60天免费使用,快来体验!>>>   

使用组件

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>\
  '
})

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 1
博文 178
码字总数 73283
×
tianyawhl
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: