组件化开发

组件化开发思想

  • 标准
  • 分治
  • 重用
  • 组合
    组件开发规范:Web Components
    通过封装好功能的定制元素(自定义标签)来解决问题
    Vue部分实现来上述规范

组件的注册方式

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

一个具体组件:
      Vue.component('button-counter',{
            data:function(){
                return{
                    count:0
                }
            },
            template:'<button @click="count++" >按钮被点击{{count}}次</button>'
        })

组件是可以重用的,重用的每个组件是相互独立的,他们之间的数据是相互不影响的

注意事项:
1 自定义组件的data必须是一个函数,而Vue实例的data可以是个对象
2 组件内部的模板根元素只应该有一个
3 组件模板内容可以是模板字符串
eg:

template:`
                <button @click='count++'>
                    按钮被点击{{count}}次
                </button>
            `

4 组件的命名方式有短横线方式和驼峰命名方式
如果组件要在别的组件中使用,可以直接按照命名的写法用
如果要在html中直接使用,在命名时无论用的哪种方法,使用时都得时短横线方式

  • 局部组件
//注册一个Vue实例中的局部组件
        var ComponentA = {
            data:function(){
                return {

                }
            },
            template:``
        }
        var vm = new Vue({
            el:'#box',
            components:{
                'component-a':ComponentA
            }
        })

组件之间数据的交互

  • 父组件向子组件传值
    1 组件内部通过props接收传递过来的值
    2 父组件将值通过v-bind绑定到属性上,用以传给子组件


    image.png

    props属性名规则:
    1 在props中使用驼峰形式,在模板中需要使用短横线模式
    2 字符串形式的模版中没有这个限制
    props属性值类型:
    - 字符串
    - 数值
    - 布尔
    - 数组
    - 对象
    在传递数字的时候,如果用了v-bind,传过去的是数值,否则就是string
    传递布尔型的值时,属性可以不加引号,但是推荐加上,而且也需要v-bind绑定

  • 子组件向父组件传值
    props传递数据的原则:单向数据流,只能父传子,不能子传父
    -- 子组件通过自定义事件的方式向父组件传值
    举个例子:
    业务场景:点击子组件的按钮,增大父组件中的值的大小(子组件向父组件传递信息,没有传值)
<body>
    <div id="box">
        <div :style='{fontSize:fontSize+"px"}'>{{msg}}</div>
        <button-son @enlarge-text='handle'></button-son>
    </div>
        <script>
        Vue.component('button-son',{
            template:`
                <button @click='$emit("enlarge-text")'>扩大父组件的字体大小</button>
            `
        })
        var vm = new Vue({
            el:'#box',
            data() {
                return {
                    msg:'Hello world',
                    fontSize:13
                }
            },
            methods: {
                handle:function(){
                    //扩大字体大小
                    this.fontSize ++;
                    console.log(this.fontSize);
                    
                }
            },
        })
        </script>
    
</body>

-- 传值:在emit时传递的第一个参数是自定义事件名,而第二个参数则可以用来传递值,在父组件中用$event来接收值

子组件:
<button @click='$emit("enlarge-text",2)'>扩大父组件的字体大小</button>
父组件:
<button-son @enlarge-text='handle($event)'></button-son>
  • 非父子组件之间传值:
-- 单独的事件中心管理组件间的通信
var eventBus = new Vue();
-- 监听事件与销毁事件
eventBus.$on('add-todo',addtodo);
eventBus.$off('add-todo')
-- 触发事件
eventBus.$emit('add-todo',id)
image.png

实例:在一个父组件中有两个子组件,都是button和data,点击第一个子组件的button,第二个子组件的data改变,点击第二个子组件的button,第一个子组件的data改变。

<body>
    <div id="box">
        <p>father</p>
        <button v-on:click='handle'>销毁事件</button>
        <button-a></button-a>
        <button-b></button-b>
    </div>
    <script>
        //事件中心
        var eventBus = new Vue()

        Vue.component('button-a',{
            data:function(){
                return {
                    msg:0
                }
            },
            template:`
            <div>   
                <div>A:{{msg}}</div>
                <button @click='handle'>给老弟加2</button>
            </div> 
            `,
            methods: {
                handle:function(){
                    eventBus.$emit('b-event',2)
                }
            },
            mounted() {
                //监听事件
                eventBus.$on('a-event',(val)=>{
                    this.msg += val
                })
            },
        })
        Vue.component('button-b',{
            data:function(){
                return {
                    msg:0
                }
            },
            template:`
            <div>
                <div>B:{{msg}}</div>
                <button @click='handle'>给老哥加3</button>
            </div>
            `,
            methods: {
                handle:function(){
                    //触发兄弟组件的事件
                    eventBus.$emit('a-event',3)
                }
            },
            mounted() {
                //监听事件
                eventBus.$on('b-event',(val)=>{
                    this.msg += val
                })
            },
        })
        var vm = new Vue({
            el:'#box',
            data:{

            },
            methods: {
                handle:function(){
                    eventBus.$off('a-event')
                    eventBus.$off('b-event')
                }
            },
        })
    </script>
</body>

组件插槽的用法

在之前的自定义组件中,我们在使用的时候都是没有具体值的,比如:

<a-button><a-button>

可如果我们想要这样使用呢?:

<a-button>click me</a-button>

这时候,我们要使用插槽
我们在模板字符串中定义组件的dom时,可以预留<slot></slot>,
在使用组件时输入的值就在slot中
eg:

        <my-com>I don't know...</my-com>
...
        Vue.component('my-com',{
            template:`
            <div>   
                <strong>Error : </strong>
                <slot></slot>
            </div> 
            `
        })

-- 具名插槽
定义方法:

template:`
          <div>   
              <div>
              <slot name='header'></slot>
              </div>
              <strong>Error : </strong>
              <div><slot></slot></div>
              <div>
              <slot name='footer'></slot>    
              </div>
          </div> 
          `

使用:

<my-com>
            <div slot="header">标题信息</div>
            I don't know...
            <div slot="footer">底部信息</div>
 </my-com>

给了name的插槽对号入座,没有给name的,放入默认插槽中

-- 作用域插槽:
应用场景:父组件可以对子组件的内容进行加工处理
例子:子组件是个水果列表,但是高亮内容和文本内容应该是动态的

        <fruit-list v-bind:list='list'>
            <template slot-scope="slotProps">
                <strong v-if='slotProps.info.id==2' class="orange">{{slotProps.info.name}}</strong>
                <span v-else>{{slotProps.info.name}}</span>
            </template>
        </fruit-list>
//组件定义
        Vue.component('fruit-list',{
            props:['list'],
            
            template:`
                <div>
                    <li :key='item.id' v-for='item in list'>
                        <slot :info='item'>{{item.name}}</slot>    
                    </li>                        
                </div>
            `
        })

Vue组件调试工具的用法

组件在浏览器中会被渲染成原始的DOM,我们在浏览器的检查工具上只能看到已经渲染好了的DOM,不便于调试
官方调试工具,dev-tools,直接下载安装chrome插件即可
https://github.com/vuejs/vue-devtools

基于组件方式实现业务功能

一个小例子:

<body>
    <script>
        /*
        1 组件化划分「
                    1.标题组件(展示文本)
                    2.列表组件(列表展示、商品数量变更、商品删除)
                    3.结算组件(计算商品总额)
                    」
        
        */
    </script>
    <div id="box">
        <div id="containner">
            <my-cart></my-cart>
        </div>
    </div>
    <script>
        var cartTitle = {
            props:['uname'],
            template:`
            <div class='title'>{{uname}}的商品</div>
            `
        }
        var list = {
            props:['list'],
            template:`
                <div>
                    <div :key=item.id  v-for='item in list'>
                        <img :src="item.img" alt="">
                        <div>{{item.name}}</div>
                        <div class='change'>
                            <a class='a' href="" @click.prevent='sub(item.id)'>-</a>    
                            <input type="text" class='num' :value='item.num' @blur='changeNum(item.id,$event)'/>    
                            <a class='a' href="" @click.prevent='add(item.id)'>+</a>    
                            <button class='delete' @click='del(item.id)'>✖️</button>
                        </div>
                        
                    </div>    
                </div>
            `,
            methods: {
                changeNum:function(id,event){
                    this.$emit('numChange',{id:id,num:parseInt( event.target.value),type:'change'})
                    //console.log(id,parseInt( event.target.value));
                    //新的值是event.target.value
                },
                del:function(id){
                    this.$emit('cart-del',id)
                },
                sub:function(id){
                    this.$emit('numChange',{
                        id:id,
                        type:'sub'
                    })
                },
                add:function(id){
                    this.$emit('numChange',{
                        id:id,
                        type:'add'
                    })
                }
            },
        }
        var totol = {
            props:['list'],
            template:`
            <div class='totol'>
                <span>{{result}}</span>    
                <button>结算</button>
            </div>
            `,
            computed: {
                result:function(){
                    var totolNum = 0;
                for(var i = 0;i<this.list.length;i++){
                    totolNum += this.list[i].num * this.list[i].price;                   
                }
                // console.log(this.list.length);
                // console.log(totolNum);
                
                return totolNum;
                }
            }
        }
        Vue.component('my-cart',{
            data:function(){
                return{
                    list:[
                    {id:1,name:'tcl',img:'',num:1,price:100},
                    {id:2,name:'tl',img:'',num:1,price:1100},
                    {id:3,name:'tfel',img:'',num:1,price:1200},
                    {id:4,name:'gdgmkll',img:'',num:1,price:1400},
                    {id:5,name:'fghl',img:'',num:2,price:1900}
                ],
                uname:'sam'
                }
            },
            template:`
                <div class='cart'>
                    <cart-title :uname='uname'></cart-title>    
                    <cart-list :list='list' @numChange='changeNum($event)' @cart-del='delCart($event)'></cart-list>
                    <cart-totol :list='list'></cart-totol>
                </div>
            `,
            components:{
                'cart-title':cartTitle,
                'cart-list':list,
                'cart-totol':totol
            },
            methods:{
                delCart:function(id){
                    console.log(id);
                    //根据ID删除list中对应的数据
                    //1 根据id找到需要删除的数据的索引
                    var index = this.list.findIndex(item=>{
                        return item.id == id
                    })
                    //2 根据索引删除对应的数据
                    this.list.splice(index,1)
                },
                changeNum:function(obj){
                    //根据子组件传递来的数据,更新list的数据
                    // for(var i = 0; i< this.list.length;i++){
                    //     if(this.list[i].id == obj.id){
                    //         this.list[i].num = obj.num
                    //     }
                    // }
                    if(obj.type == 'change'){
                       this.list.some(item=>{
                        if(item.id == obj.id){
                         item.num = obj.num
                         return true
                        }
                    })
                    //some这个API用起来遍历,简洁了很多
                    }else if(obj.type == 'sub'){
                        this.list.some(item=>{
                            if(item.id == obj.id&&item.num!=1){
                                item.num -= 1;
                                return true
                            }
                        })
                    }
                    else{
                        this.list.some(item=>{
                            if(item.id == obj.id){
                                item.num += 1
                                return true
                            }
                        })

                    }
                    
                }
            }
        })
        var vm = new Vue({
            el:'#box',
            data:{
                
            },

        })
    </script>
</body>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容