Vue学习记录

Vue.js(读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。

安装

  • 安装 Node 略…
  • 安装脚手架 npm install --g vue-cli
  • 基于模板初始化项目 vue init webpack my-project
  • 切换到目录并且安装依赖 cd my-project && npm install
  • 调试 npm run dev
  • 编译 npm run build

起步

组件化应用构建

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

  • 定义一个最简单的组件

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

    1
    2
    3
    4
    <ol>
    <!-- 创建一个 todo-item 组件的实例 -->
    <todo-item></todo-item>
    </ol>
  • 定义一个带属性的组件

    1
    2
    3
    4
    5
    6
    7
    Vue.component('todo-item', {
    // todo-item 组件现在接受一个
    // "prop",类似于一个自定义属性
    // 这个属性名为 todo。
    props: ['todo'],
    template: '<li>{{ todo.text }}</li>'
    })
  • 使用一个带属性的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <div id="app-7">
    <ol>
    <!-- 现在我们为每个todo-item提供待办项对象 -->
    <!-- 待办项对象是变量,即其内容可以是动态的 -->
    <todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
    </ol>
    </div>
    Vue.component('todo-item', {
    props: ['todo'],
    template: '<li>{{ todo.text }}</li>'
    })
    var app7 = new Vue({
    el: '#app-7',
    data: {
    groceryList: [
    { text: '蔬菜' },
    { text: '奶酪' },
    { text: '随便其他什么人吃的东西' }
    ]
    }
    })

属性与方法

  • 每个 Vue 实例都会代理其 data 对象里所有的属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var data = { a: 1 }
    var vm = new Vue({
    data: data
    })
    vm.a === data.a // -> true
    // 设置属性也会影响到原始数据
    vm.a = 2
    data.a // -> 2
    // ... 反之亦然
    data.a = 3
    vm.a // -> 3

    注意只有这些被代理的属性是响应的。如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。我们将在后面详细讨论响应系统。

  • 除了 data 属性, Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀 $,以便与代理的 data 属性区分。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var data = { a: 1 }
    var vm = new Vue({
    el: '#example',
    data: data
    })
    vm.$data === data // -> true
    vm.$el === document.getElementById('example') // -> true
    // $watch 是一个实例方法
    vm.$watch('a', function (newVal, oldVal) {
    // 这个回调将在 `vm.a` 改变后调用
    })

    注意,不要在实例属性或者回调函数中(如 vm.$watch(‘a’, newVal => this.myMethod()))使用箭头函数。因为箭头函数绑定父级上下文,所以 this 不会像预想的一样是 Vue 实例,而是 this.myMethod 未被定义。

实例生命周期

组件生命周期

模板语法

插值

  • 文本
    • 绑定渲染 <span>Message: </span>
    • 一次性渲染 <span v-once>This will never change: </span>
  • 纯HTML
    • <div v-html="rawHtml"></div> 注意防范XSS攻击

      渲染的模板不会进行数据绑定 如果需要请使用组件实现

  • 属性

    • 使用 v-bind 绑定 <div v-bind:id="dynamicId"></div>
    • 对于 boolean 类型的数据 如果结果为 false 则属性会被移除
      1
      <button v-bind:disabled="isButtonDisabled">Button</button>
  • 使用 JavaScript 表达式

    • 可以被支持的语法
      1
      2
      3
      4
      {{ number + 1 }}
      {{ ok ? 'YES' : 'NO' }}
      {{ message.split('').reverse().join('') }}
      <div v-bind:id="'list-' + id"></div>

    每个绑定只能包含单个表达式

    • 错误的语法
      1
      2
      3
      4
      <!-- 这是语句,不是表达式 -->
      {{ var a = 1 }}
      <!-- 流控制也不会生效,请使用三元表达式 -->
      {{ if (ok) { return message } }}

模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不应该在模板表达式中试图访问用户定义的全局变量。

指令

指令(Directives)是带有 v- 前缀的特殊属性 其预期值是 单一 JavaScript 表达式

  • 参数

    • 指令可以接受参数 在指令后用 : 指定

      • v-bind 绑定属性

        1
        <a v-bind:href="url"></a>
      • v-on 绑定方法

        1
        <a v-on:click="doSomething">
  • 修饰符

    • 修饰符(Modifiers)是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
      • 例如 .prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()
        1
        <form v-on:submit.prevent="onSubmit"></form>

过滤器

Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化。

1
2
3
4
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>

Vue 2.x 中,过滤器只能在 mustache 绑定和 v-bind 表达式(从 2.1.0 开始支持)中使用,因为过滤器设计目的就是用于文本转换。为了在其他指令中实现更复杂的数据变换,你应该使用计算属性。

  • 过滤器函数总接受表达式的值作为第一个参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    new Vue({
    filters: {
    capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
    }
    }
    })
  • 过滤器可以串联

    1
    {{ message | filterA | filterB }}
  • 过滤器可以传参(由于第一个参数为传入值 所以 参数往后延续)

    1
    2
    // arg1 实际上为 filterA 方法的第二个参数 以此类推
    {{ message | filterA('arg1', arg2) }}

缩写

  • v-bind 缩写

    1
    2
    3
    4
    <!-- 完整语法 -->
    <a v-bind:href="url"></a>
    <!-- 缩写 -->
    <a :href="url"></a>
  • v-on 缩写

    1
    2
    3
    4
    <!-- 完整语法 -->
    <a v-on:click="doSomething"></a>
    <!-- 缩写 -->
    <a @click="doSomething"></a>

计算属性(computed)

  • Computed vs Methods

    • Computed 是基于它们的依赖进行缓存的 Computed 只有在它的相关依赖发生改变时才会重新求值
    • Method 只要发生重新渲染 则始终会执行该函数
  • Computed 属性 vs Watched 属性 (简化代码)

    • 使用 watch

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      var vm = new Vue({
      el: '#demo',
      data: {
      firstName: 'Foo',
      lastName: 'Bar',
      fullName: 'Foo Bar'
      },
      watch: {
      firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
      },
      lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
      }
      }
      })
    • 使用 computed

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      var vm = new Vue({
      el: '#demo',
      data: {
      firstName: 'Foo',
      lastName: 'Bar'
      },
      computed: {
      fullName: function () {
      return this.firstName + ' ' + this.lastName
      }
      }
      })
  • 计算属性添加 Setter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    computed: {
    fullName: {
    // getter
    get: function () {
    return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
    var names = newValue.split(' ')
    this.firstName = names[0]
    this.lastName = names[names.length - 1]
    }
    }
    }

观察 Watchers

  • 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的 watcher 。这是为什么 Vue 提供一个更通用的方法通过 watch 选项,来响应数据的变化。当你想要在数据变化响应时,执行异步操作或开销较大的操作,这是很有用的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <div id="watch-example">
    <p>
    Ask a yes/no question:
    <input v-model="question">
    </p>
    <p>{{ answer }}</p>
    </div>
    <!-- Since there is already a rich ecosystem of ajax libraries -->
    <!-- and collections of general-purpose utility methods, Vue core -->
    <!-- is able to remain small by not reinventing them. This also -->
    <!-- gives you the freedom to just use what you're familiar with. -->
    <script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
    <script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script>
    <script>
    var watchExampleVM = new Vue({
    el: '#watch-example',
    data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
    },
    watch: {
    // 如果 question 发生改变,这个函数就会运行
    question: function (newQuestion) {
    this.answer = 'Waiting for you to stop typing...'
    this.getAnswer()
    }
    },
    methods: {
    // _.debounce 是一个通过 lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问yesno.wtf/api的频率
    // ajax请求直到用户输入完毕才会发出
    // 学习更多关于 _.debounce function (and its cousin
    // _.throttle), 参考: https://lodash.com/docs#debounce
    getAnswer: _.debounce(
    function () {
    var vm = this
    if (this.question.indexOf('?') === -1) {
    vm.answer = 'Questions usually contain a question mark. ;-)'
    return
    }
    vm.answer = 'Thinking...'
    axios.get('https://yesno.wtf/api')
    .then(function (response) {
    vm.answer = _.capitalize(response.data.answer)
    })
    .catch(function (error) {
    vm.answer = 'Error! Could not reach the API. ' + error
    })
    },
    // 这是我们为用户停止输入等待的毫秒数
    500
    )
    }
    })
    </script>