Vue全家桶--08 Vue组件化开发

Vue全家桶--08 Vue组件化开发

8.1 组件的概念

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:

 在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:

// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
  template: '<li>这是个待办项</li>'
})

var app = new Vue(...)

现在你可以用它构建另一个组件模板:

<ol>
  <!-- 创建一个 todo-item 组件的实例 -->
  <todo-item></todo-item>
</ol>

 **组件就是对局部视图的封装,每个组件包含了:

  HTML结构

  CSS样式

  JavaScript行为:data数据/methods行为

**提高开发效率,增强可维护性,更好的去解决软件上的高耦合、低内聚、无重用的3大代码问题

**Vue 中的组件思想借鉴于 React

**目前主流的前端框架:Angular、React 、Vue 都是组件化开发思想

8.2 组件的基本使用

 为了能够在模板中使用,这些组件必须先注册以便Vue能够识别,注册类型:全局注册和局部注册

**全局注册

Vue.component('组件名',{
template: '定义组件模板',
data: function(){ //data 选项在组件中必须是一个函数
return {}
}
//其他选项:methods
})

组件名:可使用驼峰(camelCase)或者横线分隔(kebab-case)命名方式

    但DOM中只能使用横线分隔方式进行引用组件

    官方强烈推荐组件名字母全小写且必须包含一个连字符

template:定义组件的视图模板

data:在组件中必须是一个函数

<body>

    <div id="app">
        <component-a></component-a>
    </div>

    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
        /*
        全局组件注册:
            它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中
        参数1:组件名
            1.可使用驼峰(camelCase)或者横线分隔(kebab-case)命名方式
            2.DOM 中只能使用横线分隔方式进行引用组件
        */
        Vue.component('component-a', {
            // template 选项指定此组件的模板代码
            template: `<div><h3>头部组件--{{ name }}</h3></div>`,
            // data 必须是函数
            data: function () {
                return {
                    name: 'hello Vue!'
                }
            }
        })

        var vm = new Vue({
            el: '#app'
        });
    </script>
</body>

**局部注册

一般把一些非通用部分注册为局部组件,一般只适用于当前项目的

1. JS 对象来定义组件:
var ComponentA = { data: function(){}, template: '组件模板A'}
var ComponentA = { data: function(){}, template: '组件模板A'}
2. 在Vue实例中的 components 选项中引用组件:
new Vue({
  el: '#app',
  data: {},
  components: { // 组件选项
    'component-a': ComponentA // key:组件名,value: 引用的组件对象
    'component-b': ComponentB
  }
})
<body>
    <div id="app">
        <component-b></component-b>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
        var ComponentB = {
            template: '<div> 这是:{{ name }}</div>',
            data: function () {
                return {
                    name: '局部组件'
                }
            }
        }
        var vm = new Vue({
            el: '#app',
            components: {
                'component-b': ComponentB
            }
        });
    </script>
</body>

**总结:

*组件是可复用的 Vue 实例,不需要手动实例化

*与 new Vue 接收相同的选项,例如 data 、computed 、watch 、methods 等

*组件的 data 选项必须是一个函数

8.3 多个组件示例

 组件js文件:

Header.js
Vue.component('app-header',{
    template:`<h1>头部组件--{{ content }}</h1>`,
    data() {
        return {
            content: 'header-value',
        };
    },
})
Main.js
Vue.component('app-main', {
    template: `<ul>
                <li v-for="(item,index) in content">{{ item.name }}</li>
              </ul>`,
    data () {
        return {
            content: [
                { id: 1, name: '客户管理' },
                { id: 2, name: '供应商管理' },
                { id: 3, name: '信息管理' }
            ]
        };
    },
})
Footer.js
Vue.component('app-footer',{
    template:`<h3>底部组件--{{ property }}</h3>`,
    data () {
        return {
            property: 'footer-value',
        };
    },
})

html

<body>
    <div id="app">
        <app-header></app-header>
        <app-main></app-main>
        <app-footer></app-footer>
    </div>

    <script src="./node_modules/vue/dist/vue.js"></script>
    <script src="./components/Header.js"></script>
    <script src="./components/Main.js"></script>
    <script src="./components/Footer.js"></script>
    <script>
        var vm = new Vue({
            el:'#app'
        });
    </script>

</body>

8.4 Bootstrap首页组件化(重点)

8.4.1 首页分析

首页拆分多个组件:
    a.头部导航区域 -- AppNavbar
    b.左边菜单栏区域 -- AppLeft
    c.右边主区域 -- AppHome
    d.DashBoard
    e.HomeList

8.4.2 头部导航组件注册 AppNavbar

AppNavbar.js :声明自调用函数;并且声明window属性,否则函数之外无法调用

;(function(){

    const template=`<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
          aria-expanded="false" aria-controls="navbar">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="#">{{ title }}</a>
      </div>
      <div id="navbar" class="navbar-collapse collapse">
        <ul class="nav navbar-nav navbar-right">
          <li><a href="#">Dashboard</a></li>
          <li><a href="#">Settings</a></li>
          <li><a href="#">Profile</a></li>
          <li><a href="#">Help</a></li>
        </ul>
        <form class="navbar-form navbar-right">
          <input @blur="searchBlur" type="text" class="form-control" placeholder="Search...">
        </form>
      </div>
    </div>
  </nav>`;

    window.AppNavbar ={
        template,
        data() {
            return {
                title: '梦学谷',
            };
        },
        methods:{
            searchBlur(){
                alert('失去焦点!');
            }
        }

    }

})()

index.html 页面引用 AppNavbar.js,并声明组件

<script src="./components/AppNavbar.js"></script>
 <!--头部导航区域-->
    <app-navbar></app-navbar>

8.4.3 左侧导航组件注册 AppLeft

;(function(){

    const template=`<div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
      <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
      <li><a href="#">Reports</a></li>
      <li><a href="#">Analytics</a></li>
      <li><a href="#">Export</a></li>
    </ul>
    <ul class="nav nav-sidebar">
      <li><a href="">Nav item</a></li>
      <li><a href="">Nav item again</a></li>
      <li><a href="">One more nav</a></li>
      <li><a href="">Another nav item</a></li>
      <li><a href="">More navigation</a></li>
    </ul>
    <ul class="nav nav-sidebar">
      <li><a href="">Nav item again</a></li>
      <li><a href="">One more nav</a></li>
      <li><a href="">Another nav item</a></li>
    </ul>
  </div>`;

    window.AppLeft={
        template
    }

})()
<script src="./components/AppLeft.js"></script>
<!--左边菜单栏区域-->
        <app-left></app-left>

8.4.4 右侧主页面

分为3个组件:AppHome/DashBoard/HomeList

AppHome为父组件,DashBoard和HomeList为子组件

AppHome.js

;(function(){

    const template=` <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dash-board></dash-board>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list></home-list>
  </div>`;

    window.AppHome={
        template,
        components:{
            DashBoard,
            HomeList
        }
    }

})()

DashBoard.js

;(function(){

    const template=`<div class="row placeholders">
    <div class="col-xs-6 col-sm-3 placeholder">
      <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
        height="200" class="img-responsive" alt="Generic placeholder thumbnail">
      <h4>Label</h4>
      <span class="text-muted">Something else</span>
    </div>
    <div class="col-xs-6 col-sm-3 placeholder">
      <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
        height="200" class="img-responsive" alt="Generic placeholder thumbnail">
      <h4>Label</h4>
      <span class="text-muted">Something else</span>
    </div>
    <div class="col-xs-6 col-sm-3 placeholder">
      <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
        height="200" class="img-responsive" alt="Generic placeholder thumbnail">
      <h4>Label</h4>
      <span class="text-muted">Something else</span>
    </div>
    <div class="col-xs-6 col-sm-3 placeholder">
      <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
        height="200" class="img-responsive" alt="Generic placeholder thumbnail">
      <h4>Label</h4>
      <span class="text-muted">Something else</span>
    </div>
  </div>`;

    window.DashBoard={
        template
    }

})()

HomeList.js

;(function(){

    const template=`<div class="table-responsive">
    <table class="table table-striped">
      <thead>
        <tr>
          <th>#</th>
          <th>Header</th>
          <th>Header</th>
          <th>Header</th>
          <th>Header</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1,001</td>
          <td>Lorem</td>
          <td>ipsum</td>
          <td>dolor</td>
          <td>sit</td>
        </tr>
        <tr>
          <td>1,002</td>
          <td>amet</td>
          <td>consectetur</td>
          <td>adipiscing</td>
          <td>elit</td>
        </tr>
        <tr>
          <td>1,003</td>
          <td>Integer</td>
          <td>nec</td>
          <td>odio</td>
          <td>Praesent</td>
        </tr>
        <tr>
          <td>1,003</td>
          <td>libero</td>
          <td>Sed</td>
          <td>cursus</td>
          <td>ante</td>
        </tr>
        <tr>
          <td>1,004</td>
          <td>dapibus</td>
          <td>diam</td>
          <td>Sed</td>
          <td>nisi</td>
        </tr>
        <tr>
          <td>1,005</td>
          <td>Nulla</td>
          <td>quis</td>
          <td>sem</td>
          <td>at</td>
        </tr>
        <tr>
          <td>1,006</td>
          <td>nibh</td>
          <td>elementum</td>
          <td>imperdiet</td>
          <td>Duis</td>
        </tr>
        <tr>
          <td>1,007</td>
          <td>sagittis</td>
          <td>ipsum</td>
          <td>Praesent</td>
          <td>mauris</td>
        </tr>
        <tr>
          <td>1,008</td>
          <td>Fusce</td>
          <td>nec</td>
          <td>tellus</td>
          <td>sed</td>
        </tr>
        <tr>
          <td>1,009</td>
          <td>augue</td>
          <td>semper</td>
          <td>porta</td>
          <td>Mauris</td>
        </tr>
        <tr>
          <td>1,010</td>
          <td>massa</td>
          <td>Vestibulum</td>
          <td>lacinia</td>
          <td>arcu</td>
        </tr>
        <tr>
          <td>1,011</td>
          <td>eget</td>
          <td>nulla</td>
          <td>Class</td>
          <td>aptent</td>
        </tr>
        <tr>
          <td>1,012</td>
          <td>taciti</td>
          <td>sociosqu</td>
          <td>ad</td>
          <td>litora</td>
        </tr>
        <tr>
          <td>1,013</td>
          <td>torquent</td>
          <td>per</td>
          <td>conubia</td>
          <td>nostra</td>
        </tr>
        <tr>
          <td>1,014</td>
          <td>per</td>
          <td>inceptos</td>
          <td>himenaeos</td>
          <td>Curabitur</td>
        </tr>
        <tr>
          <td>1,015</td>
          <td>sodales</td>
          <td>ligula</td>
          <td>in</td>
          <td>libero</td>
        </tr>
      </tbody>
    </table>
  </div>`;

    window.HomeList={
        template
    }

})()

index.html

<body>

  <div id="app">

    <!--头部导航区域-->
    <app-navbar></app-navbar>

    <!--核心区域:分左右两边-->
    <div class="container-fluid">
      <div class="row">

        <!--左边菜单栏区域-->
        <app-left></app-left>

        <!--右边主页面区域: 分上下两个区域-->
        <app-home></app-home>

      </div>
    </div>

  </div>

  <script src="../node_modules/vue/dist/vue.js"></script>
  <script src="./components/AppNavbar.js"></script>
  <script src="./components/AppLeft.js"></script>
  <script src="./components/Home/DashBoard.js"></script>
  <script src="./components/Home/HomeList.js"></script>
  <script src="./components/Home/AppHome.js"></script>
  <script>
    var vm = new Vue({
      el: '#app',
      components:{
        AppNavbar,
        AppLeft,
        AppHome
      }

    });
  </script>

</body>

 8.4.5 极致组件化

根组件提取 App.js注意:template 模板中必须要的根元素,所以要在提取的内容外层加上 <div></div> , 一定要不要少了,不然报错!

报错提示:

App.js 将之前的组件AppHome/AppNavbar/AppLeft 声明在App.js中

; (function () {

    const template = `
    
        <!--头部导航区域-->
        <app-navbar></app-navbar>

        <!--核心区域:分左右两边-->
        <div class="container-fluid">
        <div class="row">

            <!--左边菜单栏区域-->
            <app-left></app-left>

            <!--右边主页面区域: 分上下两个区域-->
            <app-home></app-home>

        </div>
        </div>
    
    `;

    window.App = {
        template,
        components:{
            AppNavbar,
            AppLeft,
            AppHome
          }
    }

})()

index.html 页面引用App.js ,并且声明App的组件

<body>

  <div id="app">

   <app></app>

  </div>

  <script src="../node_modules/vue/dist/vue.js"></script>
  <script src="./components/AppNavbar.js"></script>
  <script src="./components/AppLeft.js"></script>
  <script src="./components/Home/DashBoard.js"></script>
  <script src="./components/Home/HomeList.js"></script>
  <script src="./components/Home/AppHome.js"></script>

  <script src="./App.js"></script>
  <script>
    var vm = new Vue({
      el: '#app',
      components:{
        App
      }
    });
  </script>

</body>

注意:在 <div id="app"> 下通过 </app> 引用 App 组件不是很好, 因为页面代码中会多出一个 div.
更好的方式是,在 <div id="app"> 下无须使用 </app> 引用 App 组件,
可以通过 Vue 根实例的 template 选项引用组件 <app></app> 后,然后会把 template 中的渲染结果替换掉
#app 标签。

当前 index.html 文件中还有 JS 代码,可以将这些 JS 代码提取出来放到 main.js 中

main.js 
var vm = new Vue({
    el: '#app',
    template:'<app></app>',
    components:{
      App
    }
  });

index.html

<body>

  <div id="app">

  </div>

  <script src="../node_modules/vue/dist/vue.js"></script>
  <script src="./components/AppNavbar.js"></script>
  <script src="./components/AppLeft.js"></script>
  <script src="./components/Home/DashBoard.js"></script>
  <script src="./components/Home/HomeList.js"></script>
  <script src="./components/Home/AppHome.js"></script>

  <script src="./App.js"></script>
  <script src="./main.js"></script>

</body>

8.5 组件化注意事项

**组件可以理解为特殊的 Vue 实例,不需要手动实例化,管理自己的 template 模板
**组件的 template 必须有且只有一个根节点
**组件的 data 选项必须是函数,且函数体返回一个对象
**组件与组件之间是相互独立的,可以配置自己的一些选项资源 data、methods、computed 等等
**思想:组件自己管理自己,不影响别人

8.6 Vue父子组件间通信

8.6.1 组件间通信方式

1. props 父组件向子组件传递数据
2. $emit 自定义事件
3. slot 插槽分发内容

8.6.2 组件间通信规则

1. 不要在子组件中直接修改父组件传递的数据。
2. 数据初始化时,应当看初始化的数据是否用于多个组件中,如果需要被用于多个组件中,则初始化在父组件
中;如果只在一个组件中使用,那就初始化在这个要使用的组件中。
3. 数据初始化在哪个组件, 更新数据的方法(函数)就应该定义在哪个组件。

8.6.3 props向子组件传递数据

  8.6.3.1 声明组件对象中定义 props(子组件中声明)

  在声明组件对象中使用 props 选项指定

const MyComponent = {
    template: '<div></div>',
    props: 此处值有以下3种方式,
    components: {
    }
}

  方式1:指定传递属性名,注意是 数组形式

props: ['id','name','salary', 'isPublished', 'commentIds', 'author', 'getEmp']

  方式2:指定传递属性名和数据类型,注意是 对象形式

props:{
  id:Number,
  name:String,
  salary:Number,
  isPublished:Boolean,
  commentIds:Array,
  author:Object,
  getEmp:Function    
}

  方式3:指定属性名、数据类型、必要性、默认值

props: {
name: {
type: String,
required: true,
default: 'mxg'
}
}

  8.6.3.2 引用组件时动态赋值(父组件)

  在引用组件时,通过 v-bind 动态赋值

<my-component v-bind:id="2" :name="meng" :salary="9999" :is-published="true" :comment-ids="[1, 2]"
:author="{name: 'alan'}" :get-emp="getEmp" >
</my-component>

  8.6.3.3 传递数据注意

  1. props 只用于父组件向子组件传递数据
  2. 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
  3. 问题:
    a. 如果需要向非子后代传递数据,必须多层逐层传递
    b. 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以

8.6.4 props案例 -- 列表渲染

渲染DashBoard和HomesList数据,由父组件AppHome定义数据

AppHome.js  --调用子组件 v-bind传参

; (function () {

    const template = ` <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dash-board :hobbies='hobbies'></dash-board>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :empList='empList'></home-list>
  </div>`;

    window.AppHome = {
        template,
        components: {
            DashBoard,
            HomeList
        },
        data() {
            return {
                hobbies: ['看书', '台球', '睡觉', '撸代码'],//dashboard显示数据
                empList: [//homelist 显示数据
                    { id: 1, name: '小梦1', salary: 80001 },
                    { id: 2, name: '小梦2', salary: 80002 },
                    { id: 3, name: '小梦3', salary: 80003 },
                    { id: 4, name: '小梦4', salary: 80004 }
                ]
            };
        },
    }

})()

DashBoard.js

;(function(){

    const template=`
    <div class="row placeholders">
        <div v-for='(item,index) in hobbies' class="col-xs-6 col-sm-3 placeholder">
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
            height="200" class="img-responsive" alt="Generic placeholder thumbnail">
        <h4>{{item}}</h4>
        <span class="text-muted">Something else</span>
        </div>
    </div>
    `;

    window.DashBoard={
        template,
        props:['hobbies'] //定义父组件传过来的数据
    }

})()

HomeList.js 

**将HomeList组件中的 tr 在单独封装一个组件,取名Item

Item.js  -- 定义props 定义父组件传过来的参数

;(function(){

    const template=`
    <tr>
        <td>{{ emp.id }}</td>
        <td>{{ emp.name }}</td>
        <td>{{ emp.salary }}</td>
    </tr>
    `;

    window.Item={
        template,
        props:{//
            emp:Object
        }
    }

})()

HomeList.js  --

;(function(){

    const template=`<div class="table-responsive">
    <table class="table table-striped">
      <thead>
        <tr>
          <th>id</th>
          <th>name</th>
          <th>salary</th>
        </tr>
      </thead>
      <tbody>
        <item v-for='(emp,index) in empList' :emp='emp'></item>
      </tbody>
    </table>
  </div>`;

    window.HomeList={
        template,
        props:{ //接收父组件传过来的数据,然后在将数组里面的对象传给子组件
            empList:Array
        },
        components:{
            Item
        }
    }

})()

8.6.5 props案例 -- 删除员工功能实现

根据组件通信的原则,在哪个组件定义的数据就在哪个组件上定义相关方法!!!

因为要对emplist数据在删除,所在在AppHome组件上定义删除的方法,并且传递给子组件

; (function () {

    const template = ` <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dash-board :hobbies='hobbies'></dash-board>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :empList='empList' :delItem="delItem"></home-list>
  </div>`;

    window.AppHome = {
        template,
        components: {
            DashBoard,
            HomeList
        },
        data() {
            return {
                hobbies: ['看书', '台球', '睡觉', '撸代码'],//dashboard显示数据
                empList: [//homelist 显示数据
                    { id: 1, name: '小梦1', salary: 80001 },
                    { id: 2, name: '小梦2', salary: 80002 },
                    { id: 3, name: '小梦3', salary: 80003 },
                    { id: 4, name: '小梦4', salary: 80004 }
                ]
            };
        },
        methods: {
            // 删除指定下标的数据
            // 因为删除 emp 会对 empList 做更新操作,
            // 而 empList 是初始化在当前这个组件里,所以删除的函数要定义在这个组件中
            delItem(index) {
                this.empList.splice(index, 1);
            }
        }
    }

})()

**Item组件并不是AppHome组件的子组件,而是孙子组件,所以需要逐层传递,先传给HomeList组件,再由HomeList组件传给Item组件

HomeList组件

;(function(){

    const template=`<div class="table-responsive">
    <table class="table table-striped">
      <thead>
        <tr>
          <th>id</th>
          <th>name</th>
          <th>salary</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <item v-for='(emp,index) in empList' :key='emp.id' :emp='emp'  :index='index' :delItem='delItem'></item>
      </tbody>
    </table>
  </div>`;

    window.HomeList={
        template,
        props:{ //接收父组件传过来的数据,然后在将数组里面的对象传给子组件
            empList:Array,
            delItem:Function  //逐层传递
        },
        components:{
            Item
        }
    }

})()

Item组件

; (function () {

    const template = `
    <tr>
        <td>{{ emp.id }}</td>
        <td>{{ emp.name }}</td>
        <td>{{ emp.salary }}</td>
        <td><a href='#' @click='deleteItem'>删除</a></td>
    </tr>
    `;

    window.Item = {
        template,
        props: {//
            emp: Object,
            delItem: Function,
            index: Number
        },
        methods: {
            deleteItem() {
                // 移除索引为index的一条记录,
                // 注意:不要少了 this
                this.delItem(this.index);
            }
        }
    }

})()

8.6.6 自定义事件

作用:通过 自定义事件 来代替 props 传入函数形式

a.绑定自定义事件

在父组件中定义事件监听函数,并引用子组件标签上 v-on 绑定事件监听。

// 通过 v-on 绑定
// @自定义事件名=事件监听函数
// 在子组件 dashboard 中触发 delete_hobby 事件来调用 deleteHobby 函数
<dashboard @delete_hobby="deleteHobby"></dashboard>

b.触发监听事件函数执行

在子组件中触发父组件的监听事件函数调用

// 子组件触发事件函数调用
// this.$emit(自定义事件名, data)
this.$emit('delete_emp', index)

c.自定义事件注意

1. 自定义事件只用于子组件向父组件发送消息(数据)

2. 隔代组件或兄弟组件间通信此种方式不合适

8.6.7 自定义事件案例

删除DashBoard中的图标元素

在父组件AppHome中定义删除函数deleteHobby,并通过 v-on自定义事件传递给子组件

AppHome.js

; (function () {

    const template = ` <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dash-board :hobbies='hobbies' @del-Hobby="deleteHobby"></dash-board>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :empList='empList' :delItem="delItem"></home-list>
  </div>`;

    window.AppHome = {
        template,
        components: {
            DashBoard,
            HomeList
        },
        data() {
            return {
                hobbies: ['看书', '台球', '睡觉', '撸代码'],//dashboard显示数据
                empList: [//homelist 显示数据
                    { id: 1, name: '小梦1', salary: 80001 },
                    { id: 2, name: '小梦2', salary: 80002 },
                    { id: 3, name: '小梦3', salary: 80003 },
                    { id: 4, name: '小梦4', salary: 80004 }
                ]
            };
        },
        methods: {
            // 删除指定下标的数据
            // 因为删除 emp 会对 empList 做更新操作,
            // 而 empList 是初始化在当前这个组件里,所以删除的函数要定义在这个组件中
            delItem(index) {
                this.empList.splice(index, 1);
            },
            deleteHobby(index) {
                this.hobbies.splice(index, 1);
            }
        }
    }

})()

DashBoard.js

;(function(){
    const template=`
    <div class="row placeholders">
        <div v-for='(item,index) in hobbies' class="col-xs-6 col-sm-3 placeholder">
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
            height="200" class="img-responsive" alt="Generic placeholder thumbnail">
        <h4>{{item}}</h4>
        <span class="text-muted"><a @click='delHobby(index)' href="#">删除</a></span>
        </div>
    </div>
    `;
    window.DashBoard={
        template,
        props:['hobbies'] ,//定义父组件传过来的数据
        methods:{
            delHobby(index){
                // 触发父组件中 del-Hobby 事件进行删除操作
                this.$emit('del-Hobby', index);
            }
        }
    }
})()

8.6.8 slot插槽

作用: 主要用于父组件向子组件传递 标签+数据 , (而上面prop和自定事件只是传递数据)
场景:一般是某个位置需要经常动态切换显示效果

a.子组件定义插槽

在子组件中定义插槽, 当父组件向指定插槽传递标签数据后, 插槽处就被渲染,否则插槽处不会被渲染.

<div>
    <!-- name属性值指定唯一插槽名,父组件通过此名指定标签数据-->
    <slot name="aaa">不确定的标签结构 1</slot>
    <div>组件确定的标签结构</div>
    <slot name="bbb">不确定的标签结构 2</slot>
</div>

b.父组件传递标签数据

<child>
    <!--slot属性值对应子组件中的插槽的name属性值-->
    <div slot="aaa">向 name=aaa 的插槽处插入此标签数据</div>
    <div slot="bbb">向 name=bbb 的插槽处插入此标签数据</div>
</child>

c.插槽注意事项

1. 只能用于父组件向子组件传递 标签+数据

2. 传递的插槽标签中的数据处理都需要定义所在父组件中

8.6.9 slot插槽案例

 将Dashboard 的标题通过插槽显示

 将 Dashboard 的标题标签定义为插槽 <slot name="dashboard"></slot>

; (function () {

    const template = ` <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

    <!--右边上半区域-->
    <!--<h1 class="page-header">Dashboard</h1>-->
    <!--定义插槽-->
    <slot name="dashboard"></slot>
    <dash-board :hobbies='hobbies' @del-Hobby="deleteHobby"></dash-board>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :empList='empList' :delItem="delItem"></home-list>
  </div>`;

    window.AppHome = {
        template,
        components: {
            DashBoard,
            HomeList
        },
        data() {
            return {
                hobbies: ['看书', '台球', '睡觉', '撸代码'],//dashboard显示数据
                empList: [//homelist 显示数据
                    { id: 1, name: '小梦1', salary: 80001 },
                    { id: 2, name: '小梦2', salary: 80002 },
                    { id: 3, name: '小梦3', salary: 80003 },
                    { id: 4, name: '小梦4', salary: 80004 }
                ]
            };
        },
        methods: {
            // 删除指定下标的数据
            // 因为删除 emp 会对 empList 做更新操作,
            // 而 empList 是初始化在当前这个组件里,所以删除的函数要定义在这个组件中
            delItem(index) {
                this.empList.splice(index, 1);
            },
            deleteHobby(index) {
                this.hobbies.splice(index, 1);
            }
        }
    }

})()

向子组件中定义的插槽处传递标签数据。其中标题使用 title 数据绑定

; (function () {

    const template = `
    <div>
        <!--头部导航区域-->
        <app-navbar></app-navbar>

        <!--核心区域:分左右两边-->
        <div class="container-fluid">
        <div class="row">

            <!--左边菜单栏区域-->
            <app-left></app-left>

            <!--右边主页面区域: 分上下两个区域-->
            <app-home>
                <h1 slot="dashboard" class="page-header">{{title}}</h1>
            </app-home>

        </div>
        </div>
    </div>
    `;

    window.App = {
        template,
        components: {
            AppNavbar,
            AppLeft,
            AppHome
        },
        data() {
            return {
                title: '仪表盘',
            };
        },
    }

})()

8.7 非父子组件间通信

Vue.js 可通过 PubSubJS 库来实现非父子组件之间的通信 ,使用 PubSubJS 的消息发布与订阅模式,来进行数据的
传递。

理解:订阅信息 ==== 绑定事件监听 ,发布消息 ==== 触发事件。
注意: 但是必须先执行订阅事件 subscribe ,然后才能 publish 发布事件。

订阅消息(绑定事件监听)

先在 created 钩子函数中订阅消息

//event接收的是消息名称,data是接收发布时传递的数据
PubSub.subscribe('消息名称(相当于事件名)', function(event, data) {
    //事件回调处理
})

发布消息(触发事件)

PubSub.publish('消息名称(相当于事件名)', data)

案例

1.安装 pubsub-js

npm install pubsub-js

2.index.html 引入 pubsub-js 库

<script src="./node_modules/vue/dist/vue.js"></script>
<script src="./node_modules/pubsub-js/src/pubsub.js"></script>

3.AppLeft.js 订阅消息

初始化数量 ,在 created 钩子中订阅消息,监听当删除爱好后,进行统计已删除总数

; (function () {

  const template = `<div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
      <li class="active"><a href="#">Overview 
      <span v-show="delNum">({{ delNum }})</span></a>
      </li>
      <li><a href="#">Reports</a></li>
      <li><a href="#">Analytics</a></li>
      <li><a href="#">Export</a></li>
    </ul>
    <ul class="nav nav-sidebar">
      <li><a href="">Nav item</a></li>
      <li><a href="">Nav item again</a></li>
      <li><a href="">One more nav</a></li>
      <li><a href="">Another nav item</a></li>
      <li><a href="">More navigation</a></li>
    </ul>
    <ul class="nav nav-sidebar">
      <li><a href="">Nav item again</a></li>
      <li><a href="">One more nav</a></li>
      <li><a href="">Another nav item</a></li>
    </ul>
  </div>`;

  window.AppLeft = {
    template,
    data() {
      return {
        delNum: 0,
      };
    },
    created() {
      PubSub.subscribe('changeNum', (event, num) => { // 箭头函数
        // 删除成功
        this.delNum = this.delNum + num
      })
    }
  }

})()

4.AppHome.js 发布消息

deleteHobby(index) {
                this.hobbies.splice(index, 1);
                // 删除成功,发布消息,导航统计数据
                PubSub.publish('changeNum', 1)
            }

优点:不管是父子之间还是非父子之间通信 PubSubJS 都可以实现

8.8 单文件组件

8.8.1 当前项目存在的问题

**全局定义 (Global definitions) 强制要求每个 component 中的命名不得重复。
**字符串模板 (String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 。
**不支持 CSS (No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏。
**没有构建步骤 (No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器,如 Pug
(formerly Jade) 和 Babel。

文件扩展名为 .vue 的 single-file components(单文件组件) 为以上所有问题提供了解决方法,并且还可以使用
webpack 或 Browserify 等构建工具。

8.8.2 单文件组件模板格式

<template>
// 组件的模块
</template>
<script>
// 组件的JS
</script>
<style>
// 组件的样式
</style>
You are never too old to set another goal or to dream a new dream!!!
原文地址:https://www.cnblogs.com/youguess/p/15466432.html