零基础学Vue--day03

1. 组件 (Component)

  • 组件:可以扩展 HTML 元素,封装可重用的代码;
  • 组件化规范 (Web Components):通过创建,封装好的定制元素(自定义标签),解决上述问题;
  • 并非所有浏览器,都支持 Web Components 规范,Vue部分实现了,上述规范;

1) 全局组件注册

Vue.component(组件名称, {
   data:function(){ 
      return{ 
   		cont:0 
      }
   }
   template:`模板字符串:反引号 ES6语法 需要浏览器支持` //模板中,必须有唯一的根元素,不能平级;
})

/*组件中的数据 data,必须是一个函数(形成闭包环境,保证每一个组件,都有一份独立的数据),且返回一个{对象};
 *子组件不像根组件,只调用一次,调用多次时,每个子组件的数据,都要是独立的,互不影响;
  通过一个函数,来返回一个对象,就是让每一个子组件,都有一个独立的数据存储;*/
<div id="example">
  <button-counter></button-counter> //组件名称 以HTML标签的形式使用;
  <button-counter></button-counter> //每个组件的数据,都是相互独立的,互不影响
</div>

<script> //驼峰式命名组件,以短横线的方式使用;
	Vue.component('buttonCounter', {
      data: function(){
        return {
          count: 0
        }
      },	
      template: '<button @click="handle">点击了{{count}}次</button>',
      methods: {
        handle: function(){
          this.count += 2;//使用data中的数据,加this
        }
      }
    })
</script>

2) 局部组件注册

  • 只能在注册,他的父组件中使用,<div id="app"></div>;嵌套在其它兄弟组件中,不行;
  • vue实例对象,顶层组件;
<div id="app">
     <component-a></component-a> 
</div>

<script>
    //组件内容抽取到一个对象当中;
    var ComponentA = {
      data:function(){},  
      template: '<div>A {{msg}}</div>'
    };
    new Vue({
      el: '#app'
      components: {
        'component-a': ComponentA,
        '组件名称':组件内容
       }
    })
</script>

2. Vue 调试工具安装

在这里插入图片描述
在这里插入图片描述

3. 父组件 向子组件传值

  • 父组件通过属性,把值传递给子组件;子组件用 props 接收;
  • props中 属性名,使用驼峰形式,html中,必须使用,短横线形式 (html中不区分大小写);
    template: 模板字符串中,则没有这个限制;
<div id="app">
    <div>{{pmsg}}</div> //{{}} 必须有标签包裹
    //pname不加:动态绑定,和content一样,传递的只是 字符串,而非变量;加: 是js表达式 
    <menu-item content='hello' pname='ptitle'></menu-item> 
    <menu-item :title='ptitle'></menu-item>
    //动态数据,需要属性绑定的形式, ptitle来自父组件 data中的数据; 
</div>

Vue.component('menu-item', {
    props: ['title', 'content','pname'], //title和父组件的,属性名保存一致;
    data: function() {
        return {
            msg: '子组件本身的数据'
        }
    },
    template: '<div>{{msg + "---" + title + "---" + content +"---"+ pname}}</div>'
        	   //子组件本身的数据---undefined---hello---ptitle
        	   //子组件本身的数据---父组件中的内容---undefined---undefined
});
var vm = new Vue({  //vue实例本身就是一个组件,而且还是根组件;
    el: '#app',
    data: {
        ptitle: '父组件中的内容'
    }
});
-----------------------------------------------------------------------------------------
<menu-item :menu-title='ptitle'></menu-item>  //ptitle: '来自父组件'

/* menu-item父组件,third-com子组件,父组件在模板中,直接嵌套子组件:
   <third-com testTile="hello"></third-com> 
   传值也类似与 app中的子组件,在父组件区域,直接写在子组件中传值 */
Vue.component('menu-item', {
  props: ['menuTitle'],
  template: `<div>
                {{menuTitle}}
                <third-com testTile="hello"></third-com>
             </div>` /也可以写成 test-tile="hello"
});
Vue.component('third-com', {
  props: ['testTile'],
  template: '<div>{{testTile}}</div>'
});
//页面显示:	 来自父组件	hello

props属性值类型:(前三种基本类型,后两种引用类型)

	字符串  String
	数值    Number
	布尔值  Boolean
	数组    Array
	对象    Object
<menu-item :pstr='pstr' :pnum='12' :pboo='true' :parr='parr' :pobj='pobj'></menu-item>
//动态绑定,是对应类型的数据;不动态绑定,是字符串;
//数值类型 pnum='12' 不加: 静态绑定 是字符串,加: 动态绑定,是数值类型,可以直接进行运算;
//布尔类型pboo 加: 是boolean类型,不加是字符串true;

 Vue.component('menu-item', {
    props: ['pstr','pnum','pboo','parr','pobj'],
    template: `
        <div>
            <div>{{pstr}}</div> //hello
            <div>{{typeof pnum}}</div> //number 可以直接进行运算
            <div>{{pboo}}</div> //true;typeof pboo = boolean
            <ul>
            	<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
            </ul>  //apple     orange     banana
                <span>{{pobj.name}}</span> -->lisi
                <span>{{pobj.age}}</span> -->12
            </div>  
        </div>`
});
var vm = new Vue({
    el: '#app',
    data: {
        pstr: 'hello',
        parr: ['apple','orange','banana'],//数组的传递
        pobj: {
          name: 'lisi',
          age: 12
    	}
    }
});

4. 子组件向 父组件传值

props 单向传递数据流 
//子组件通过$emit("自定义事件名称", 需要传递的数据),触发自定义事件,向父组件传递数据;
//父组件区域: html标签中,v-on监听 相同的事件名称,默认用 $event接收传过来的值,并执行相应的函数;  

<div id="app">  	//渲染字体大小样式
    <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
    <menu-item @enlarge-text='handle($event)'></menu-item> //$event可省略
			 //@enlarge-text='fontSize += $event' 
</div>

Vue.component('menu-item', {
    template: `
    <div>          
      <button @click='$emit("enlarge-text", 5)'>扩大字体</button>
    </div> `  //methods中要加this.$emit()
});

data: {
    pmsg: '父组件中的内容',
    fontSize: 10
},
methods: { //扩大字体大小
    handle: function(val) { //val接收$event参数 
        this.fontSize += val; //调用data里的数据,加this
    }
}

5. 兄弟组件 间传值

在这里插入图片描述

1.兄弟组件之间,通过事件中心,传递数据:var hub = new Vue()
2.传递数据:
  methods: {
       handle: function(){
           //'自定义事件名', 传递的数据
         hub.$emit('jerry-event', 2);
       }
  }         
3.接收数据:mounted 生命周期里  
  mounted: function() {
       hub.$on('jerry-event', (val) => {
			this.num += val; //bug:一定要箭头函数,否则,this不生效;
       });
  }      

4.销毁事件:父组件 methods中,hub.$off('jerry-event');销毁后,无法进行数据传递; 
----------------------------------------------------------------------------       
<button @click='handle'>销毁事件</button>        
<test-tom></test-tom>
<test-jerry></test-jerry>
    
var hub = new Vue(); //提供事件中心
Vue.component('test-tom', {
    data: function() {
        return {
            num: 0
        }
    },
    template: `<div>
                 <div>TOM:{{num}}</div>
               </div>`,
    //在 mounted钩子函数里,监听事件;(此时模板准备好了) 
    mounted: function() {  
        hub.$on('tom-event', (val) => { //val传过来的数据
            this.num += val;
        });
    }
});
Vue.component('test-jerry', {
    template: `<div>
                 <button @click='handle'>点击传值</button>
               </div>`,
    methods: {
        handle: function() {
            // 触发兄弟组件的事件
            hub.$emit('tom-event', 1);
        }
    }
});
var vm = new Vue({
    el: '#app',
    data: {
    },
    methods: {
        handle: function() {
            hub.$off('tom-event');
        }
    }
});

6. 组件插槽: 父组件向子组件,传递模板的内容

拓展慕课网:
//传递标签模板
<child content='<p>dell</p>'></child>

Vue.component('child', {
    props: ['content'], 
    template:`<div>
				 <div v-html='this.content'></div> //dell 
				 <div>{{content}}</div> //<p>dell</p>
               </div>`    	
});
//<div v-html='this.content'> 不想渲染<div>标签,用template模板占位符,是不好用的;

// template模板占位符,可以用来包裹一些元素,但并不会渲染到页面上;
<template v-for='item in list' :key='item.id'>
    <div>{{item.text}}</div>
    <span>{{item.text}}</span>
</template>
//父组件 标签中的 内容,会替换掉 <slot>标签;如果不传内容,则使用 <slot>的默认值; 
<alert-box>有一个警告</alert-box>
<alert-box></alert-box>

template: `<div>      
             //插槽内可以包含任何模板代码,包括 HTML
             <strong>ERROR:</strong>
             <slot>默认内容</slot>
           </div>`

具名插槽: 有名字的插槽;使用<slot>中的 “name” 属性,绑定元素;

<base-layout> //slot属性的值,对应模板中的 name值;否则,会被放到匿名插槽中; 
    <p slot='header'>标题信息</p> 

    <p>主要内容1</p>  //两行<p>标签,放到同一个插槽中 
    <p>主要内容2</p>   

    <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
    </template>
</base-layout>
//template 临时包裹标签,不会渲染到页面上,可以包裹多个标签;<p slot='header'>只能包裹一个标签;

//具名插槽的渲染顺序,完全取决于模板,而不是父组件中,元素的顺序;
template:`<div>    
              <header>                
                 <slot name='header'></slot>
              </header>
              
              <main>
                 <slot></slot> //显示插槽里,所有的内容;
				 <slot></slot> //两个<slot>,会显示两遍;
              </main>
              
              <footer>      
                 <slot name='footer'></slot>
              </footer>
          </div>`

作用域插槽: 父组件获取 子组件中的数据,从而进行加工处理;

<fruit-list :list= "list">//传递 list水果数组
   <template slot-scope="props"> //接收子组件传递的数据;
        <strong v-if='props.info.id==3' class="current"> 
            {{props.info.name}}
         </strong>  //{{...name}}包裹上一个标签,方便处理;
         <span v-else>{{props.info.name}}</span>           
    </template>  
</fruit-list>

/*slot-scope是 template的一个属性,可以得到模板中,动态绑定的数据;
  props-->{"info":{"id":3,"name":"banana"}} props.info-->{"id":3,"name":"banana"}
  得到以 .info方式传过来的数据;.name 对应的水果名称;*/
--------------------------------------------------------------------------------- 
Vue.component('fruit-list', {
    props: ['list'], //接收传过来的水果数组
    template:`<div>
                 <li v-for='item in list' :key='item.id'>          
                    <slot :info='item'>{{item.name}}</slot>
                 </li>
               </div>`    	
});
/* 在子组件模板中,<slot>插槽,有一个类似,向props传递数据的写法 
   :info="item";动态绑定一个属性,属性名自定义,属性值,是数组遍历出来的,每一项数据;
   {{item.name}} 插槽的默认内容,会被父组件,提供的内容覆盖;
拓展慕课网:
//作用域插槽:子组件向父组件,插槽里传数据;父组件必须使用<template>,通过slot-scope属性接收数据
<child>
    <template slot-scope="props"> //接收传递过来的数据;
        <li>{{props.item}}<li>        
    </template>
</child>

Vue.component('child', {
    data: function(){
        return {
            list:[1,2,3,4]
        }
    }, 
    template:`
      <div>
         <ul>  
			<slot v-for='item in list' :item='item'></slot>
		 </ul>
      </div>`    	
});

7. 购物车结算案例

在这里插入图片描述
1) 实现组件化布局
把静态页面转换成,三个局部组件(名称不易全局冲突)

<div id="app">
    <div class="container">
       <my-cart></my-cart> //把组件渲染到页面上 
    </div>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
//标题组件 
var CartTitle = {
  template: `
    <div class="title">我的商品</div>
  `
}
//商品列表组件:注意组件模板,必须是单个根元素
var CartList = {
  template: `
    <div>
      <div class="item"> ....
    </div>
  `
}
//商品结算组件 
var CartTotal = {
  template: `
    <div class="total">
      <span>总价:123</span>
      <button>结算</button>
    </div>
  `
}
//全局组件 my-cart
Vue.component('my-cart',{
  template: `
    <div class='cart'> //每个组件都需要一个根节点;用一个容器包裹住3个子组件;
      <cart-title></cart-title>
      <cart-list></cart-list>
      <cart-total></cart-total>
    </div>
  `,
  components: {
    'cart-title': CartTitle,
    'cart-list': CartList,
    'cart-total': CartTotal
  }
});
var vm = new Vue({
  el: '#app'    
});

2) 标题和结算功能组件

  • 动态渲染标题组件
    • 父组件把标题数据,传给子组件;子组件接收数据,并渲染到页面上;
  • 结算功能组件
    • 把商品列表 list 数组,传给子组件;
    • 用计算属性,遍历数组,计算商品总价 (单价*数量);渲染到页面上;
//标题组件     
var CartTitle = {
    props: ['uname'],
    template: `<div class="title">{{uname}}的商品</div>` 
}
//商品结算组件  
var CartTotal = {
    props: ['list'], //接收数据
    template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
        `,
    computed: { 
        total: function() {
          var t = 0;
          this.list.forEach(item => {   //遍历数组
            t += item.price * item.num; //单价和数量相乘,再累加;
          });
          return t;
        }
    }
}
// my-cart全局组件 
Vue.component('my-cart',{
  data: function() {
    return {
      uname: '张三',
      list: [{     
        id: 1,
        name: 'TCL彩电',
        price: 1000,
        num: 1,
        img: 'img/a.jpg'
        }, ......]
    }
  }, 
  template: `
    <div class='cart'>
      <cart-title :uname='uname'></cart-title> 
      <cart-list></cart-list>
      <cart-total :list='list'></cart-total>  
    </div>`,
  components: {...}
})

3) 实现列表组件删除功能

  • 动态渲染列表页面;
  • 点击 “删除按钮”,把要删除的 id 传过去;
  • 根据 id,找到对应数据的索引;
  • 根据索引,删除 list 数组中,对应的数据;
var CartList = {
  props: ['list'],//接收数据,动态渲染到页面上
  template: `
    <div>
      <div :key='item.id' v-for='item in list' class="item">
         <img :src="item.img"/>
         <div class="name">{{item.name}}</div>
         <div class="change">
            <a href="">-</a>
            <input type="text" class="num" />
            <a href="">+</a>
         </div>
         //点击按钮,把要删除的id传过去;
         <div class="del" @click='del(item.id)'>×</div>
      </div>
    </div>
  `,
  methods: {
    del: function(id){
      this.$emit('cart-del', id);
    }
  }
}
// my-cart全局组件
Vue.component('my-cart',{
  ...... 
  template: `
    <div class='cart'>
      ......
      						  //父组件,接收子组件,传过来的数据; 
      <cart-list :list='list' @cart-del='delCart($event)'></cart-list>
      ......                  
    </div>
  `,
  methods: {      
    delCart: function(id) {
      //根据 id,找到对应数据 的索引;
      var index = this.list.findIndex(item=>{
        return item.id == id;
      });
      //根据索引,删除数据;
      this.list.splice(index, 1);
    }
  }
})

4) 实现组件更新数据功能 - 商品数量的变更

  • 将输入框中的,默认数据,渲染出来;
    数据来自 props,单向数据流,不能修改,所以不能用 v-model;
    动态绑定 input 框的 value属性,直接呈现出来 :value=‘item.num’
  • 修改值时,绑定输入框,失去焦点事件: @blur = ‘changeNum(item.id, $event)’ ;
    item.id 要修改的 id;修改的值,也就是输入框中的内容,存在 $event.target.value 中;
  • 把数据传给,父组件修改;
    this.$emit() 通过对象的方式,传递两项数据;父组件通过 $event 接收这个对象;
//商品列表组件
`<div class="change">
   <a href="">-</a>
   <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
   <a href="">+</a>  
 </div>`,
methods: {
  changeNum: function(id, event){
     // 把数据传递给父组件修改,不直接操作list数据 
     this.$emit('change-num', { 
    	id: id, 
    	num: event.target.value
     }); //携带两项数据,通过对象的方式传递;父组件只需通过 $event接收这个对象;
  }    
}
//全局组件 my-cart
Vue.component('my-cart',{
  ... ...
  template: `
	 ... ...
	 <cart-list @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
	 ... ... `,
  methods: {
    changeNum: function(val) {
      //根据子组件,传过来的数据,更新list中,对应的数据
      this.list.some(item=>{
        if(item.id == val.id) {
          item.num = val.num;
          return true; //终止遍历
        }
      })
   }
})

5)实现组件更新数据功能 - ‘+’ ‘-’ 的变更

  • @click.prevent=‘sub(item.id)’> 阻止a标签的默认跳转;
  • ‘+’ '- ’ input输入框,绑定同一个自定义事件,把参数传递给父组件修改;传递的参数中,用 type 类型区分;
//商品列表组件
`<a href="" @click.prevent='sub(item.id)'>-</a>
 <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
 <a href="" @click.prevent='add(item.id)'>+</a>`,
methods: {
    changeNum: function(id, event){
       this.$emit('change-num', {
          id: id,
          type: 'change',
          num: event.target.value
       });
    },
    sub: function(id){
      //数量的增加和减少,通过父组件来计算,每次都是 加1,或减1,不需要传递;
      //父组件需要一个类型来判断,是加一,减一,还是输入框输入的数据;我们通过 type标识符,来标记不同的操作;
      this.$emit('change-num', {
        id: id,
        type: 'sub'
      });
    },
    add: function(id){
      this.$emit('change-num', {
        id: id,
        type: 'add'
      });
    },
    del: function(id){
      this.$emit('cart-del', id); //把删除的id,传递给父组件;
    }
}
//全局组件 my-cart
Vue.component('my-cart',{
  data: ...
  template: `	
     //父组件通过事件监听   接收子组件的数据  
     <cart-list @change-num='changeNum($event)' @cart-del='delCart($event)'> `,
  components: {
    'cart-title': CartTitle,
    'cart-list': CartList,
    'cart-total': CartTotal
  },
  methods: {
    changeNum: function(val) {
      //分为三种情况:输入框变更操作
      if(val.type=='change') {
        //根据子组件传递过来的数据,更新list中对应的数据
        this.list.some(item=>{
          if(item.id == val.id) {
            item.num = val.num;            
            return true; //终止遍历
          }
        });
      }else if(val.type=='sub'){
        // 减一操作
        this.list.some(item=>{
          if(item.id == val.id) {
            item.num -= 1;
            return true;
          }
        });
      }else if(val.type=='add'){
        // 加一操作
        this.list.some(item=>{
          if(item.id == val.id) {
            item.num += 1;
            return true;
          }
       });
    }
  }
})
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值