zoukankan      html  css  js  c++  java
  • 07.vue-charp-07 组件详解(二)

    其它

    $nextTick

    Vue一个重要的概念:异步更新队列。

    Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作。所以如果你用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,这固然是一个很大的开销。
    Vue会根据当前浏览器环境优先使用原生的Promise.then和MutationObserver,如果都不支持,就会采用setTimeout 代替。

    $nextTick就是用来知道什么时候DOM更新完成的:

    <body>
        <div id="app">
            <div id="div" v-if="showDiv">这是一段文本</div>
            <button @click="getText">获取 div内容</button>
        </div>
        <script src="../lib/vue.2.6.11.js"></script>
        <script>
            var app = new Vue({
                el: '#app',
                data: {
                    showDiv: false
                },
                methods: {
                    getText: function () {
                        this.showDiv = true;
                        //Vue在观察到数据变化时并不是直接更新DOM
                        //let text1 = document.getElementById('div').innerHTML;
                        //console.log(text1);
                        this.$nextTick(function () {
                            let text2 = document.getElementById('div').innerHTML;
                            console.log(text2);
                        });
                    }
                }
            });
        </script>
    </body>
    

    X-Templates

    果你没有使用webpack、gulp等工具,试想一下你的组件template的内容很冗长、复杂,如果都在JavaScript里拼接字符串,效率是很低的,因为不能像写HTML 那样舒服。Vue提供了另一种定义模板的方式,在<script> 标签里使用text/x-template类型,并且指定一个id,将这个id赋给template。

    
    <body>
        <div id="app">
            <my-component></my-component>
            <script type="text/x-template" id="my-component">
                <div>这是组件的内容</div>
                <div>appMsg={{appMsg}}</div>
                <div>cptMsg={{cptMsg}}</div>
            </script>
        </div>
        <script src="../lib/vue.2.6.11.js"></script>
        <script>
            Vue.component('my-component', {
                template: '#my-component',
                data: function () {
                    return {
                        cptMsg: "子组件的内容"
                    }
                }
            });
            var app = new Vue({
                el: '#app',
                data: {
                    appMsg: "父组件的内容"
                }
            });
        </script>
    </body>
    

    ​ 但是,这种方式,x-template中无法识别父组件和子组件中的变量

    手动挂载实例

    我们现在所创建的实例都是通过new Vue()的形式创建出来的。在一些非常特殊的情况下,我们需要动态地去创建Vue实例,Vue提供了Vue.extend和$mount 两个方法来手动挂载一个实例。
    Vue.extend是基础Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象。
    如果Vue实例在实例化时没有收到el选项,它就处于“未挂载”状态,没有关联的DOM元素。可以使用$mount()手动地挂载一个未挂载的实例。这个方法返回实例自身,因而可以链式调用其他实例方法。

    动挂载实例(组件)是一种比较极端的高级用法,在业务中几乎用不到,只在开发一些复杂的独立组件时可能会使用,所以只做了解就好。

    示例:数组输入框

    数字输入框只能输入数字,而且有两个快捷按钮,可以直接减 1或加1。除此之外,还可以设置初始值、最大值、最小值,在数值改变时,触发一个自定义事件来通知父组件。

    • index.html
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title>Vue 示例:数字输入框</title>
    </head>
    
    <body>
        <div id="app">
            <input-number v-model="value" :max="10" :min="0" @on-change="onValueChange"></input-number>
        </div>
    
        <script src="../../lib/vue.2.6.11.js"></script>
        <script src="input-number.js"></script>
        <script src="index.js"></script>
    </body>
    
    </html>
    
    • index.js
    
    var app = new Vue({
        el: '#app',
        data: {
            value: 0
        },
        methods: {
            onValueChange: function () {
                console.log('onValueChange->', this.value);
            }
        }
    });
    
    • input-number.js
    Vue.component('input-number', {
        template: '
            <div class="input-number"> 
                <input 
                    type="text" 
                    :value="currentValue" 
                    @change="handleChange"> 
                <button 
                    @click="handleDown" 
                    :disabled="currentValue <= min">-</button> 
                <button 
                    @click="handleUp" 
                    :disabled="currentValue >= max">+</button> 
            </div>',
        props: {
            max: {
                type: Number,
                default: Infinity
            },
            min: {
                type: Number,
                default: -Infinity
            },
            value: {
                type: Number,
                default: 0
            }
        },
        data: function () {
            return {
                currentValue: this.value
            }
        },
        methods: {
            handleDown: function () {
                if (this.currentValue <= this.min) return;
                this.currentValue -= 1;
            },
            handleUp: function () {
                if (this.currentValue >= this.max) return;
                this.currentValue += 1;
            },
            updateValue: function (val) {
                if (val > this.max) val = this.max;
                if (val < this.min) val = this.min;
                this.currentValue = val;
            },
            handleChange: function (event) {
                var val = event.target.value.trim();
    
                var max = this.max;
                var min = this.min;
    
                if (isValueNumber(val)) {
                    val = Number(val);
                    this.currentValue = val;
    
                    if (val > max) {
                        this.currentValue = max;
                    } else if (val < min) {
                        this.currentValue = min;
                    }
                } else {
                    event.target.value = this.currentValue;
                }
            }
        },
        watch: {
            currentValue: function (val) {
                this.$emit('input', val);
                this.$emit('on-change', val);
            },
            value: function (val) {
                this.updateValue(val);
            }
        },
        mounted: function () {
            this.updateValue(this.value);
        }
    });
    
    

    watch

    watch选项用来监听某个prop或data的改变,当它们发生变化时,就会触发watch配置的函数

    在本例中,我们要监听两个量:value和currentValue。监听value是要知晓从父组件修改了value,监听currentValue是为了当currentValue 改变时,更新value。

    watch监听的数据的回调函数有2个参数可用,第一个是新的值,第二个是旧的值,这里没有太复杂的逻辑,就只用了第一个参数。

    input事件与v-model

    监听currentValue的回调里,this.$emit('input', val)是在使用v-model时改变value的;this.$emit('on-change', val)是触发自定义事件on-change

    组件的为什么不用v-model进行双向绑定

    这里绑定的currentValue也是单向数据流,并没有用v-model,所以在输入时,currentValue的值并没有实时改变。如果输入的不是数字(比如英文和汉字等),就将输入的内容重置为之前的currentValue。如果输入的是符合要求的数字,就把输入的值赋给currentValue。

    示例:Tab组件

    • index.html
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title>Vue 示例:Tab组件</title>
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    
    <body>
        <div id="app" v-cloak>
            <tabs v-model="activeKey">
                <pane label="标签一" name="1">
                    标签一的内容
                </pane>
                <pane label="标签二" name="2">
                    标签二的内容
                </pane>
                <pane label="标签三" name="3">
                    标签三的内容
                </pane>
            </tabs>
        </div>
    
        <script src="../../lib/vue.2.6.11.js"></script>
        <script src="pane.js"></script>
        <script src="tab.js"></script>
        <script src="index.js"></script>
    </body>
    
    </html>
    
    • index.js
    var app = new Vue({
        el: '#app',
        data: {
            activeKey: '1'
        }
    });
    
    • tabs.js
    Vue.component('tabs', {
        template: '
        <div class="tabs"> 
            <div class="tabs-bar">
               <div class="tabs-bar"> 
                    <div :class="tabCls(item)" v-for="(item, index) in navList"  @click="handleChange(index)"> 
                        {{ item.label }} 
                    </div> 
                </div> 
            </div> 
            <div class="tabs-content"> 
                <slot></slot> 
            </div> 
        </div>',
        props: {
            value: {
                type: [String, Number]
            }
        },
        data: function () {
            return {
                // 用于渲染 tabs的标题
                navList: [],
                // 因为不能修改 value,所以复制一份自己维护
                currentValue: this.value,
            }
        },
        methods: {
            getTabs() {
                // 通过遍历子组件,得到所有的pane组件
                return this.$children.filter(function (item) {
                    return item.$options.name === 'pane';
                });
            },
            updateNav() {
                this.navList = [];
                // 设置对this的引用,在function 回调里,this指向的并不是Vue实例
                var _this = this;
    
                this.getTabs().forEach(function (pane, index) {
                    _this.navList.push({
                        label: pane.label,
                        name: pane.name || index
                    });
                    // 如果没有给pane 设置 name,默认设置它的索引
                    if (!pane.name) pane.name = index;
                    // 设置当前选中的tab的索引,在后面介绍
                    if (index === 0) {
                        if (!_this.currentValue) {
                            _this.currentValue = pane.name || index;
                        }
                    }
                });
    
                this.updateStatus();
            },
            updateStatus() {
                var tabs = this.getTabs();
                var _this = this;
                // 显示当前选中的tab对应的pane组件,隐藏没有选中的
                tabs.forEach(function (tab) {
                    return tab.show = tab.name === _this.currentValue;
                })
            },
            tabCls: function (item) {
                return [
                    'tabs-tab',
                    {
                        //给当前选中的tab 加一个class
                        'tabs-tab-active': item.name === this.currentValue
                    }
                ]
            },
            // 点击 tab 标题时触发
            handleChange: function (index) {
                var nav = this.navList[index];
                var name = nav.name;
                // 改变当前选中的tab ,并触发下面的watch
                this.currentValue = name;
                // 更新value
                this.$emit('input', name);
                // 触发一个自定义事件,供父级使用
                this.$emit('on-click', name);
            }
        },
        watch: {
            value: function (val) {
                this.currentValue = val;
            },
            currentValue: function () {
                //在当前选中的tab 发生变化时,更新pane的显示状态
                this.updateStatus();
            }
        }
    
    })
    
    • pane.js
    Vue.component('pane', {
        name: 'pane',
        template: '
            <div class="pane" v-show="show"> 
                <slot></slot> 
            </div>',
        props: {
            name: {
                type: String
            },
            label: {
                type: String,
                default: ''
            }
        },
        data: function () {
            return {
                show: true
            }
        }
        , methods: {
            updateNav() {
                this.$parent.updateNav();
            }
        },
        watch: {
            label() {
                this.updateNav();
            }
        },
        mounted() {
            this.updateNav();
        }
    });
    
    • style.css
    [v-cloak] {
        display: none;
    }
    .tabs{
        font-size: 14px;
        color: #657180;
    }
    .tabs-bar:after{
        content: '';
        display: block;
         100%;
        height: 1px;
        background: #d7dde4;
        margin-top: -1px;
    }
    .tabs-tab{
        display: inline-block;
        padding: 4px 16px;
        margin-right: 6px;
        background: #fff;
        border: 1px solid #d7dde4;
        cursor: pointer;
        position: relative;
    }
    .tabs-tab-active{
        color: #3399ff;
        border-top: 1px solid #3399ff;
        border-bottom: 1px solid #fff;
    }
    .tabs-tab-active:before{
        content: '';
        display: block;
        height: 1px;
        background: #3399ff;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
    }
    .tabs-content{
        padding: 8px 0;
    }
    
  • 相关阅读:
    委托和异步方法
    线程池_ThreadPool
    委托_deleget
    一步一步实现视频播放器client(二)
    mysql忘记password
    POJ 2456 Aggressive cows (二分 基础)
    Fragment小结
    Cocos2d-x粒子系统
    淘宝数据库OceanBase SQL编译器部分 源代码阅读--解析SQL语法树
    C与C++在形參的一点小差别
  • 原文地址:https://www.cnblogs.com/easy5weikai/p/13257723.html
Copyright © 2011-2022 走看看