遇见Vue.js

  • 关键词:Vue.js – 读书笔记

一、基本理论基础

MVC

image

MVP

image

MVVM

image

View的变化会自动更新到ViewModel,ViewModel的变化也会自动同步到View显示。

    <div id="didi-navigator">
        <ul>
            <li v-for="tab in tabs">
                
            </li>
        </ul>
    </div>
    <script>
        new Vue({
            el: '#didi-navigator',
            data:{
                tabs: [
                    {text: '巴士'},
                    {text: '快车'}
                ]
            }
        })
    </script>

1.Vue.js的总览

Vue.js不是一个框架–它是只是聚焦视图层,是一个构建数据驱动的Web界面的库(数据绑定和灵活的组件库)。

数据绑定和组件化

    //假设数据
    var object = {
      message: 'Hello World!'
    }
    //DOM
    <div id="example">
      
    </div>
    //我们可以这样
    new Vue({
      el: '#example',
      data: object
    })
    ======================
    //假设数据
    var object1 = {
      message: 'Hello World!'
    }
    var object2 = {
      message: 'Hello World!'
    }
    //我们可以组件化
    var Example = Vue.extend({
        template: '<div></div>',
        data: function () {
            return {
              message: 'Hello Vue.js!'
            }
        }
    })
    // 将该组件注册为 <example> 标签
    Vue.component('example', Example)

模块化

    <!-- MyComponent.vue -->
    <!-- css -->
    <style>
        .message {
          color: red;
        }
    </style>
    <!-- template -->
    <template>
      <div class="message"></div>
    </template>
    <!-- js -->
    <script>
        export default {
            props: ['message'],
            created() {
                console.log('MyComponent created!')
            }
        }
    </script>

路由

    个人感觉vue-router烦的问题是组件之间的数据交互,rootRouter的数据很难向其他组件传递.
    /**
    *解决方法
    **/
    var app = Vue.extend({
      data:function(){
          return {
              data:'',
          };
      },
    });
    router.map({
          '/': {
              component: Vue.extend({
                    mixins: [calendar.mixin],
                    data:function(){
                        return {
                            data:data
                        }
                    }
              })
          },
    })
    router.start(app, '#app');

Vue.js与其他框架的区别

image

2.Vue.js的指令

image

(1) v-if和v-show

v-if是根据表达式的值在DOM中生成或者移除一个元素。

v-show是根据表达式的值来显示或者隐藏HTML元素(v-show不支持template)。

一般来说,v-if有更高的切换消耗,而v-show有更高的初始渲染消耗。因此,如果需要频繁地切换,使用v-show较好;如果在运行时条件不大可能改变,使用v-if较好。

    <body class="native">
        <div id="example">
            //生成或者移除
            <p v-if="greeting">hello</p>
            //style = "display: none"
            <p v-show="greeting">hello</p>
        </div>
    </body>
    <script>
        var example = new Vue({
            el: '#example',
            data: {
                greeting: false
            }
        })
    </script>

(2) v-else

v-else必须跟着v-if或者v-show,充当else的功能。

    <body class="native">
        <div id="example">
            <p v-if="ok">yes</p>
            <p v-else="ok">no</p>
        </div>
    </body>
    <script>
        var example = new Vue({
            el: '#example',
            data: {
                ok: false
            }
        })
    </script>
    //===============================
    //将v-show用在组件上时,因为指令的优先级v-else会出现问题,所以不要这样做。
    <custom-component v-show="condition"></custom-component>
        <p v-else></p>
    //我们可以用另一个v-show代替v-else
    <custom-component v-show="condition"></custom-component>
    <p v-show="!condition"></p>

(3) v-Model(number/lazy/debounce)

v-model是用来在input/select/text/checkbox/radio等表单控件元素上创建双向数据绑定。

根据控件类型,v-model自动选择正确的方法更新元素(有点神奇)。

    <body class="native">
        <form action="">
            <input type="text" v-model="data.name"/>
            <input type="text" v-model="data.sex"/>
            //v-model指令后面可以添加多个参数
            //number(将输入转换为Number类型)
            //lazy(因为加了lazy属性,msg的值一直没有发生变化)
            <input type="text" v-model="mgs" lazy/>
            //debounce(设置一个最小的延时,在每次敲击之后延时同步输入框的值与数据)
            <input type="text" v-model="mgs" debounce="50000"/>
        </form>
    </body>
    <script>
        var example = new Vue({
            el: '#example',
            data: {
                data:{
                    name: "",
                    sex: ""
                },
                msg: '内容是在change事件后才改变的'
            }
        })
    </script>

(4) v-for

v-for指令是基于源数据重复渲染元素,$index来呈现相对应的数组索引。

使用v-for,将得到一个特殊的作用域,我们需要明确指定的props属性传递数据,否则在组件内将获取不到数据。

    <body id="example">
        <ul id="demo">
            <li v-for="item in items" :item="item" :index="$index">
            //
            <li v-for="item in items" class="item-">
                 - 
                
            </li>
        </ul>
    </body>
    <script>
        var demo = new Vue({
            el: '#demo',
            data: {
                items:[
                    parentMessage:'didi',
                    {mgs: '顺风车'},
                    {mgs: '专车'}
                ]
            }
        })
    </script>

源码如下(for.js)

    //parseFor
    type ForParseResult = {
        for: string;
        alias: string;
        iterator1?: string;
        iterator2?: string;
    };
    export function parseFor (exp: string): ?ForParseResult {
        const inMatch = exp.match(/([^]*?)\s+(?:in|of)\s+([^]*)/);
        if (!inMatch) {
            return
        }
        const res = {};
        res.for = inMatch[2].trim();
        const alias = inMatch[1].trim().replace(/^\(|\)$/g, '');
        const iteratorMatch = alias.match(/,([^,\}\]]*)(?:,([^,\}\]]*))?$/);
        if (iteratorMatch) {
            res.alias = alias.replace(/,([^,\}\]]*)(?:,([^,\}\]]*))?$/, '')
            res.iterator1 = iteratorMatch[1].trim();
            if (iteratorMatch[2]) {
                res.iterator2 = iteratorMatch[2].trim();
            }
        } else {
            res.alias = alias
        }
        return res;
    }
    //=====================================================================
    import { parseFor } from 'compiler/parser/index'
    import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'
    export function preTransformVFor (el: ASTElement, options: WeexCompilerOptions) {
      const exp = getAndRemoveAttr(el, 'v-for')
      if (!exp) {
        return
      }
      const res = parseFor(exp)
      if (!res) {
        if (process.env.NODE_ENV !== 'production' && options.warn) {
          options.warn(`Invalid v-for expression: ${exp}`)
        }
        return
      }
      const desc: Object = {
        '@expression': res.for,
        '@alias': res.alias
      }
      if (res.iterator2) {
        desc['@key'] = res.iterator1;
        desc['@index'] = res.iterator2
      } else {
        desc['@index'] = res.iterator1
      }
      delete el.attrsMap['v-for'];
      addRawAttr(el, '[[repeat]]', desc)
    }

当数组数据出现变动时如何检测呢?

源码如下(array.js)

    export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
        Object.defineProperty(obj, key, {
            value: val,
            enumerable: !!enumerable,
            writable: true,
            configurable: true
        })
    }
    //=================================================================
    import { def } from '../util/index'
    const arrayProto = Array.prototype;
    export const arrayMethods = Object.create(arrayProto);
    const methodsToPatch = [
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
    ];
    /**
     * Intercept mutating methods and emit events
     */
    methodsToPatch.forEach(function (method) {
        // cache original method
        const original = arrayProto[method];
        def(arrayMethods, method, function mutator (...args) {
            const result = original.apply(this, args);
            const ob = this.__ob__;
            let inserted;
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args;
                    break
                case 'splice':
                    inserted = args.slice(2);
                    break
            }
            if (inserted) ob.observeArray(inserted);
            // notify change
            ob.dep.notify();
            return result
        });
    });
    {
        items:[
            _uid: '1',
            _uid: '2',
        ]
    }
    //可以这样
    <div v-for="item in items" track-by="_uid"></div>
    //如果没有唯一的键供追踪
    <div v-for="item in items" track-by="$index"></div>
    //或者给对象的键值提供一个别名
    <div v-for="item in items">
        :
    </div>
    <div v-for="(key,item) in items">
        :
    </div>

我们应该尽量避免直接设置数据绑定的数组元素,因为这些变化不会被Vue.js检测到,因而也不会更新视图渲染。

比如:直接用索引设置元素;修改数据的长度等

(5) v-bind

v-bind指令用于响应更新HTML特性,将一个或多个attribute,或者一个组件prop动态绑定到表达式。

    <img v-bind:src = "imageSrc">
    //缩写
    <img :src = "imageSrc">
    //绑定class或者style时
    <div :class = "[classA, {classB: isB, classC: isC}]"></div>
    <script>
        var demo = new Vue({
            el: 'example',
            data:{
                classA: A,
                isB: false,
                isC: true
            }
        })
    </script>
    //没有参数时,可以绑定到一个对象。注意,此时class和style绑定不支持数组和对象。
    <div id="exampleA">
        <div v-bind="{id: someProp, 'otherAttr': otherProp}"></div>
    </div>

    var demo = new Vue({
        el: 'exampleA',
        data:{
            someProp: 'idName',
            otherProp: 'prop'
        }
    })

在绑定prop时,prop必须在子组件中声明。

    <my-component :prop="someThing"></my-component>
    //双向绑定
    <my-component :prop.sync="someThing"></my-component>
    //单向绑定
    <my-component :prop.once="someThing"></my-component>
    //.camel-将绑定的特性名字转换为驼峰命名,只能用于普通HTML特性的绑定。

(6) v-on

用于绑定事件监听器。

image

    //方法处理器
    <button v-on:click="doThis"></button>
    //内联语句
    <button v-on:click="doThat('hello',$event)"></button>
    //缩写
    <button @click="doThis"></button>
    //添加修饰符
    <button @click.stop="doThis"></button>
    <button @click.prevent="doThis"></button>
    //串联
    <button @click.stop.prevent="doThis"></button>

(7) v-ref

在父组件上注册一个子组件的索引,便于直接访问。不需要表达式,必须提供参数id。可以通过父组件的$refs对象访问子组件。

(8) 其他组件v-el、v-pre、v-cloak

v-el为DOM元素注册一个索引,方便通过所属实例的$els访问这个元素。可以用v-el:some-el设置this.$els.someEl。

    <span v-el:msg>hello</span>
    <span v-el:other-msg>world</span>

    this.$els.mgs.textContent // hello
    this.$els.otherMgs.textContent // world

v-pre跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

v-cloak这个指令保持在元素上直到关联实例结束编译。

    //CSS
    [v-cloak] {
      display: none;
    }
    //html
    <div v-cloak>
      
    </div>

(9) v-text

    <span v-text="msg"></span>
    //等价
    <span></span>

(10) v-html

不建议在网站上直接动态渲染任意HTML片段,很容易导致XSS(跨站脚本攻击)攻击。

3.自定义指令

(1) 钩子函数

AngularJS提供了两个函数:compile和link,其中编译函数主要负责将作用域和DOM进行链接,链接函数用来创建可以操作DOM的指令。

Vue.js也允许注册自定义指令。自定义指令提供一种机制将数据的变化映射为DOM行为。

一个指令定义对象可以提供如下几个钩子函数(均为可选):

    Vue.directive('my-directive',{
        bind: function(el, binding, vnode, oldVnode){
        //准备工作
        //例如:添加事件处理器或只需要运行一次的高耗任务
        },
        inserted
        //被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
        update: function(newValue,oldValue){
        //值更新时的工作
        //也会以初始值为参数调用一次
        },
        componentUpdated
        //指令所在组件的 VNode 及其子 VNode 全部更新后调用
        unbind: function(){
        //清理工作,指令与元素解绑时调用
        //例如:删除bind()添加的事件监听器
        }
    })

    //注册之后,可以这样用(添加前缀:-v):
    <div v-my-directive = "someValue"></div>

    //当只需要update时,可以传入一个函数替代定义对象
    Vue.directive('my-directive', function(value){
        //这个函数用作update()
    })

image

image

(2) 指令实例属性

所有的钩子函数,都将被复制到实际的指令对象中,在钩子内this指向这个指令对象。这个对象暴露了一些有用的属性。

image

(3) 对象字面量

    <div id="demoA" v-model="{color: 'vhite', text: 'hello'}"></div>
    <script>
        Vue.directive('demoA', function (value) {
            // 'white'
            console.log(value.color)
            // 'hello'
            console.log(value.text)
        })
    </script>

(4) 字面修饰符

当指令使用了字面修饰符,它的值将按普通字符串处理并传递给update方法。update方法将只调用一次,因为普通字符串不能相应数据变化。

(5) 元素指令

以自定义元素的形式使用指令,而不是以属性的形式。

    //自定义元素指令
    <body id="demo">
        <my-directive class="hello" name="hi"></my-directive>
    </body>
    <script>
        Vue.elementDirective('my-directive', {
            bind: function(){
                console.log(this.el.className);
                console.log(this.el.getAttribute('name'));
            }
        })
    </script>

元素指令不能接受参数或者表达式,但是它可以读取元素的特性,从而决定它的行为。

不同于普通指令,元素指令是终结性的。这意味着,一旦vue遇到一个元素指令,它将跳出该元素及其子元素-只有该元素指令本身可以操作该元素及其子元素。