渐进式框架 - 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创建实例时经过一系列初始化过程。

如:设置数据监听、编译模板、将实例挂载到DOM。

在数据变化时更新DOM:

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在组件方面的抽象,与其他框架的组件概念并无二致,一个组件本质上是一个拥有预定义选项的实例。

定义:

// 定义一个名为 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>

就像一个html中只有一个body节点。

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

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

模板语法

用过Handlebars模板库的都深有体会,胜在足够简洁。

模板与数据处理逻辑完全分离,足够简洁的模板结合原生js应对复杂场景。

vue模板提倡将计算转移到逻辑处理单元,而非模板中。

Demo.vue

<template>
 <div class="container">
</div>
</template>
//第二部分
<script>
import {Uploader} from "vant";
export default {
  name: "Demo",
  props: {},
 created() {},
  data() {
      return {}
  },
  methods: {
    cancel() {
      //to do
    },
  },
 };
</script>
//第三部分,可选
<style scoped>
</style>

指令

与其他框架一样,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>

原样输出html元素。

v-model

<div id="watch-example">
    <input v-model="question">
</div>

自定组件中比较有用,v-model是v-on和v-bind组合的简写。

vue父子组件间传递值遵循如下规则:

子组件使用porps定义传值的属性,父组件通过属性传递值。

父组件监听指定事件,子组件使用事件向父组件传递值。

如此,子组件改变父组件数据的行为受得明确限制。

Children.vue

<template>
  <div class="children"></div>
</template>
<script>
export default {
  name: "Children",
  props: {
  	 a:String  
    }
}
</script>

说明:子组件定义了一个a属性。

父组件:

<template>
  <div class="parent">
     <Children :a="c"/>
  </div>
</template>
<script>
import Children from "./Children";
export default {
  name: "父组件",
  props: {},
    components: {
     Children
    },
    data(){
       return {
            c:'这是给子组件准备的值'
        }
    }
  }
  </script>

上面两步完成了父组件向子组件传递数据的需求。

子组件要向父组件传递就得发送事件:

//子组件
bus.$emit("事件", {});
//父组件
bus.$on("事件", val=>{

});

这种写法有其灵活性,就传值这事儿而言太啰嗦。

所以,v-model会默认给父组件添加一个监听器,监听input事件,子组件发送input事件就可以了。

v-model默认使用子组件的value属性传递值。

<template>
  <div class="parent">
	  <Children v-model="c"/>
  </div>
</template>

watch

watch是手动方式监视一个值是否发生变化:

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

vue插槽/slot

向一个组件传递内容可使用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>

属性传递

静态传递

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

动态传递

<blog-post v-bind:title="post.title"></blog-post>

数据单向性和引用回逆

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,子组件中修改对象或数组将会影响父组件的状态。