1 组件基础

1.1 什么是组件

组件的概念

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。

如何理解组件

简单理解,组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。

通常一个应用会以一棵嵌套的组件树的形式来组织:

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为什么要使用组件

举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。

而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。

<calendar></calendar>

我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。

有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。

 

1.2 创建组件

注册组件

我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件

Vue.component(componentName, {
    //选项
})

对于自定义组件的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。

组件的选项

  • 与创建Vue示例时的选项相同(除了一些根级特有的选项)

  • 一个组件的 data 选项必须是一个函数 (每个组件实例具有自己的作用域,组件复用不会互相影响)

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

组件的使用

<div id="components-demo">
  <button-counter></button-counter>
</div>

组件可以复用

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue组件</title>
</head>
<body>
    <div id="app">
        <h1>{{ title }}</h1>
        <hr>
        <!--组件的使用-->
        <!-- 组件的好处可以将组件当做一个自定义元素重复的复用 -->
        <!-- 注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。 -->    
        <my-button></my-button>
        <my-button></my-button>
        <my-button>小莉莉</my-button>
    </div>

    <script src="../dist/js/vue.js"></script>
    <script>

        //注册组件
        // 注册组件要在创建实例之前进行,向将组件绑定给Vue,传入组件名和对象
        // 组件是可复用的 Vue 实例,且带有一个名字
        // 因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
        Vue.component('my-button', {
            data: function() {      //组件中的data多了一个function(),一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
                return {
                    counter: 0,      //设定初始时的按钮的内显示的值为0
                    // title:'hello',   //会被根组件中的相同数据属性的值覆盖
                    message:[1,2,3,4]
                }
            },
            methods: {
                addCounter() {     //在methods中定义一个方法,目的是让按钮绑定的事件在触发的时候执行该方法,进行加1的操作
                    this.counter ++
                }
            },
            // 字符串模板
            template: `
                <button @click="addCounter">被点了{{ counter }}次</button>
            `,

            // 构造函数会在实例创建完成后立即被调用,在控制台可以显示结果
            created() {
                console.log('啊,我被创建了')   //组件名被复用了三次,所以在控制台可以看到被执行了三次
            }
        })
        

        //创建根实例
        let app = new Vue({
            el: '#app', //根实例,挂载元素是必须的
            //template: `<h2>HELLO WORLD</h2>` //如果指定了 template,挂载元素会被替换(也就是元素id="app"内的所有元素都会被字符串模板元素替换)
            data: {
                title:"Vue组件 同志交友"
            }
        })
    </script>
</body>
</html>

 

组件模板

每个组件模板必须只有一个根元素

模板的形式:

  • template 选项指定字符串 (模板字符串)

  • 单文件组件(.vue)

  • 内联模板 (不推荐)

    <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>
    
  • X-Templates

    <script type="text/x-template" id="hello-world-template">
      <p>Hello hello hello</p>
    </script>
    
    Vue.component('hello-world', {
      template: '#hello-world-template'
    })

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue 组件模板</title>
</head>
<body>
    <div id="app">
        <my-button></my-button>
        <br>

        <!-- 将组件设置为内联模板 -->
        <!-- 当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。 -->
        <my-input inline-template>
            <div>
                <label for="">{{ title }}</label>
                <input type="text">
            </div>
        </my-input>

        <br>

        <my-panel></my-panel>

        <br>

        <my-heading></my-heading>

    </div>
    

    <script type="text/x-template" id="my-panel-template">
        <div class="panel">
            <h2>{{ title }}</h2>
            <p>{{ content }}</p>
        </div>
    </script>
    <script src="../dist/js/vue.js"></script>
    <script>
        
        //注册组件
        //1、字符串模板
        Vue.component('my-button', {
            // 字符串模板-----创建一个按钮
            template: `
                <button>my-button</button>
            `
        });

        //注册组件
        //2、内联模板 不建议使用
        Vue.component('my-input', {
            data: function() {
                return {
                    title:'用户名'
                }
            }
        });

        //注册组件
        //3、x-templates 模板
        // 另一个定义模板的方式是在一个 <script> 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去
        Vue.component('my-panel', {
            data: function(){
                return {
                    title: '我的小莉莉',
                    content: '啊,小莉莉,啊啊啊,啊啊啊'
                }
            },

            template:'#my-panel-template'    //指向一个id,并将定义的属性的值传过去
        });


        //注册组件
        // Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接近编译器。
        Vue.component('my-heading', {
            render: function(createElement) {
                return createElement('h2', 'HELLO');   //创建元素,第一个参数为标签名,第二个参数为标签内的内容
            }
        })

        //创建根实例,并将根实例挂载到一个元素上
        /*let app = new Vue({
        })
        app.$mount('#app')*/
        new Vue({

        }).$mount('#app')

    </script>
</body>
</html>

全局组件和局部组件

使用 Vue.component()注册的组件都是全局组件

我们可以定义局部组件

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

new Vue({
  el: '#app'
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>全局组件和局部组件</title>
</head>
<body>
    <div id="app">
        <h1>全局组件和局部组件</h1>
        <hr>
        
        <!--该组件 就是 根组件的 子组件,虽然是全局组件,应该组件包含根组件挂载的元素内-->
        <my-panel></my-panel>
        
        <!--也是根组件的子组件-->
        <my-button></my-button>


        <my-textarea></my-textarea>

        <my-input></my-input>
    </div>


    <script src="../dist/js/vue.js"></script>
    <script>
        //注册组件
        //1、全局组件  作用域是全局 ,在哪都能用
        // 该组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中
        Vue.component('my-panel', {
            // 字符串模板
            template: `
            <div style="border:1px solid #ccc">
                <h2>my-panel</h2>
                <p>Lorem ipsum dolor sit amet.</p>
                <my-textarea></my-textarea>               //因为my-teatarea是全局组件,所以在哪都可以用  
            </div>
            `,
            // 组件内任然可以定义多个组件
            components: {
                //定义 n 多组件

            }
        });

        // 组件my-panel包含组件my-textarea组件,但my-textarea也是全局组件,即在哪都可以用
        Vue.component('my-textarea', {
            template: `
            <textarea></textarea>
            `
        })


        //定义根实例
        //根组件
        new Vue({
            el:'#app',
            //2、局部组件加s,可以包含多个组件 -----注意局部注册的组件在其子组件中不可用
            // 局部组件是一个对象,key对应的是组件名和字符串模板名,局部组件名可以在挂载的HTML元素中直接当做自定义双标签直接使用
            components: {  //对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
                'my-button': {
                    data:function(){
                        return {
                            counter: 0
                        }
                    },
                    // 字符串模板
                    template: `
                    <button>{{counter}}</button>
                    `
                },

                // 字符串模板---局部组件
                'my-input': {
                    template:`
                    <input type="text" style='background:pink'>
                    `
                }
            }
        })

    </script>
</body>
</html>

2. 组件之间的嵌套使用和互相通信

组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。

每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。

2.1 通过Prop向子组件传递数据

基本使用

在子组件中声明 prop,然后添加一个 message

Vue.component('child', {
  // 声明 props
  props: ['message'],
  // 就像 data 一样,prop 也可以在模板中使用
  // 同样也可以在 vm 实例中通过 this.message 来使用
  template: '<span>{{ message }}</span>'
})

一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。我们能够在组件实例中访问这个值,

然后直接传入值就可以在子组件中使用 message。

<child message="hello!"></child>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件通信</title>
    <link rel="stylesheet" href="../dist/css/bootstrap.css">
</head>
<body>
    <div id="app">
        
    </div>

    <script src="../dist/js/vue.js"></script>
    <script>
        //注册组件
        Vue.component('thumb', {
            data: function(){
                return {}
            },
            //props中定义的属性 根 data中定义的数据 具有相同的地位
            // 数组形式的props
            /*props: ['productSrc', 'productName', 'productDes', 'productPrice'],*/
            /*props: {
                productSrc: String,
                productName: String,
                productDes: String,
                productPrice: Number
            },*/
            /*props: {
                productSrc: {
                    type: String,
                    required: true
                },
                productName: {
                    type:String,
                    default:'8848手机'
                },
                productDes: {
                    type: String,
                    required: true,
                    validator: function(val){
                        return val.length <= 20;
                    }
                },
                productPrice: [String, Number]
            },*/

            // 通常我们希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型:
            //props中定义的属性 根 data中定义的数据 具有相同的地位
            props: {
                product: {
                    type: Object,  //对象
                    required: true
                }
            },

            // 字符串模板----引用了bootstrap.css
            template: `
            <div class="col-xs-6 col-sm-4 col-md-3">
                <div class="thumbnail">
                  <img :src="product.productSrc" alt="...">
                  <div class="caption">
                    <h3>{{ product.productName }}</h3>
                    <p>{{ product.productDes }}</p>
                    <p style='color:red'>{{ product.productPrice }}</p>
                    <p><a href="#" class="btn btn-primary" role="button">直接购买</a> <a href="#" class="btn btn-default" role="button">加入购物车</a></p>
                  </div>
                </div>
            </div>
            `
        });


        //创建根实例 //根组件
        // new Vue({参数1,参数,参数3,..}
        new Vue({
            el:'#app',
            data: {
                // 创建数据
                productList: [
                    {productName:'纯金手机', productSrc:'../dist/images/product01.jpg', productDes:'非常好用的手机,牛逼', productPrice:'998元'},
                    {productName:'纯银手机', productSrc:'../dist/images/product02.jpg', productDes:'非常好用的手机,牛逼', productPrice:'998元'},
                    {productName:'纯铜手机', productSrc:'../dist/images/product03.png', productDes:'非常好用的手机,牛逼', productPrice:'998元'},
                    {productName:'纯铁手机', productSrc:'../dist/images/product04.jpg', productDes:'非常好用的手机,牛逼', productPrice:'998元'},
                ]
            },
            // 字符串模板
            template: `
            <div class="container">
                <div class="page-header">
                    <h1>组件通信</h1>
                                    
                </div>
            
                <div class="row">
                    <thumb v-for='item in productList' :product="item" :key='item.productName'></thumb>
                </div>
            </div>
            `,
            // el:'#app', 挂载元素也可以写在创建实例参数的后面
            
        })
    </script>
</body>
</html>

父组件向子组件通信

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同志加油</title>
</head>
<body>
    <div id="app"></div>
    
    <script type="text/x-template" id="root-template">
        <div class="container">
            <child-component :message="message"></child-component>
        </div>
    </script>

    <script type="text/x-template" id="child-template">
        <div class="box">
            <p>{{ message }}</p>
        </div>
    </script>

    <script src="../dist/js/vue.js"></script>
    <script>
        //定义 注册 
        //组件 注册在Vue 类中
        Vue.component('child-component', {
            props: ['message'], //根data 中的属性 地位相同
            template: '#child-template'
        })


        //根组件 根实例
        let app = new Vue({
            el:'#app',
            data:{
                message:"HELLO 同志",
            },
            template:'#root-template'
        });


        /*
            给类添加东西
            实例化 new 类 a  
            在给类添加东西 
        */
    </script>
</body>
</html>

Prop 的大小写

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

传入一个对象的所有属性

<blog-post v-bind="post"></blog-post>

等价于

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

Prop验证

我们可以为组件的 prop 指定验证要求

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 匹配任何类型)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组且一定会从一个工厂函数返回默认值
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

类型列表:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
  • 自定义的构造函数

2.2 通过事件向父级组件发送消息

on(eventName)+on(eventName)+emit(eventName) 实现通讯

在父组件中使用 on(eventName)监听事件,然后在子组件中使用on(eventName)监听事件,然后在子组件中使用emit(eventName) 触发事件,这样就能实现子组件向父组件传值。

Vue.component('button-counter', {
  template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
      console.log('第'+this.total+'次点击')
    }
  }
})
<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>子组件向父组件通信</title>
    <style>
        p {
            font-size:100px;
            margin: 5px 0px;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>{{ title }}</h1>
        <hr>

        <p>{{ count }}</p>

        <hr>

        <button-count @add="addFn($event)" @del="delFn"></button-count>
    </div>


    <script src="../dist/js/vue.js"></script>
    <script>
        //注册组件
        Vue.component('button-count', {
            methods: {
                addCount() {
                    //向父组件 发送事件
                    this.$emit('add', 2)
                },
                deleteCount(){
                    this.$emit('del', 4)
                }
            },
            template: `
            <div>
                <button @click="addCount()">增加2</button>
                <button @click="deleteCount()">减少4</button>
            </div>
            `,
            created() {
                this.$emit('add', 2)
            }
        })

        //创建根实例 //根组件
        let app = new Vue({
            el: '#app',
            data: {
                title:'同志交友',
                count: 0
            },
            methods: {
                addFn(val){
                    this.count += val;
                },
                delFn(val) {
                    this.count -= val;
                }
            }
        })
    </script>
</body>
</html>

子组件向父组件通信

使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是非常有用的。这时可以使用 $emit 的第二个参数来提供这个值

 incrementCounter: function () {
      this.counter += 1
      this.$emit('increment', this.counter)
 }

然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值

 <button-counter v-on:increment="postFontSize + $event"></button-counter>

或者,如果这个事件处理函数是一个方法:那么这个值将会作为第一个参数传入这个方法:

<button-counter v-on:increment="incrementTotal"></button-counter>

methods: {
  incrementTotal: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

在组件上使用 v-model

组件内input需要满足条件:

  • 将其 value 特性绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

v-model 在组件上的使用

<custom-input v-model="searchText"></custom-input>

<!-- 上面的写法 等价于 下面的写法 -->
<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

3 插槽 slot

3.1 通过插槽分发内容

Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
})
<alert-box>
  Something bad happened.
</alert-box>

·Something bad happened.· 会替换掉 slot标签

3.2 模板中多个插槽

组件模板

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

调用组件

<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <p slot="footer">Here's some contact info</p>
</base-layout>

3.3 插槽默认内容

<button type="submit">
  <slot>Submit</slot>
</button>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件插槽</title>
</head>
<body>
    <div id="app">
        <h1>组件插槽</h1>
        <hr>
        <!-- 组件会被加上字符模板template的样式 -->
        <my-article>HELLO WORLD</my-article>
        <my-article>HELLO 小莉莉</my-article>

        <my-article> 
            <button>按钮</button> 
            <a href="#">超链接</a> 
        </my-article>

        <hr>

        <layout>
            <h1 slot="page-header">同志你好</h1>
            
            <p>Lorem ipsum dolor sit amet.</p>
            <p>Lorem ipsum dolor sit amet.</p>
                
            <p slot="page-footer">footer</p>

            <p>Lorem ipsum dolor sit amet.</p>
            <p>Lorem ipsum dolor sit amet.</p>
        </layout>
    </div>


    <script src="../dist/js/vue.js"></script>
    <script>
        //注册组件 
        Vue.component('my-article', {
            template: `
            <div style='border:1px solid #ccc;width:600px'>
                <p>
                    <slot></slot>                //相当于占位符组件中的内容会直接替换插槽显示
                </p>
            </div>
            `
        });

        //注册组件
        Vue.component('layout', {
            template: `
            <div>
                <header>
                    <slot name="page-header"></slot>       //指定插槽的名字会寻找有该名字的插槽,并将其替换
                </header>
                <main>
                    <slot></slot>      //该插槽没有指定名字,会被没有插槽名字的p元素替换         
                </main>
                <footer>
                    <slot name="page-footer">page-footer默认内容</slot>
                </footer>
            </div>
            `
        })

        //创建根实例
        // 挂载中元素在页面中的显示顺序取决于字符模板中元素的顺序
        new Vue({
            el:'#app'
        })
    </script>
</body>
</html>

4. 动态组件

4.1 实现动态组件

在不同组件之间进行动态切换

<component is="组件名" class="tab"></component>

实现选项卡案例

4.2 在动态组件上使用 keep-alive

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

主要用于保留组件状态或避免重新渲染

<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>动态组件</title>
    <link rel="stylesheet" href="../dist/css/bootstrap.css">
    <style>
        .panel {
            border-top: none;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="container">
            <div class="page-header">
                <h1>动态组件</h1>
            </div>
            <div class="row">
                <div class="col-md-6">
                    <ul class="nav nav-tabs">
                        <!-- 循环数据属性对应的数组内的对象,通过触发事件来显示选项卡表标题下的内容,点击会得到对应的索引值 -->
                        <li v-for="item,index in tabs" :class="{active:isTab(index)}" @click="setTab(index)"><a href="javascript:0">{{ item.tabName }}</a></li>    <!--item.tabNam传入选项卡的标题-->
                    </ul>

                    <div class="panel">
                        <div class="panel-body">
                            <!-- <component is="组件名" class="tab"></component>在不同组件之间进行动态切换 -->
                            <keep-alive>      <!--实现缓存变化频繁是要加上-->
                                <component :is="tabs[tab].tabComponent"></component>    <!--进行组件的动态切换,通过数组索引和点key拿到组件的名字,从而显示组件中的字符模板中的元素内容-->
                            </keep-alive>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="../dist/js/vue.js"></script>
    <script>
        //创建根实例
        // 动态组件的好处,我们添加组件只需要在数组中和组件在中添加字符串模板即可
        new Vue({
            el:'#app',
            data: {
                tabs: [
                        {'tabName':'登录', 'tabComponent':'login-component'},
                        {'tabName':'注册', 'tabComponent':'register-component'},
                        {'tabName':'免登录', 'tabComponent':'no-login-component'},
                        {'tabName':'商品列表', 'tabComponent':'product-component'},
                        {'tabName':'未付款商品', 'tabComponent':'no-pay-component'},
                        {'tabName':'已付款商品', 'tabComponent':'payed-component'},
                      ],
                tab: 0      //默认是显示登录选项卡中的内容
            },

            methods: {
                isTab(index) {
                    return this.tab === index;   //通过返回布尔值的真假来决定class类知否显示
                    alert('OK')
                },
                setTab(index) {
                    this.tab = index;    
                }
            },

            // 选项卡标题下对应的组件内容
            components: {
                'login-component': {
                    template: `
                    <form action="#">
                        <div class="form-group">
                            <label for="#">用户名</label>
                            <input type="text" class="form-control" />
                        </div>
                        <div class="form-group">
                            <label for="#">密码</label>
                            <input type="password" class="form-control" />
                        </div>
                        <button class="btn btn-default btn-block">登录</button>
                    </form>
                    `
                },
                'register-component': {
                    template: `
                    <form action="#">
                        <div class="form-group">
                            <label for="#">用户名</label>
                            <input type="text" class="form-control" />
                        </div>
                        <div class="form-group">
                            <label for="#">密码</label>
                            <input type="password" class="form-control" />
                        </div>
                        <div class="form-group">
                            <label for="#">确认密码</label>
                            <input type="password" class="form-control" />
                        </div>
                        <button class="btn btn-primary btn-block">注册</button>
                    </form>
                    `
                },
                'no-login-component': {
                    template:`
                    <div>
                        我是李刚,我不用登录,我牛逼
                    </div>
                    `
                },

                'product-component': {
                    template:`
                    <div>
                        苹果手机<br>mac笔记本<br>香奈儿<br>兰博基尼<br>特斯拉<br>阿尼玛
                    </div>
                    `
                },


                'no-pay-component': {
                    template:`
                    <div>
                        华为手机<br>mac笔记本<br>香奈儿<br>路虎<br>特斯拉<br>秋裤
                    </div>
                    `
                },


                'payed-component': {
                    template:`
                    <div>
                        苹果手机<br>mac-pro笔记本<br>瑞士手表<br>宾利<br>特斯拉<br>林肯加长版
                    </div>
                    `
                }
            }
        })
    </script>
</body>
</html>

动态组件应用:选项卡

4.3 绑定组件选项对象

动态组件可以绑定 组件选项对象(有component属性的对象),而不是已注册组件名的示例

var tabs = [
  {
    name: 'Home', 
    component: { 
      template: '<div>Home component</div>' 
    }
  },
  {
    name: 'Posts',
    component: {
      template: '<div>Posts component</div>'
    }
  },
  {
    name: 'Archive',
    component: {
      template: '<div>Archive component</div>',
    }
  }
]

new Vue({
  el: '#dynamic-component-demo',
  data: {
      tabs: tabs,
    currentTab: tabs[0]
  }
})
<component
    v-bind:is="currentTab.component"
    class="tab"
 >
</component>

5 组件的其他特性

5.1 解析 DOM 模板时的注意事项

有些 HTML 元素,诸如 <ul><ol><table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr> 和 <option>,只能出现在其它某些特定的元素内部。

<table>
  <blog-post-row></blog-post-row>
</table>

上面的写法,渲染效果会不甚理想,可以采用以下写法

<table>
  <tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:

  • 字符串 (例如:template: ‘…’)
  • 单文件组件 (.vue)
  • <script type="text/x-template">

5.2 Prop的一些问题

Prop的属性名问题

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

如果你使用字符串模板,那么这个限制就不存在了。

非Prop属性

组件上定义的非Prop属性 会传递到 组件模板的根元素上

class 和 style 特性会非常智能,即两边的值会被合并起来

对prop重新赋值

子组件中,对prop重新赋值,会报警告

5.3 组件事件的相关问题

将原生事件绑定到组件

想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符

<base-input v-on:focus.native="onFocus"></base-input>

.sync 修饰符

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”

推荐以 update:my-prop-name 的模式触发事件

//子组件中
this.$emit('update:title', newTitle)
<!-- 上级组件 模板中 -->
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

以上写法可以换成下列写法

<text-document v-bind:title.sync="doc.title"></text-document>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue组件注意事项</title>
</head>
<body>
    <div id="app">
        <h1>组件</h1>
        <hr>

        <table border="1" width="600">
            <div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A explicabo minima labore nam earum odit asperiores atque, debitis distinctio praesentium reprehenderit, voluptate rerum beatae molestiae dolorum, quis eaque excepturi nemo.</div>
            <tr is="table-row"></tr>
            <tr is="table-row"></tr>
            <tr is="table-row"></tr>
        </table>
        <hr>
        
        <!--给组件 指定 class和style 会继承到 组件模板的根元素上-->
        <my-button style="border:1px solid green" @click.native="clickFn"></my-button>

        <!--实现prop的双向数据绑定-->

        <hr>
        <my-input :msg="message" @update:msg='message = $event'></my-input>
        
        <my-input :msg.sync="message"></my-input>
    
        <h2>{{message}}</h2>
        
        <my-component @update="fn">
    </div>
    
    <script src="../dist/js/vue.js"></script>
    <script>
        

        new Vue({
            el:'#app',
            data: {
                message:'HELLO'
            },
            methods: {
                clickFn(){
                    alert('OK')
                },
                fn(n1,n2,n3){

                }
            },
            components: {
                'table-row': {
                    template: `
                    <tr><td>1</td><td>1</td><td>1</td></tr>
                    `
                },
                'my-button': {
                    template: `
                    <div>          //div是组件模板的根元素
                        <button style='color:red'>按钮</button>
                        <button style='color:red'>按钮</button>
                    </div>
                    `
                },
                'my-input': {
                    props: ['msg','title'],
                    template: `
                    <div>
                        <label for="#">{{ msg }}</label>
                        <input type="text" v-model="msg">
                    </div>
                    `,
                    watch: {
                        msg(){
                            this.$emit('update:msg', this.msg)
                        },
                        title(){
                            this.$emit('update:title', this.title)
                        }
                    }

                }
            },

        })
    </script>
</body>
</html>

Vue组件注意事项

5.4 官方文档-组件

深入了解组件

网址: https://cn.vuejs.org/v2/guide/components-registration.html

版权声明:本文为sui776265233原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/sui776265233/p/9534708.html