Vue.js渐进式框架重点介绍

Vue.js框架之所以受到追捧,与当下流行的分批加载资源,渐进式渲染页面的架构模式密不可分。

渐进式页面渲染

Vue.js使用简单直观的对象结构,替代复杂多变的数据模型,使用原生html作为模板,并优化DOM操作,以此降低学习曲线,减少开发工作量。

vue数据模型

Vue.js利用Object.defineProperty函数,将传入的数据对象遍历后,为每个属性增加一对getter/setter方法,一旦对属性进行赋值运算和读取操作时,vue就会观察到数据的变化,并开启一个队列,缓存一个事件引发的数据改变,然后,利用Promise.then或setTimeout(fn, 0)异步更新DOM。Vue利用缓存去除重复数据,以避免不必要的计算和DOM操作。

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  {{ message }}
</div>
<script type="text/javascript">
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      }
    });
</script>

vue实例生命周期和回调函数

vue生命周期

每个Vue实例在被创建时都要经过一系列的初始化过程,例如:设置数据监听、编译模板、将实例挂载到DOM并在数据变化时更新DOM等。
在这个过程中vue留出一些回调函数,方便在不同阶段添加处理逻辑。

new Vue({
  data: {
    a: 1
  },
  created: function () {
    console.log('a is: ' + this.a)
  },
  mounted: function () {
      this.$nextTick(function () {
       //el被新创建的vm.$el替换,并挂载到实例以后调用此函数。
      })
  },
  updated: function () {
      this.$nextTick(function () {
        //数据变化导致虚拟DOM重新渲染后,调用该函数。
      })
  },
  destroyed: function(){
      //Vue实例销毁后调用。
  }

)

回调函数中this指向调用它的Vue实例。

Vue组件化

JavaScript弱类型特性在构建大型应用时,可维护性比起强类型语言相去甚远,通过功能组件化缓解这一问题,大多主流前端框架在这方面都有所考虑。vue虽然在组件方面也做出了抽象,但与其他框架的组件化概念并无不同,一个组件本质上是一个拥有预定义选项的实例。

注册一个Vue组件

// 定义一个名为 todo-item 的组件
Vue.component('todo-item', {
  template: '<li>Hello Word!</li>'
})
<ol>
  <!--使用 todo-item 组件 -->
  <todo-item></todo-item>
</ol>

根据W3C规范,组件名字母应全部小写且必须包含一个连字符,此命名方式称之为kebab-case(横线命名法)。

Vue.component('my-component-name', { /* ... */ })
<my-component-name>

当然,也使用PascalCase(帕斯卡命名法)命名原则。

Vue.component('MyComponentName', { /* ... */ })
<MyComponentName>

尽管如此,如果直接在DOM (即非字符串的模板) 中使用组件,只能使用kebab-case方式的命名。与其他前端框架组件化一样,vue组件也需要通过根元素进行实例化。

<div id="app">
  {{ message }}
  <ol>
    <todo-item></todo-item>
  </ol>
</div>
<script type="text/javascript">
    Vue.component('todo-item', {
      template: '<li>Hello Word!</li>'
    })

    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      }
    });
</script>

vue模板语法

用过Handlebars模板库的可能都有这样的体会,胜在足够简洁。模板与数据处理逻辑完全分离,足够简洁的模板结合原生js应对复杂场景。

vue模板也提倡把计算转移到逻辑处理单元,而非在模板中融入逻辑。

vue指令

与其他框架一样,vue通过扩展html属性,实现一些自有的特性。

v-bind

v-bind:[id][disabled][title][HTML原生属性]
<a v-bind:[attributeName]="url"> ... </a>
<a v-bind:href="url">...</a>

v-if

<ul v-if="items.length">

v-for

<li v-for="item in items">{{ item.name }}</li>

v-on

<a v-on:[eventName]="doSomething"> ... </a>
<a v-on:click="doSomething">...</a>
<a @click="doSomething">...</a>
<form v-on:submit.prevent="onSubmit">...</form>

v-html

<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

v-model

<div id="watch-example">
    <input v-model="question">
</div>
new Vue({
    el: '#watch-example',
      data: {
        question: ''
      },
    watch:{
        // 如果 `question` 发生改变,这个函数就会运行
        question: function (newQuestion, oldQuestion) {
         //todo
        }
    }
})

vue插槽(slot)

vue向一个组件传递内容可以使用slot:

<todo-item v-bind:value="12">
    <todolist></todolist>
</todo-item>

Vue.component('todo-item', {
  template: '<ol><slot></slot></ol>'
})

Vue.component('todolist', {
  template: '<li>Hello</li>'
})
<ol value="12"><li>Hello</li></ol>

vue属性传递

传递静态属性

<blog-post title="My journey with Vue"></blog-post>

传递动态属性

<blog-post v-bind:title="post.title"></blog-post>
post: {
  id: 1,
  title: 'My Journey with Vue'
}

vue数据单向性和引用回逆

vue所有prop之间都是单向下行绑定,父级prop的更新会向下流动到子组件中,父级组件发生更新时,子组件中所有的prop都将被刷新。但反过来则不行。以防止子组件意外改变父级组件的状态,导致应用数据流向难以理解。使用prop分为两种场景:

prop为一个初始值,子组件将其值当作本地prop使用。

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

prop为一个原始值,需要进行转换。

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

由于在JavaScript中对象和数组通过引用传递,所以对于数组或对象类型的prop,在子组件中修改对象或数组将会影响到父组件的状态。

当data为function而非字面量对象时,组件的每个实例都有机会获得一个属于自己的值,因为,可以在function中根据传入prop计算一个结果。