Vue简介

官方文档:https://cn.vuejs.org/guide/introduction.html

  • Vue是一款用于构建用户界面的JavaScript框架。

  • 基于HTML、CSS、JavaScript,提供一套声明式的、组件化的编程模型。

核心功能

  • 声明式渲染:声明式的描述最终输出的HTML和JavaScript状态之间的关系。

  • 响应性:Vue会自动跟踪JavaScript状态并在其发生变化时响应式地更新DOM。

渐进式框架

  • 无需构建步骤,渐进式增强静态的HTML

  • 在任何页面中作为Web Components嵌入

  • 单页应用(SPA)

  • 全栈/服务端渲染(SSR)

  • Jamstack/静态站点生成(SSG)

  • 开发桌面端、移动端、WebGL、甚至命令行终端中的界面

单文件组件

  • 定义:类似HTML格式的文件来书写Vue组件(也被称为*.vue文件)

  • 特点:将一个组件的逻辑(JavaScript),模板(HTML)和样式(CSS)封装在同一个文件中

 <script>
    export default {
    data() {
        return {
        count: 0
        }
    }
    }
 </script>

 <template>
    <button @click="count++">Count is: {{ count }}</button>
 </template>

 <style scoped>
    button {
    font-weight: bold;
    }
 </style>

API风格

  1. 选项式API(Options API)

  • 使用包含多个选项的对象来描述组件的逻辑,如:data、methods、mounted

  • 选项所定义的属性都会暴露在函数内部的this上,它会指向当前的组件实例

  • 更适用于有面向对象语言的,通常与基于类的心智模型更为一致

  • 又将响应性的细节抽象出来,强制按照选项来组织代码

   <script>
    export default {
    // data() 返回的属性将会成为响应式的状态
    // 并且暴露在 this 上
    data() {
        return {
        count: 0
        }
    },

    // methods 是一些用来更改状态与触发更新的函数
    // 它们可以在模板中作为事件监听器绑定
    methods: {
        increment() {
        this.count++
        }
    },

    // 生命周期钩子会在组件生命周期的各个不同阶段被调用
    // 例如这个函数就会在组件挂载完成后被调用
    mounted() {
        console.log(`The initial count is ${this.count}.`)
    }
    }
 </script>

 <template>
    <button @click="increment">Count is: {{ count }}</button>
 </template>
  1. 组合式API(Composition API)

  • 使用导入的API函数来描述组件逻辑

  • 在单文件组件中,组合式API通常与<script setup>配合使用

  • setup attribute是一个标识,告诉Vue需要在编译时进行一些处理,可以更简洁地使用组合式API

  • 核心思想是直接在函数作用域内定义响应式状态变量

  • 并将从多个函数中得到的状态组合起来处理复杂问题

  • 更加自由,但也需要对响应式系统有更深的理解

 <script setup>
    import { ref, onMounted } from 'vue'
    // 响应式状态
    const count = ref(0)
    // 用来修改状态、触发更新的函数
    function increment() {
    count.value++
    }

    // 生命周期钩子
    onMounted(() => {
    console.log(`The initial count is ${count.value}.`)
    })
 </script>

 <template>
    <button @click="increment">Count is: {{ count }}</button>
 </template>
  1. 选择哪一个?

如果你是使用 Vue 的新手,这里是我们的大致建议:

在学习的过程中,推荐采用更易于自己理解的风格。再强调一下,大部分的核心概念在这两种风格之间都是通用的。熟悉了一种风格以后,你也能够很快地理解另一种风格。

在生产项目中:

当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。

当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。

——摘自官方文档的建议

基础

创建一个应用

每个Vue都是通过createApp函数创建一个新的应用实例:

import { createApp } from 'vue'

const app = createApp({
 /* 根组件选项 */
})

根组件

每个应用都需要一个根组件,其余组件作为其子组件。

而使用单文件组件时,可以直接从另一个文件中导入根组件。

import { createApp } from 'vue'

import App from ./App.vue'

const app = createApp(App)
挂载应用

应用实例必须在调用了.mount()方法后才会渲染出来,该方法接收一个’容器‘参数,可以是一个实际的DOM元素或是一个CSS选择器字符串:

<div id = "app"></div>

app.mount('#app')

应用根组件的内容将会被渲染在容器的元素里面,容器元素自己将不会被视为应用的一部分。

.mount()方法始终在整个应用配置和资源注册完成后被调用,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。

DOM中的根组件模板

可以直接通过在挂载容器内编写模板来单独提供:

<div id="app">
    <button @click="count++">{{ count }}</button>
</div>

import { createApp} from 'vue'

const app = createApp({
  data() {
    return {
      count: 0
    }
  }
})

app.mount('#app')

当根组件没有设置template选项时,Vue将自动使用容器的innerHTML作为模板。

DOM内模板通常用于无构建步骤的Vue应用程序,也可以与服务器端框架一起使用,其中根模板可能是由服务器动态生成的。

应用配置

应用实例会暴露一个.config对象允许配置一些应用级的选项,如定义一个应用级的错误处理器,用来捕获所有子组件上的错误:

app.config.errorHandler = (err) => {
  /* 处理错误 */
}

app.component('TodoDeleteButton', TodoDeleteButton)

应用实例还会提供一些方法来注册应用范围内可用的资源,这使得TodeDeleteButton在应用的任何地方都是可用的。

应用实例并不只限制一个,createApp的API允许在同一个页面中创建多个共存的Vue应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。

如果只使用Vue去控制一个大型页面中的特殊的一小部分,则应该避免将一个单独的Vue应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去。

模板语法

  • 基于HTML的模板语法

  • 声明式的将组件实例的数据绑定到呈现的DOM上

  • 将模板编译成高度优化的JavaScript代码

<span>Message: {{ msg }}</span>
// 最基本的数据绑定形式-文本插值,使用的是“Mustache”语法,即双大括号
//双大括号标签会被替换为相应组件实例中msg属性的值,当其更改时也会同步更新

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
//双大括号会将数据解释为纯文本,而不是HTML,如果想要插入HTML,则需要使用v-html指令

//完整示例如下:
<script setup>
import { ref } from 'vue'

const rawHtml = ref('<span style="color: red">This should be red.</span>')
</script>

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

//Output:
Using text interpolation: <span style="color: red">This should be red.</span>
Using v-html directive: This should be red.

上面的v-htmlattribute被称为一个指令,由v-作为前缀,表明是一些由Vue提供的特殊attribute。

即在当前组件实例上,将此元素的innerHTML与rawHTML属性保持同步。

注意:

  1. 不能使用v-html来拼接组合模板,因为Vue不是一个基于字符串的模板引擎。

  2. 在使用Vue时,应当使用组件作为UI重用和组合的基本单元。

Attribute
<div v-bind:id="dynamicId"></div>
//双大括号不能在HTML attributes中使用,需要响应式的绑定一个attribute,则应该使用v-bind指令
//该指令指示Vue将元素的id attribute与组件的dynamicID属性保持一致
//如果绑定的值是null或者undefined,就将该attribute从渲染的元素上移除
//同时,v-bind可以简写,也更常见
<div :id="dynamicId"></div>

<button :disabled="isButtonDisabled">Button</button>
//布尔型attribute依据true/false值来决定attribute是否应该存在于该元素上
//disabled就是最常见的例子之一
//当isButtonDisabled为真值或者空字符串(<button disabled="">)时,元素会包含这个disabled attribute
//反之,则将被忽略

//动态绑定多个值
data() {
  return {
    objectOfAttrs: {
      id: 'container',
      class: 'wrapper'
    }
  }
}
===>
<div v-bind="objectOfAttrs"></div>
JavaScript表达式
{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

//Vue在所有的数据绑定中都支持完整的JavaScript表达式
//以当前组件实例为作用域解析执行
//使用场景:①文本插值中(双大括号内)②任何Vue指令(v-开头的特殊attribute)attribute的值中
//每个绑定仅支持单一表达式,也就是一段能够被求值的JavaScript代码

<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}

<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}
调用函数
<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

//Tips:
//绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。
受限的全局访问

模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math 和 Date。

没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。

指令Directives
  • 指令是带有v-前缀的特殊attribute。

  • 指令attribute的期望值为一个JavaScript表达式(除少数几个例外,v-for,v-on,v-slot)。

  • 一个指令的任务是在其表达式的值变化时响应式地更新DOM。

<p v-if="seen">Now you see me</p>
//v-if指令会基于表达式seen的值的真假来移除/插入该<p>元素
参数Arguments
<a v-bind:href="url"> ... </a>

<!-- 简写 -->
<a :href="url"> ... </a>
//某些指令需要一个“参数”,可以通过一个冒号隔开做标识
//这里的href就是一个参数

<a v-on:click="doSomething"> ... </a>

<!-- 简写 -->
<a @click="doSomething"> ... </a>
//监听DOM事件
//v-on相应的缩写即@字符

//动态参数:
<!--注意,参数表达式有一些约束-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
//指令参数上也可以使用一个JavaScript表达式,需要包含在一对方括号内[ ]
//上述示例中的attributeName会作为一个JavaScript表达式被动态执行,计算得到的值会被用作最终的参数
//e.g:如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href
//值限制:其值应当是一个字符串,或者是null;特殊值null意为显式移除该绑定,其他非字符串的值都会触发警告
//语法限制:空格和引号都是不合法的
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>
//如果需要传入一个复杂的动态参数,推荐使用计算属性替换复杂的表达式
//使用DOM内嵌模板时,需要避免在名称中使用大写字母,浏览器会强制将其转换为小写
<a :[someAttr]="value"> ... </a>
//上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]。
//如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。
//单文件组件内的模板不受此限制。

//将一个函数绑定到动态的事件名称上:
<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething">
//当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus
修饰符Modifiers
  • 以点开头的特殊后缀

  • 表明指令需要以一些特殊的方式被绑定

<form @submit.prevent="onSubmit">...</form>
//.prevent修饰符会告知v-on指令对触发的事件调用event.preventDefault()

完整的指令语法:

指令语法图

响应式

选项式API
  • 使用选项式API时,会用data选项来声明组件的响应式状态。

  • 此选项的值应为返回一个对象的函数。

  • Vue在创建新组件实例的时候调用此函数,并将函数返回的对象用响应式系统进行包装。

  • 此对象的所有顶层属性都会被代理到组件实例(即方法和生命周期钩子中的this)上。

export default {
  data() {
    return {
      count: 1
    }
  },

  // `mounted` 是生命周期钩子,之后我们会讲到
  mounted() {
    // `this` 指向当前组件实例
    console.log(this.count) // => 1

    // 数据属性也可以被更改
    this.count = 2
  }
}

Tips:

  • 实例上的属性仅在实例首次创建时被添加

  • 需要确保都出现在data函数返回的对象上

  • 如果所需的值还未准备好,必要时可以用null、undefined或其他一些值占位

  • 当不在data上定义,直接向组件实例添加新属性,则这个属性将无法触发响应式更新

  • Vue在组件实例上暴露的内置API使用$作为前缀

  • 同时也为内部属性保留_前缀

  • 所以需要在data中避免使用$_前缀

export default {
  data() {
    return {
      someObject: {}
    }
  },
  mounted() {
    const newObject = {}
    this.someObject = newObject

    console.log(newObject === this.someObject) // false
  }
}

//当你在赋值后再访问 this.someObject,此值已经是原来的 newObject 的一个响应式代理。
//与 Vue 2 不同的是,这里原始的 newObject 不会变为响应式:请确保始终通过 this 来访问响应式状态。
声明方法

为组件添加方法,需要使用methods选项,作为包含所有方法的对象。

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    // 在其他方法或是生命周期中也可以调用方法
    this.increment()
  }
}

//Vue 自动为 methods 中的方法绑定了永远指向组件实例的 this。
//确保了方法在作为事件监听器或回调函数时始终保持正确的 this。
//不应该在定义 methods 时使用箭头函数,因为箭头函数没有自己的 this 上下文。
export default {
  methods: {
    increment: () => {
      // 反例:无法访问此处的 `this`!
    }
  }
}

与组件实例上的其他属性一样,方法也可以在模板上被访问,在模板中常常被用作事件监听器。

<button @click="increment">{{ count }}</button>
//increment 方法会在 <button> 被点击时调用
DOM更新时机
  • 当更改响应式状态后,DOM会自动更新

  • DOM更新并不是同步的

  • Vue会将缓冲它们直到更新周期的“下个时机”,确保无论进行了多少次状态更改,每个组件都只更新一次

若要等待一个状态改变后的DOM更新完成,可以使用nextTic()这个全局API:

import { nextTick } from 'vue'

export default {
  methods: {
    increment() {
      this.count++
      nextTick(() => {
        // 访问更新后的 DOM
      })
    }
  }
}
深层响应性

Vue中,状态都是默认深层响应式的,即在更改深层次的对象或数组,改动也能被检测到。

也可以创建浅层响应式对象,仅在顶层具有响应性,一般仅在某些特殊场景中需要。

export default {
  data() {
    return {
      obj: {
        nested: { count: 0 },
        arr: ['foo', 'bar']
      }
    }
  },
  methods: {
    mutateDeeply() {
      // 以下都会按照期望工作
      this.obj.nested.count++
      this.obj.arr.push('baz')
    }
  }
}
有状态方法
import { debounce } from 'lodash-es'

export default {
  methods: {
    // 使用 Lodash 的防抖函数
    click: debounce(function () {
      // ... 对点击的响应 ...
    }, 500)
  }
}
  • 对于被重用的组件来说是有问题的,这个预置防抖的函数是有状态的,运行时维护着一个内部状态。

  • 若多个组件实例都共享这同一个预置防抖的函数,它们之间将会互相影响。

  • 若要保持每个组件实例的防抖函数都彼此独立,我们可以改为在created声明周期钩子中创建。

export default {
  created() {
    // 每个实例都有了自己的预置防抖的处理函数
    this.debouncedClick = _.debounce(this.click, 500)
  },
  unmounted() {
    // 最好是在组件卸载时
    // 清除掉防抖计时器
    this.debouncedClick.cancel()
  },
  methods: {
    click() {
      // ... 对点击的响应 ...
    }
  }
}

计算属性

//初始代码:
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
}

<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

//优化后的代码:
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // 一个计算属性的 getter
    publishedBooksMessage() {
      // `this` 指向当前组件实例
      return this.author.books.length > 0 ? 'Yes' : 'No'
    }
  }
}

<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
  • 定义了一个计算属性publishedBooksMessage

  • 当更改应用data中的books数组的值,计算属性也会随之改变

  • 在模板中使用计算属性的方法和一般的属性并无区别

计算属性缓存VS方法
<p>{{ calculateBooksMessage() }}</p>
// 组件中
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}

将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实完全相同的,但不同之处在于计算属性值会基于其响应式依赖被缓存

一个计算属性仅会在其响应式依赖更新时才重新计算,这意味着只要author.books不改变,无论多少次访问publishedBooksMessage都会立即返回先前的计算结果,而不是重复执行getter函数。

computed: {
  now() {
    return Date.now()
  }
}

//该计算属性永远都不会更新
//Date.now()不是一个响应式依赖

而方法调用总是会在重渲染发生时再次执行函数。

当需要做许多计算逻辑且可能也有其他计算属性依赖于这个非常耗性能的计算属性时,就会需要缓存,可以避免重复执行非常多次的getter。

可写计算属性
  • 计算属性默认是只读的

  • 只有某些特殊场景中才需要用到“可写”属性

  • 可以同时提供getter和setter来创建

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        // 注意:我们这里使用的是解构赋值语法
        [this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}

<template>
  <span>{{ this.fullName = "Sleny Zl" }}</span>
</template>
//当运行this.fullName = "Sleny Zl"时,setter就会被调用,this.firstName和this.lastName会随之更新

注意:

  • 计算属性的getter应该只做计算而没有任何其他的副作用

  • 即不要在getter中做异步请求或者更改DOM

  • 避免直接修改计算属性值,因为计算属性返回的值是派生状态,可以将其当作是一个临时快照,源发生改变时就会创建新的快照

  • 因此更改快照是没有意义的,而其返回值应该被视为只读的,并且永远不应该被更改,应该更新它所依赖的源状态以触发新的计算

Class和Style绑定

Vue专门为class和style的v-bind用法提供了特殊的功能增强,除字符串外,表达式的值也可以是对象或数组。

绑定HTML class
  1. 绑定对象

    给:class传递一个对象来动态切换class:

    <div :class="{ active: isActive }"></div>
    //该语法表示 active 是否存在取决于数据属性 isActive 的真假值。
    //可以在对象中写多个字段来操作多个 class
    data() {
      return {
        isActive: true,
        hasError: false
      }
    }
    
    <div
      class="static"
      :class="{ active: isActive, 'text-danger': hasError }"
    ></div>
    
    //渲染结果
    <div class="static active"></div>
    //当 isActive 或者 hasError 改变时,class 列表会随之更新。举例来说,如果 hasError 变为 true,class 列表也会变成 "static active text-danger"

    绑定的对象并不一定需要写成内联字面量的形式,可以直接绑定一个对象:

    data() {
      return {
        classObject: {
          active: true,
          'text-danger': false
        }
      }
    }
    
    <div :class="classObject"></div>

    也可以绑定一个返回对象的计算属性:

    data() {
      return {
        isActive: true,
        error: null
      }
    },
    computed: {
      classObject() {
        return {
          active: this.isActive && !this.error,
          'text-danger': this.error && this.error.type === 'fatal'
        }
      }
    }
    
    <div :class="classObject"></div>
  2. 绑定数组

    可以给:class绑定一个数组来渲染多个CSS class:

    data() {
      return {
        activeClass: 'active',
        errorClass: 'text-danger'
      }
    }
    
    <div :class="[activeClass, errorClass]"></div>
    //渲染的结果:
    <div class="active text-danger"></div>

    在数组中有条件地渲染某个class,可以使用三元表达式:

    <div :class="[isActive ? activeClass : '', errorClass]"></div>
    //errorClass 会一直存在,但 activeClass 只会在 isActive 为真时才存在
    //可能在有多个依赖条件的 class 时会有些冗长,因此也可以在数组中嵌套对象:
    <div :class="[{ active: isActive }, errorClass]"></div>
  3. 在组件上使用

    【标记】看完组件后再学习该小结

绑定内联样式
  1. 绑定对象

    :style支持绑定JavaScript对象值,对应地是HTML元素地style属性:

    data() {
      return {
        activeColor: 'red',
        fontSize: 30
      }
    }
    
    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
    //推荐使用camelCase,但也支持kebab-cased形式的CSS属性key(对应CSS中实际名称)
    <div :style="{ 'font-size': fontSize + 'px' }"></div>
    
    //直接绑定一个样式对象:
    data() {
      return {
        styleObject: {
          color: 'red',
          fontSize: '13px'
        }
      }
    }
    
    <div :style="styleObject"></div>

    同样,如果样式对象需要更复杂的逻辑,也可以使用返回样式对象的计算属性。

  2. 绑定数组

    :style支持绑定一个包含多个样式对象的数组,这些对象会被合并后渲染到同一元素上:

    <div :style="[baseStyles, overridingStyles]"></div>
  3. 自动前缀

    当在:style 中使用了需要浏览器特殊前缀的 CSS 属性时,Vue 会自动为他们加上相应的前缀。

    Vue 是在运行时检查该属性是否支持在当前浏览器中使用。

    如果浏览器不支持某个属性,那么将尝试加上各个浏览器特殊前缀,以找到哪一个是被支持的。

  4. 样式多值

    可以对一个样式属性提供多个 (不同前缀的) 值

    <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
    //数组仅会渲染浏览器支持的最后一个值。
    //在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex。
文章作者: Sleny
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 咸鱼说
Vue Vue
喜欢就支持一下吧
打赏
微信 微信
支付宝 支付宝