• vue组件化开发


    • 组件化开发思想
    • 组件注册
    • Vue调试工具用法
    • 组件间数据交互
    • 组件插槽
    • 基于组件的案例

    1. 组件化开发思想

    • 标准:这些组件要想组合到一块,肯定要有统一的标准
    • 分治:将不同的功能封装到不同的组件中,这样的话每个组件都可以进行独立的生产
    • 重用:如果一个项目中某些组件已经不能使用了,这时只需要换一个组件即可
    • 组合:组件之间的组合可以形成一个独立的产品

    ① 编程中的组件化思想体现

    组件化开发的核心思想其实就是把不同的功能封装到不同的组件中,然后组件可以通过组合的方式形成一个完整的应用。

    上图中每一块都可以看作是一个独立的组件,组件与组件之间是有一定的关系的,比如兄弟关系、父子关系

    ② 组件化规范: Web Components

    • 我们希望尽可能多的重用代码
    • 自定义组件的方式不太容易(html、 css和js)
    • 多次使用组件可能导致冲突

    Web Components 通过创建封装好功能的定制元素(自定义元素)解决上述问题。Vue部分实现了上述规范

    官网:https://developer.mozilla.org/zh-CN/docs/Web/Web_Components 

    2. 组件注册

    ① 全局组件注册语法

    Vue.component(组件名称, {
        data: 组件数据,
        template: 组件模板内容
    })

    例如:

    // 注册一个名为 button-counter 的新组件
    Vue.component('button-counter', {
        data: function () {
            return {
                count: 0
            }
        },
        // template: '<button v-on:click="count++">点击了{{ count }}次.</button>'
        template: '<button @click="handle">点击了{{ count }}次.</button>',
        methods: {
            handle: function() {
                this.count += 2;
            }
        }
    })

    ② 组件用法

    <div id="app">
        <button-counter></button-counter>
    </div>
    <div id="app">
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
    </div>

    注意:每个组件都是独立的,所以每一个button里面的数据都是独立的,相互不影响。

    ③ 组件注册注意事项

    1)data必须是一个函数:data使用函数的话,会形成一个闭包的环境,这样会保证每一个组件都是拥有一份独立的数据。

    注意:注册组件中的data是一个函数,但是注册普通的vue对象里面的data只是一个对象

    /* 注册组件 */
    Vue.component('test', {
        data: function() {
            return {
                count1: 0
            }
        }
    })
    /* 注册普通的vue对象 */
    var vm = new Vue({
        el: '#app',
        data: {
            count2: 0
        }
    })

    2)组件模板内容必须是单个根元素:template里面必须要有一个根元素,如果是只有兄弟元素则会报错。

    /* 没有根元素,只有兄弟节点会报错,最外层必须要包裹一个父元素 */
    template: `
        <button>测试1</button>
        <button>测试2</button>
    `

    3)组件模板内容可以是模板字符串:模板字符串需要浏览器提供支持(ES6语法) 

    注意:模板字符串是使用反引号包裹,可以提高代码的可读性。

    4)组件命名方式

    • 短横线方式
    Vue.component('my-component', { /* ... */ })
    • 驼峰方式
    Vue.component('MyComponent', { /* ... */ })

    注意:如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中使用驼峰的方式使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件

    /* 使用驼峰式命名法创建了一个HelloWorld组件 */
    Vue.component('HelloWorld', {
        // 相应的逻辑代码
    });
    
    /* 在其他的组件的字符串模板中使用HelloWorld组件 */
    Vue.component('test', {
        // 相应的逻辑代码
        // 在该test组件中使用HelloWorld组件
        template: `
            <HelloWorld></HelloWorld>
        `
    });
    <!-- 在普通的标签中使用HelloWorld组件,必须使用短横线的方式 -->
    <div id="app">
        <hello-world></hello-world>
    </div>

    注意:上面的注册方式都是全局组件,在哪里都可以使用:普通的标签模板中或者是其他的组件的字符串模板中

    ④ 局部组件注册

    var ComponentA = { /* ... */ }
    var ComponentB = { /* ... */ }
    var ComponentC = { /* ... */ }
    new Vue({
        el: '#app'
        components: {
            'component-a': ComponentA,
            'component-b': ComponentB,
            'component-c': ComponentC,
        }
    })

    注意:局部注册的组件只能在注册它的父组件中使用,全局注册的组件不能使用局部组件

    3. Vue调试工具用法

    安装vue扩展程序:https://www.cnblogs.com/aisowe/p/11580623.html

    如果安装出错的话试一下另一种方法:https://blog.csdn.net/yizufengdou/article/details/103985709

    4. 组件间数据交互

    ① 父组件向子组件传值

    1)组件内部通过props接收传递过来的值

    Vue.component('menu-item', {
        props: ['title'],
        template: '<div>{{ title }}</div>'
    })

    2)父组件通过属性将值传递给子组件 

    <!-- 静态绑定title属性 -->
    <menu-item title="来自父组件的数据"></menu-item>
    <!-- 动态绑定title属性 -->
    <menu-item :title="title"></menu-item>

    例如下面这段完整的代码:

    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item title='来自父组件的值'></menu-item>
        <menu-item :title='ptitle' content='hello'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
          父组件向子组件传值-基本使用
        */
        Vue.component('menu-item', {
          props: ['title', 'content'],
          data: function() {
            return {
              msg: '子组件本身的数据'
            }
          },
          template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
        });
        var vm = new Vue({
          el: '#app',
          data: {
            pmsg: '父组件中内容',
            ptitle: '动态绑定属性'
          }
        });
    </script>

    3)props属性名规则

    • 在props中使用驼峰形式,模板中需要使用短横线的形式。原因是DOM中的元素是不区分大小写的
    • 字符串形式的模板中没有这个限制。 在字符串形式的模板和props属性中,我们可以使用驼峰的形式来命名,在组件的内部也使用驼峰的形式来接收。但是不能在普通的标签中不能使用驼峰式,必须使用短横线的方式。
    Vue.component('menu-item', {
        // 在 JavaScript 中是驼峰式的
        props: ['menuTitle'],
        template: '<div>{{ menuTitle }}</div>'
    })
    <!-- 在html中是短横线方式的 -->
    <menu-item menu-title="nihao"></menu-item>

    4)props属性值类型

    • 字符串 String
    • 数值 Number
    • 布尔值 Boolean
    • 数组 Array
    • 对象 Object

    注意:数值类型和布尔类型必须要用v-bind绑定,否则是字符串类型

    <menu-item :pnum='12' :pboo='true'></menu-item>

    props传递数据原则:单向数据流。即只允许父组件向子组件传递数据,而不允许子组件直接操作props中的数据。虽然并没有禁止子组件操作父组件传递过来的数据,但是不推荐这样做。

    例如下面这段代码:子组件接收父组件传递过来的parr属性后,点击按钮后页面中可以添加相应的数据,但是不推荐子组件直接操作父组件传递过来的数据。

    Vue.component('menu-item', {
        props: ['parr'],
        template: `
            <div>
                <ul>
                    <li :key='index' v-for='(item, index) in parr'>{{item}}</li>
                    <button @click='parr.push("lemon")'>点击</button>
                </ul>
            </div>
        `
    });
    var vm = new Vue({
        el: '#app',
        data: {
            pmsg: '父组件中的内容'
            parr: ['apple', 'orange', 'banana']
        }
    })

    那如何解决呢?只能是子组件传值给父组件,然后让父组件做相应的操作。

    ② 子组件向父组件传值

    1)子组件通过自定义事件向父组件传递信息

    注意:$emit这个方法名是固定的,并且需要携带一个参数,参数名称就是自定义事件

    <button v-on:click='$emit("enlarge-text") '>扩大字体</button>

    2)父组件监听子组件的事件

    <menu-item v-on:enlarge-text='fontSize += 0.1'></menu-item>

    例如下面这段代码:

    <div id="app">
        <menu-item @enlarge-text='handle'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
          子组件向父组件传值-基本用法
          props传递数据原则:单向数据流
        */
        Vue.component('menu-item', {
          template: `
            <button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button>
          `
        });
        var vm = new Vue({
          el: '#app',
          data: {
            fontSize: 10
          },
          methods: {
            handle: function(){
              // 扩大字体大小
              this.fontSize += 5;
            }
          }
        });
    </script>

    3)子组件通过自定义事件向父组件传递信息

    <button v-on:click='$emit("enlarge-text", 0.1) '>扩大字体</button>

    4)父组件监听子组件的事件

    注意:$event是固定的,表示真实传递过来的值

    <menu-item v-on:enlarge-text='fontSize += $event'></menu-item>

    例如下面这段代码(携带参数):

    <div id="app">
        <menu-item @enlarge-text='handle($event)'></menu-item>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
          子组件向父组件传值-基本用法
          props传递数据原则:单向数据流
        */
        Vue.component('menu-item', {
          template: `
            <button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
          `
        });
        var vm = new Vue({
          el: '#app',
          data: {
            fontSize: 10
          },
          methods: {
            handle: function(val){
              // 扩大字体大小
              this.fontSize += val;
            }
          }
        });
    </script>

    ③ 兄弟之间传值(非父子组件间传值 ):

    兄弟之间传值不能直接通信,需要一个事件中心做中转

    1)单独的事件中心管理组件间的通信

    var eventHub = new Vue()

    2)监听事件与销毁事件

    /* 监听事件 */
    eventHub.$on('add-todo', addTodo)
    /* 销毁事件 */
    eventHub.$off('add-todo')

    3)触发事件

    eventHub.$emit('add-todo', id)

    例如下面这段代码所实现的功能:点击TOM的按钮,JERRY会加2;点击JERRY的按钮,TOM会加1

    <div id="app">
        <div>父组件</div>
        <div>
          <button @click='handle'>销毁事件</button>
        </div>
        <test-tom></test-tom>
        <test-jerry></test-jerry>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
          兄弟组件之间数据传递
        */
        // 提供事件中心
        var hub = new Vue();
    
        Vue.component('test-tom', {
          data: function(){
            return {
              num: 0
            }
          },
          template: `
            <div>
              <div>TOM:{{num}}</div>
              <div>
                <button @click='handle'>点击</button>
              </div>
            </div>
          `,
          methods: {
            handle: function(){
          // 触发Jerry-event事件 hub.$emit(
    'jerry-event', 2); } }, mounted: function() { // 监听事件 hub.$on('tom-event', (val) => { this.num += val; }); } }); Vue.component('test-jerry', { data: function(){ return { num: 0 } }, template: ` <div> <div>JERRY:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ // 触发兄弟组件的事件 hub.$emit('tom-event', 1); } }, mounted: function() { // 监听事件 hub.$on('jerry-event', (val) => { this.num += val; }); } }); var vm = new Vue({ el: '#app', data: { }, methods: { handle: function(){ hub.$off('tom-event'); hub.$off('jerry-event'); } } }); </script>

    5. 组件插槽

    注意:组件插槽传递的内容指的是模板的内容,前面分析的是数据data的交互

    ① 组件插槽的作用:父组件向子组件传递内容 

    ② 组件插槽基本用法

    1)插槽位置

    注意:slot名字是固定写法

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

    2)插槽内容

    <alert-box>Something bad happened.</alert-box>

    插槽的内容是从组件标签的中间传递过来的。

    <slot>默认内容</slot>

    注意:如果组件标签中没有传递内容,那么slot中的内容会被默认显示,如果传递内容了,则会覆盖slot中的内容。

    ③ 具名插槽用法

    1)插槽定义

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

    2)插槽内容:根据名称来匹配,没有名称的匹配给默认的插槽。

    第一种用法:

    <base-layout>
        <h1 slot="header">标题内容</h1>
        <p>主要内容1</p>
        <p>主要内容2</p>
        <p slot="footer">底部内容</p>
    </base-layout>

    第二种用法:

    <base-layout>
        <template slot="header">
            <h1>标题内容1</h1>
            <h1>标题内容2</h1>
        </template>
        <p>主要内容1</p>
        <p>主要内容2</p>
        <template slot="footer">
            <p>底部内容1</p>
            <p>底部内容2</p>
        </template>
    </base-layout>

    可以看到header中有两个p标签,但是并没有包含template标签。

    template标签的作用只是临时性的包裹中间的内容,它最终并不会渲染到页面上。template名称是固定的,用于包裹多个标签。

    应用场景是:将多条文本填充到一个插槽中。如果是把插槽名称标注到标签上的话,则只能标注一个标签。

    ④ 作用域插槽

    应用场景:父组件对子组件的内容进行加工处理

    1)插槽定义

    <ul>
        <li v-for= "item in list" v-bind:key= "item.id" >
            <slot v-bind:item="item">
                {{item.name}}
            </slot>
        </li>
    </ul>

    2)插槽内容

    在父组件中可以获取到子组件的数据,并且对这个数据进行一些加工和处理。获取数据的方式就是通过slot-scope这个属性获取,获取到的值就是在子组件中的slot属性中绑定的。注意,slotProps这个名字是自定义的。

    <fruit-list v-bind:list= "list">
        <template slot-scope="slotProps">
            <strong v-if="slotProps.item.current">
            {{ slotProps.item.text }}
            </strong>
        </template>
    </fruit-list>

    例如下面这段完整的代码:给id为3的小li加上current样式。在字符串模板中通过info属性将内容传递过去,然后通过slot-scope接收。

    <div id="app">
        <fruit-list :list='list'>
          <template slot-scope='slotProps'>
            <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
            <span v-else>{{slotProps.info.name}}</span>
          </template>
        </fruit-list>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
          作用域插槽
        */
        Vue.component('fruit-list', {
          props: ['list'],
          template: `
            <div>
              <li :key='item.id' v-for='item in list'>
                <!-- slot里面的默认内容是{{item.name}},如果组件标签中没有传递值,则默认显示的就是该内容 -->
                <slot :info='item'>{{item.name}}</slot>
              </li>
            </div>
          `
        });
        var vm = new Vue({
          el: '#app',
          data: {
            list: [{
              id: 1,
              name: 'apple'
            },{
              id: 2,
              name: 'orange'
            },{
              id: 3,
              name: 'banana'
            }]
          }
        });
    </script>

    6. 基于组件的案例

    使用vue实现购物车功能

  • 相关阅读:
    点名
    2017.6.11 NOIP模拟赛
    HEOI 2012 旅行问题
    【1】【JUC】JDK1.8源码分析之ReentrantLock
    Git撤销&回滚操作
    源码分析之CountDownLatch
    【1】AQS详解
    循环屏障CyclicBarrier以及和CountDownLatch的区别
    【JUC】CountDownLatch
    匿名内部类中使用的外部局部变量为什么只能是final变量
  • 原文地址:https://www.cnblogs.com/zcy9838/p/13151300.html
Copyright © 2020-2023  润新知