Vue3 基础内容

  • 响应式变量

  • 生命周期

  • 模版语法

  • 计算属性 监听属性

  • 条件渲染

  • 事件处理

  • props

  • 组件事件 emit

  • 插槽

  • provide inject

  • 异步组件

  • 内置组件

响应式变量以及对象

用ref()定义响应式变量, 用作值类型引用的响应式, ref()将传入参数的值包装成一个带有.value属性的ref对象, 当类型为对象时, 会用reactive()自动转换它的.value。 ref 在模版中会自动解包 也就是说在temlate语法中可以直接调用ref定义的变量

用reactive()函数创建一个响应式对象或数组需要注意的是vue的响应式是通过属性进行追踪, 所以必须保持对该响应式对象的相同引用. 当响应式对象的属性赋值或者解构成本地变量后, 将失去响应式

// Some code
import { reactive, ref } from 'vue'
const ref_num = ref(0)
const state = reactive({ count:0 })
const mutation = () => {
    state.count ++;
    ref_num.value ++;
}
// 调用mutation方法后 ref_num 以及 state 会进行同步更新

深层响应式以及浅层响应式

toRef toRefs 的使用

toRefs 是一种用于破坏响应式对象并将其所有属性转换为ref的使用方法, 也就是说将一个reactive封装的对象转换成么个属性所对应的ref. 当一个封装后的reactive对象拥有比较复杂的结构时, toRefs可以派上用场. 可以让你少写点代码

const state = reactive({ a: 1, b: 2, c: 3 })
return toRefs(state)
// 在template中可以直接写 {{ a }} {{ b }} {{ c }}
// 达到简化代码的目的

toRef 也是一样, 可以自己选择将reactive包裹的对象的某个属性分离出来并自定义属性名同时保留响应式

// Some code
const state = reactive({ a:1, b:2, c:3 })
const a = toRef(state, 'a' )
return { a }

组件的生命周期

vue2 与 vue3 生命周期变化不大, 在composition api中的用法会略有不同。但是并不影响逻辑上的实现

  • setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method

  • onBeforeMount() : 组件挂载到节点上之前执行的函数。

  • onMounted() : 组件挂载完成后执行的函数。

  • onBeforeUpdate(): 组件更新之前执行的函数。

  • onUpdated(): 组件更新完成之后执行的函数。

  • onBeforeUnmount(): 组件卸载之前执行的函数。

  • onUnmounted(): 组件卸载完成后执行的函数

  • onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。

  • onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。

Vue3的模板语法

文本插值

插入原始HTML

指令

指令是带有 v- 前缀的特殊 attribute。Vue 提供了许多内置指令,包括上面我们所介绍的 v-bindv-html

指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-forv-onv-slot)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if 为例:

<p v-if="seen">Now you see me</p>

v-if 与 v-else-if v-else 联动, 是vue中使用条件渲染的方式。v-else 元素必须跟在一个v-if或者一个v-else-if 后,否则不会被识别

v-if 亦可以依附在一个包装器元素上, 这样可以达到同时条件渲染多个元素

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-for-列表渲染

我们可以使用 v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

Vue 的列表渲染与react的逻辑不同, 需要使用特殊的指令, 但是列表元素同样需要key标识符来提升渲染的性能

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
  {{ item.message }}
</li>

v-for不仅适用于数组的渲染同样可以 遍历对象的所有属性, 遍历的顺序会基于对象调用的Object.keys()的返回值来决定

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<ul>
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>

v-for 与 v-if 拥有不同的优先级, 故不推荐使用。当它们同时存在于一个节点上时,v-ifv-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

当数组需要排列或者是过滤可以使用监听属性

这样的好处在于不会变更原始数据解构, 而将处理过后的数组作为一个缓存只进行遍历并具备响应式

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0)
})

<li v-for="n in evenNumbers">{{ n }}</li>

v-bind v-on

某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind 指令来响应式地更新一个 HTML attribute:

<a v-bind:href="url"> ... </a>

<!-- 简写 -->
<a :href="url"> ... </a>

这里 href 就是一个参数,它告诉 v-bind 指令将表达式 url 的值绑定到元素的 href attribute 上。在简写中,参数前的一切 (例如 v-bind:) 都会被缩略为一个 : 字符。

另一个例子是 v-on 指令,它将监听 DOM 事件:

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

<!-- 简写 -->
<a @click="doSomething"> ... </a>

这里的参数是要监听的事件名称:clickv-on 有一个相应的缩写,即 @ 字符。我们之后也会讨论关于事件处理的更多细节。

动态参数

使用修饰符简写指令

修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()

我们可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="methodName"@click="handler"

事件处理器的值可以是:

  1. 内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与 onclick 类似)。

  2. 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。

// 内联事件处理器, 在模板语言中直接定义的事件处理方式.适用于一些简单的事件处理
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

// 方法事件处理器
const name = ref('Vue.js')

function greet(event) {
  alert(`Hello ${name.value}!`)
  // `event` 是 DOM 原生事件
  if (event) {
    alert(event.target.tagName)
  }
}
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>

// 也可以传入自定义参数来替代原生事件 写法如下
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>

function say(message) {
  alert(message)
}

// 当你需要在内联事件处理器中访问原生DOM事件时, 需要传入一个特殊的$event变量.或者使用箭头函数传入event
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>

function warn(message, event) {
  // 这里可以访问原生事件
  if (event) {
    event.preventDefault()
  }
  alert(message)
}

计算属性以及监听属性

计算属性 computed

我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑.

computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value

Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage 依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新。

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')
//现在当你再运行 fullName.value = 'John Doe' 时,setter 会被调用而 firstName 和 lastName 会随之更新
const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

计算函数不应有副作用#

计算属性的计算函数应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举例来说,不要在计算函数中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此计算函数的职责应该仅为计算和返回该值。

从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。

监听属性 watch watchEffect

watch 和 watchEffect都能响应式地执行有副作用的回调. 它们之间的主要区别是追踪响应式依赖的方式:

  • watch 只追踪明确监听的数据源. 它不会追踪任何回调中访问到的东西, 另外仅在数据源确实改变时才会触发回调.

  • watchEffect 则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。

vue 与 react 不同的地方在于, react 通过useEffect钩子追踪state的状态变化并处理后续的逻辑, 而vue通过监听响应式变量的变化只想相应的回调. 两者的实现方式不同但目的基本一致.

<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

const x = ref(0)
const y = ref(0)

const obj = reactive({ count: 0 })

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    }
  }
})

watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// *** 注意,你不能直接侦听响应式对象的属性值,这里需要用一个返回该属性的 getter 函数:
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

// 深层监听
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)

</script>


异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能:

ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用。类似 Vite 和 Webpack 这样的构建工具也支持此语法(并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件:可以全局注册异步组件亦可以在父组件中直接定义

通过异步组件 我们可以实现按需加载, 当一个组件仅在需要它的时候才进行加载

<script setup>
import { defineAsyncComponent } from 'vue'

const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)

----------------------------------------------
app.component('MyComponent', defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
))
</script>

<template>
  <AdminPage />
</template>

插槽

Provide Inject

Last updated