Vue深度学习总结

1. Vue生命周期解读

这一部分参考自Vue官方文档以及生命周期详解

官网的生命周期图示例如下:
Vue深度学习总结_第1张图片
测试代码如下:


<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue生命周期学习title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
    <div id="app">
      <h1>{{message}}h1>
      <br/><br/><br/>
      
    div>
  body>
  <script>
    var vm = new Vue({
      // 注释el,可理解beforeMounted之前的生命周期
      el: '#app',
      // 同步不注释h2定义的template,可理解template是如何加载
      // template: "

{{message +'这是在template中的'}}

",
data: { message: 'Vue的生命周期' }, beforeCreate: function() { console.group('------beforeCreate创建前状态------'); console.log("%c%s", "color:red" , "el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //undefined console.log("%c%s", "color:red","message: " + this.message) }, created: function() { console.group('------created创建完毕状态------'); console.log("%c%s", "color:red","el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeMount: function() { console.group('------beforeMount挂载前状态------'); console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, mounted: function() { console.group('------mounted 挂载结束状态------'); console.log("%c%s", "color:red","el : " + this.$el); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeUpdate: function () { console.group('beforeUpdate 更新前状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, updated: function () { console.group('updated 更新完成状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, beforeDestroy: function () { console.group('beforeDestroy 销毁前状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, destroyed: function () { console.group('destroyed 销毁完成状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message) } })
script> html>

在浏览器中打开,控制台输出如下图所示:
Vue深度学习总结_第2张图片

多次测试后,总结如下:


  • Vue用法:
    • Vue实例中钩子函数有:beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed.
    • 在创建Vue实例时,能够调用的钩子:beforeCreate,created,beforeMount,mounted;
    • 当Vue实例的数据(data)发生改变时,会进行对应组件的重新渲染,能够调用的钩子:beforeUpdate,updated;
    • 在Vue实例销毁前能调用钩子beforeDestroy,销毁后能够调用钩子destroyed.

  • 生命周期图解读:
    • 调用beforeCreate之前,仅仅初始化了Vue实例(都为undefined)
    • 调用created之前,已经进行了数据的初始化、属性和方法的运算以及watch/event 事件回调,但还没有将其挂载(此时页面的数据已经获取到,如message='Vue'的生命周期,el='undefined')
    • 调用beforeMount之前,先判断Vue实例是否有el属性
      • 当Vue实例没有el属性时,会停止编译,即beforeMount,mounted钩子函数不会执行。此时只有手动调用vm.$(el)el挂载在页面后,才能调用这两个钩子函数。
      • 当Vue实例有el属性时,如果Vue实例中有template属性(用来包含组件的标签),会把它当做render函数处理,该子组件的内容会替换掉页面中定义的内容。
    • 调用mounted之前,Vue实例可以继续添加el属性,内容也会随之覆盖(此时,才将用户定义的内容显示在页面,之前只是用{{message}}进行占位)
    • 当Vue实例的数据发生更新时,可以调用beforeUpdateupdated钩子函数,详情可见Vue深度学习总结_第3张图片
    • 在Vue实例销毁之前,可以调用beforeDestroy钩子函数;在Vue 实例销毁后可以调用destroyed钩子函数,此时该Vue实例指向的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

2. 计算属性、方法、侦听

  • 计算属性和方法的案例:

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue计算属性和方法title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
    <div id="example">
      <p>Original message: "{{ message }}"p>
      <p>Computed reversed message: "{{ compute_reversedMessage }}"p>
      <p>method Reversed message: "{{ method_reversedMessage() }}"p>
    div>
  body>
<script>
  // 测试计算属性和方法的区别
  var vm = new Vue({
    el: '#example',
    data: {
      message: 'Hello'
    },
    computed: {
      // 计算属性的 getter
      compute_reversedMessage: function () {
        // `this` 指向 vm 实例
        return this.message.split('').reverse().join('')
      }
    },
    // 在组件中
    methods: {
      method_reversedMessage: function () {
        return this.message.split('').reverse().join('')
      }
    }
  })
script>
html>

页面效果如图所示:
Vue深度学习总结_第4张图片

  • 侦听的案例

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue属性侦听title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
    <div id="watch-example">
      <p>Ask a yes/no question:<input v-model="question">p>
      <p>{{ answer }}p>
	  div>
  body>
  
  
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js">script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/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, oldQuestion) {
          this.answer = 'Waiting for you to stop typing...'
          this.getAnswer()
        }
      },
      methods: {
        // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。在这个例子中,我们希望限制访问 yesno.wtf/api 的// 频率AJAX 请求直到用户输入完毕才会发出。想要了解更多关于`_.debounce` 函数 (及其近亲 `_.throttle`) 的// 知识,请参考:https://lodash.com/docs#debounce
        getAnswer: _.debounce(
          function () {
            if (this.question.indexOf('?') === -1) {
              this.answer = 'Questions usually contain a question mark. ;-)'
              return
            }
            this.answer = 'Thinking...'
            var vm = this
            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>
html>

总结:

  • 计算属性和方法对数据进行操作,能达到相同的效果。计算属性会基于依赖进行缓存,只有当其依赖的值(message)发生变化时,才会重新求值。方法则是每次调用时均会进行计算求值。这里需要根据实际需求进行选择用计算属性或者方法。
  • watch侦听question属性。只要question发生变化,就会调用方法function(newQuestion,oldQuestion)。一般情况下,在数据变化时要执行异步或开销较大的操作,采用这种方式更为合理。

3. 渲染

  1. 条件渲染
  • 指令v-ifv-show两者用法相似,但只有v-if能跟v-else等配合使用。
  • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-show的元素总是会被渲染,只是简单地基于 CSS (display)进行切换。
  • 这里要注意:v-ifv-else对应两个不同的标签,两者有相同的元素时,vue默认会复用它们。可以通过指定不同的key属性,保证每次切换时不复用而是重新渲染。
  1. 列表渲染
  • v-for拥有对父作用域属性的完全访问权限。
  • 数组中,(item, index) in items,可选的index为当前项的索引。
  • 对象中,(value, key, index) in object,可选的key为对象属性,可选的index为对象属性的索引。
  • v-forv-if优先级高。
  1. 数组更新的编译方法说明

这一部分方法会对原数组进行改变:

  • push(item),从数组末尾插入一个元素item,返回值为新数组的长度
  • pop(),从数组末尾取出一个元素,返回值为取出的元素
  • shift(),从数组头部取出一个元素,返回值为取出的元素
  • unshift(item),从数组头部插入一个元素item,返回值为新数组的长度
  • splice(index),取出数组中下标index到末尾的所有元素,返回值为取出元素组成的新数组
  • reverse(),让数组倒序重新排布,返回值为重新排序后的新数组

这一部分方法不会对原数组进行改变

  • slice(index),取出数组中下标index到末尾的所有元素,返回值为取出元素组成的新数组
  • filter(item),过滤出符合条件item的所有元素,返回值为这些元素组成的数组
  • concat(item),添加一个元素到数组中,返回值为这些元素组成的数组

  1. 改变数组中指定下标的元素的值以及长度

由于JavaScript限制,vm.items[indexOfItem] = newValue改变数组中元素的值,vm.items.length = newLength改变数组的长度这种方式,在页面是不会生效的

# 改变数组中指定元素的值,会引起状态变化
## 方式一(将数组TargetArray的下标indexOfTarget的元素,值替换为newValue)
Vue.set(TargetArray, indexOfTarget, newValue)
## 方式二(将数组TargetArray的下标indexOfTarget后count个元素,值替换为newValue)
TargetArray.splice(indexOfTarget, count, newValue)
# 改变数组的长度(将数组TargetArray的长度设置为newLength)
TargetArray.splice(newLength)

4. 事件处理

常用的事件修饰符如下:

# 事件修饰符



...
...
# 按键修饰符 {tab},{delete},{esc},{space},{up},{down},{left},{right} # 其他

5. 组件使用

注册并使用一个全局组件

<html>
  <body>
    <div id="example">
      
      <my-component>my-component>
    div>
  body>
  <script>
    // 注册全局组件
    Vue.component('my-component', {
      template: '
A custom component!
'
}) // 创建根实例 new Vue({ el: '#example' })
script> html>

组件注入后,在DOM解析时可能会有一些异常,以及解决办法如下:


<table>
  <my-row>...my-row>
table>


<table>
  <tr is="my-row">tr>
table>

6. 组件通讯


组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。***prop 向下传递,事件向上传递。*** 父组件通过 prop给子组件下发数据,子组件通过事件给父组件发送消息。


父组件向子组件下发数据的实现:

  1. 子组件定义时,指定属性propskey值(myMessage)
  2. 在父组件中引入子组件后,将父组件的作用域内的数据(parentMsg)传入子组件指定propkey值(:my-message="parentMsg")
  3. 由于HTML不区分大小写,这里用的是"-"进行间隔的区分;由于这种数据的传输是单向的,子组件中值的改变不会影响父组件中的状态。

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue父子组件通讯-prop向下title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
    <div id="example">
      <div>
        <input v-model="parentMsg">
        <br>
        <child v-bind:my-message="myMessage">child>
      div>
    div>
  body>
  <script>
    Vue.component('child', {
      // 在 JavaScript 中使用 camelCase
      props: ['myMessage'],
      template: '{{ myMessage }}'
    })

    // 创建根实例
    new Vue({
      el: '#example',
      data: {
	      parentMsg: ''
      },
      computed: {
        // 如果子组件获取到父组件的数据后想更改,需要用计算属性的方法进行更改
        // 下面这个计算属性:将父组件的数据全小写展示在子组件中
        myMessage: function() {
          return this.parentMsg.toLowerCase()
        }
      }
    })
  script>
html>

父组件获知子组件触发了某个事件的实现:

  1. 通过Vue实例的($emit,$on)方法实现子组件发送信息给父组件
  2. 点击按钮,触发子组件的点击事件(incrementCounter),该事件会调用Vue实例的方法(this.$emit('increment'));父组件中时刻监听increment(v-on:increment="incrementTotal"),接收到了信号increment后会调用父组件的方法(incrementTotal),父组件更新total值。

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue父子组件通讯-事件向上title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
    <div id="counter-event-example">
		  <p>{{ total }}p>
	  	<button-counter v-on:increment="incrementTotal">button-counter>
	  	<button-counter v-on:increment="incrementTotal">button-counter>
	  div>
  body>
  <script>
    Vue.component('button-counter', {
	  	template: '',
	  	data: function () {
	    	return {
	      	counter: 0
	    	}
	  	},
	  	methods: {
	    	incrementCounter: function () {
          this.counter += 1
          this.$emit('increment')
	    	}
	  	},
	  })

	  new Vue({
	  	el: '#counter-event-example',
	  	data: {
	    	total: 0
	  	},
	  	methods: {
	    	incrementTotal: function () {
          this.total += 1
	    	}
	 	  }
	  })
  script>
html>

非父子组件的通讯,可参考子组件传递信息给父组件。均是基于Vue实例的方法($emit,$on)实现

7. 插槽

插槽,一种混合父组件内容与子组件模板内容的实现方式。

  • 子组件模板包含至少一个slot插口,否则父组件的内容将会被丢弃。最初在slot标签中的任何内容都被视为备用内容。只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
  • 具名插槽。父子组件根据slot标签的name属性进行配对分发内容,父组件匹配不到子组件的name时,内容会分发到默认的slot(未指定name属性)中。
  • 作用域插槽。一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。

8.CSS过渡动画


<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue中的CSS过渡动画效果title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
	  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js">script>
    <div id="list-complete-demo" class="demo">
      <button v-on:click="shuffle">Shufflebutton>
      <button v-on:click="add">Addbutton>
      <button v-on:click="remove">Removebutton>
      <transition-group name="list-complete" tag="p">
        <span
          v-for="item in items"
          v-bind:key="item"
          class="list-complete-item"
        >
          {{ item }}
        span>
      transition-group>
    div>
  body>
  <script>
    new Vue({
      el: '#list-complete-demo',
      data: {
        items: [1,2,3,4,5,6,7,8,9],
        nextNum: 10
      },
      methods: {
        randomIndex: function () {
          return Math.floor(Math.random() * this.items.length)
        },
        add: function () {
          this.items.splice(this.randomIndex(), 0, this.nextNum++)
        },
        remove: function () {
          this.items.splice(this.randomIndex(), 1)
        },
        shuffle: function () {
          this.items = _.shuffle(this.items)
        }
      }
    })
  script>
  <style>
    .list-complete-item {
      transition: all 1s;
      display: inline-block;
      margin-right: 10px;
    }
    .list-complete-enter, .list-complete-leave-to {
      opacity: 0;
      transform: translateY(30px);
    }
    .list-complete-leave-active {
      position: absolute;
    }
	style>
html>

总结:

  • Vue项目是通过**transition标签**包裹需要动画效果的元素,基于Vue深度学习总结_第5张图片该图片的四种状态,设置相应的播放效果。

9. Vue的常用API

  • nextTick()用法

常见的nextTick()用法如下:


<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Vue中nextTick用法案例title>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
  head>
  <body>
    <div id="app">
      <example>example>
    div>
  body>
  <script>
    Vue.component('example', {
      template: '{{ message }}',
      data: function () {
        return {
          message: '没有更新'
        }
      },
      mounted () {
          this.updateMessage();
      },
      methods: {
        updateMessage: function () {
          this.message = '更新完成'
          console.log('数据更新后直接打印:' + this.$el.textContent) // => '没有更新'
          this.$nextTick(function () {
            console.log('数据更新后在nextTick中打印:' + this.$el.textContent) // => '更新完成'
          })
        }
        }
      })

    new Vue({
      el:'#app'
    })    
  script>
html>

控制台输出结果:Vue深度学习总结_第6张图片
说明:我们经常会遇到类似上例的场景—在页面的数据(message)更新之后,需要根据更新后的数据去实现别的功能A(这里是在控制台打印:“更新完成”)。由于JavaScript中方法执行的异步性,执行功能A时所用数据可能不是更新后的数据,这会导致实现的功能并不符合我们的预期效果(就像这里,控制台输出的是:“数据更新后直接打印:没有更新”)。这时,我们可以用Vue的nextTick方法,保证功能达到预期效果。它能够保证是在数据更新后才调用该方法,使用的是更新后的DOM.

  • Vue.set(target,key,value)
    说明:前面改变数组中指定元素的值并保证页面正确显示时已经用过这个方法。Vue实例时响应式的,在初始化时声明的属性也是响应式的,但后期挂载的属性并不是响应式的。用Vue.set(target,key,value)方法能确保新建后的属性也是响应式的。

  • Vue.component(id, [definition])

// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))

// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })

// 获取注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')

Vue-router的使用

  1. (路由导航)的实现
  • 1.1 用router-link实现路由跳转

一个最基本的应用了路由的例子如下:


<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>router-link实现路由导航title>
  head>
  <body>
    <script src="https://unpkg.com/vue/dist/vue.js">script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js">script>
    <div id="app">
      <h1>Hello App!h1>
      <p>
        
        
        
        <router-link to="/foo">Go to Foorouter-link>
        <router-link to="/bar">Go to Barrouter-link>
      p>
      
      
      <router-view>router-view>
    div>

    <script>
      // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
      // 1. 定义(路由)组件。
      const Foo = { template: '
foo
'
} const Bar = { template: '
bar
'
} // 2. 定义路由 // 每个路由应该映射一个组件 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 创建 router 实例,然后传 `routes` 配置 const router = new VueRouter({routes: routes}) // 4. 创建和挂载根实例。 const app = new Vue({router}).$mount('#app')
script> body> html>

说明:/user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。要想监测变化,需要用watch或者beforeRouteUpdate.

  • 1.2 用router实例的方法(push,replace,go)实现路由跳转

用法如下:

// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)

说明

  • push方法的参数有name(目标路由名称)和path(目标路由路径)两种方式,如果提供了path,此时params(参数)的使用会失效,这里可以用query来替代;
  • pushreplace的用法一样,只是如同字面意思,只会要指向效果,没有history中添加相应的记录。
  1. 命名视图

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>命名视图简单用法title>
  head>
  <body>
    <script src="https://unpkg.com/vue/dist/vue.js">script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js">script>
    <div id="app">
      <h1>Named Viewsh1>
      <ul>
        <li>
          <router-link to="/">/router-link>
        li>
        <li>
          <router-link to="/other">/otherrouter-link>
        li>
      ul>
      <router-view class="view one">router-view>
      <router-view class="view two" name="a">router-view>
      <router-view class="view three" name="b">router-view>
    div>
    <script>
      const Foo = { template: '
foo
'
} const Bar = { template: '
bar
'
} const Baz = { template: '
baz
'
} const router = new VueRouter({ mode: 'history', routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } }, { path: '/other', components: { default: Baz, a: Bar, b: Foo } } ] }) new Vue({ router, el: '#app' })
script> body> html>

说明:当点击router-link(to='/'),解析如下:跳转至路径(path)为/的路由中,该路由的内容为有命名视图(name:component)构成的三个组件(default:Foo,a:Bar,b:Baz),按名称在router-view中展示相应组件的内容(如router-view中name为a的,展示组件Bar的内容)。这里要注意的是,只有一个路由时用component,有多个路由时用components

  1. props解耦
// 解耦前
const User = {
  template: '
User {{ $route.params.id }}
'
} const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] }) // 用props解耦后 const User = { props: ['id'], template: '
User {{ id }}
'
} const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, props: true }, // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项: { path: '/user/:id', components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false } } ] })

说明:直接在组件中通过$route获取参数id,会导致该组件与路由紧紧耦合。用props解耦后,能让该组件更易于重用和测试。

你可能感兴趣的:(Vue框架)