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实现购物车功能

posted @ 2020-06-18 10:30  浮华夕颜  Views(308)  Comments(0Edit  收藏  举报