创建组件实例:我们传入 createApp
的对象实际上是一个组件
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp(App)
大多数真实的应用都是由一棵嵌套的、可重用的组件树组成的。
App (root component)
├─ TodoList
│ └─ TodoItem
│ ├─ TodoDeleteButton
│ └─ TodoEditButton
└─ TodoFooter
├─ TodoClearButton
└─ TodoStatistics
挂载应用
app.mount('#app')
插值语法
- 原始HTML:双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令。在当前组件实例上,将此元素的 innerHTML 与
rawHtml
属性保持同步。<p>Using text interpolation: {{ rawHtml }}</p> <p>Using v-html directive: <span v-html="rawHtml"></span></p>
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用
v-html
,并且永远不要使用用户提供的 HTML 内容。
- Javascript表达式
{{ message.split('').reverse().join('') }} <div :id="`list-${id}`"></div>
- 受限的全局访问
模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如
Math
和Date
。没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在window
上的属性。然而,你也可以自行在 app.config .globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。
- 指令
指令动态参数:<a @[eventName]="doSomething"> ... </a>
- 动态参数中表达式的值应当是一个字符串,或者是
null
。特殊值null
意为显式移除该绑定。其他非字符串的值会触发警告。- 动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:<a :['foo' + bar]="value"> ... </a>
如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式
Vue3响应式
ref()
import { ref } from 'vue' export default { setup() { const count = ref(0) function increment() { // 在 JavaScript 中需要 .value count.value++ } // 不要忘记同时暴露 increment 函数 return { count, increment } } } -------------------------------------------------- //单文本组件中 <script setup> import { ref } from 'vue' const count = ref(0) function increment() { count.value++ } </script> <template> <button @click="increment"> {{ count }} </button> </template>
在模板中使用 ref 时,我们不需要附加
.value
。为了方便起见,当在模板中使用时,ref 会自动解包 (有一些注意事项)reactive()
import { reactive } from 'vue' const state = reactive({ count: 0 })
响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。
reactive()
将深层地转换对象:当访问嵌套对象时,它们也会被reactive()
包装。当 ref 的值是一个对象时,ref()
也会在内部调用它。与浅层 ref 类似,这里也有一个 shallowReactive() API 可以选择退出深层响应性。
为什么我们需要使用带有
.value
的 ref,而不是普通的变量?当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。
- 在标准的 JavaScript 中,检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作。
该
.value
属性给予了 Vue 一个机会来检测 ref 何时被访问或修改。在其内部,Vue 在它的 getter 中执行追踪,在它的 setter 中执行触发。// 伪代码,不是真正的实现 const myRef = { _value: 0, get value() { track() return this._value }, set value(newValue) { this._value = newValue trigger() } }
- 另一个 ref 的好处是,与普通变量不同,你可以将 ref 传递给函数,同时保留对最新值和响应式连接的访问。当将复杂的逻辑重构为可重用的代码时,这将非常有用。
Javascript时间循环应用—nextTick()详解_javascript 怎么使用nexttick-CSDN博客
Reactive Proxy vs. Original
const raw = {} const proxy = reactive(raw) // 代理对象和原始对象不是全等的 console.log(proxy === raw) // false
- 只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是仅使用你声明对象的代理版本。
- 为保证访问代理的一致性,对同一个原始对象调用
reactive()
会总是返回同样的代理对象,而对一个已存在的代理对象调用reactive()
会返回其本身:
reactive()
API 有一些局限性:
- 有限的值类型:它只能用于对象类型 (对象、数组和如
Map
、Set
这样的集合类型)。它不能持有如string
、number
或boolean
这样的原始类型。- 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
let state = reactive({ count: 0 }) // 上面的 ({ count: 0 }) 引用将不再被追踪 // (响应性连接已丢失!) state = reactive({ count: 1 })
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
const state = reactive({ count: 0 }) // 当解构时,count 已经与 state.count 断开连接 let { count } = state // 不会影响原始的 state count++ // 该函数接收到的是一个普通的数字 // 并且无法追踪 state.count 的变化 // 我们必须传入整个对象以保持响应性 callSomeFunction(state.count)
计算属性:推荐使用计算属性来描述依赖响应式状态的复杂逻辑
computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref,和其他一般的 ref 类似。<script setup> import { ref, computed } from 'vue' const firstName = ref('John') const lastName = ref('Doe') const fullName = computed({ // getter get() { return firstName.value + ' ' + lastName.value }, // setter set(newValue) { // 注意:我们这里使用的是解构赋值语法 [firstName.value, lastName.value] = newValue.split(' ') } }) </script>
计算属性VS函数
- 不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要
author.books
不改变,无论多少次访问publishedBooksMessage
都会立即返回先前的计算结果,而不用重复执行 getter 函数
- 为什么需要缓存呢?想象一下我们有一个非常耗性能的计算属性
list
,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于list
。没有缓存的话,我们会重复执行非常多次list
的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。避免直接修改计算属性值
计算属性的 getter 应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举例来说,不要改变其他状态、在 getter 中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此 getter 的职责应该仅为计算和返回该值。在之后的指引中我们会讨论如何使用侦听器根据其他响应式状态的变更来创建副作用。
⭐⭐⭐
不要改变其他状态:
- 在getter(或类似的访问器函数)中,你应该只返回与当前getter相关的状态或计算值。你不应该更改任何状态或触发任何副作用(如网络请求或DOM更新)。
- 这是因为getter通常被设计为无副作用的、可重用的函数,它们应该只返回数据而不改变它。
在 getter 中做异步请求:
- 异步请求(如网络请求)通常具有延迟,并可能产生副作用,如数据变更或错误。
- getter应该是同步的,这意味着它们应该立即返回结果。如果你尝试在getter中进行异步操作,它可能会导致不可预测的行为或错误。
- 如果你需要根据异步数据返回一些值,你应该在actions(在Vuex中)或effects/thunks(在Redux Toolkit中)中进行这些异步操作,并在操作完成后更新状态。
更改 DOM:
- DOM(文档对象模型)是HTML页面的编程接口。更改DOM意味着直接操作页面上的元素。
- getter不应该直接更改DOM。这是因为getter的主要目的是返回数据,而不是更改页面的表示。DOM的更改应该在组件的渲染函数、生命周期方法或事件处理程序中完成。
⭐⭐⭐
绑定HTML class
绑定内联样式
v-for遍历对象:value-key-index
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
数组变化侦测
变更方法
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
javascript修改/不修改 原数组的函数-CSDN博客
//...numbers 的意思是“取出 numbers 数组中的所有元素”。但在这个上下文中,它被用在一个数组字面量中,
//所以实际上是创建了一个包含 numbers 所有元素的"新数组"
"let numbers = [1, 2, 3, 4, 5];
let reversedNumbers = [...numbers].reverse();
console.log(reversedNumbers); // 输出: [5, 4, 3, 2, 1]
事件处理
事件处理器 (handler) 的值可以是:
内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与
onclick
类似)。方法事件处理器:一个指向组件上定义的方法的属性名或是路径。
foo
、foo.bar
和foo['bar']
会被视为方法事件处理器,而foo()
和count++
会被视为内联事件处理器
有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的
$event
变量,或者使用内联箭头函数:
表单
另外,
v-model
还可以用于各种不同类型的输入,<textarea>
、<select>
元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合:
- 文本类型的
<input>
和<textarea>
元素会绑定value
property 并侦听input
事件;<input type="checkbox">
和<input type="radio">
会绑定checked
property 并侦听change
事件;<select>
会绑定value
property 并侦听change
事件。<input :value="text" @input="event => text = event.target.value"> ------------------------------------------------- <input v-model="text">
在 Vue 中,
.lazy
修饰符通常与表单输入元素(如v-model
指令)一起使用,用于实现“懒加载”或“延迟”的双向数据绑定。这里的“懒加载”并不是指资源加载的延迟,而是指用户输入值的更新不会在每次输入时都立即触发,而是在某个“懒”的时机(通常是失去焦点或按下回车键后)才触发。
Vue生命周期Vue生命周期-Vue实例-CSDNVue入门技能树
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM
模板引用:
你只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问
input
,在初次渲染时会是null
。这是因为在初次渲染前这个元素还不存在呢!<script setup> import { ref, onMounted } from 'vue' // 声明一个 ref 来存放该元素的引用 // 必须和模板里的 ref 同名 const input = ref(null) onMounted(() => { input.value.focus() }) </script> <template> <input ref="input" /> </template>
如果你需要侦听一个模板引用 ref 的变化,确保考虑到其值为
null
的情况:watchEffect(() => { if (input.value) { input.value.focus() } else { // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制) } })
在 Vue 3 中,
watchEffect
是一个新的 API,用于响应式地执行一个副作用函数(effect function),当这个副作用函数所依赖的响应式数据发生变化时,它会被重新触发。与watch
API 不同,watchEffect
不需要明确指定要观察哪个响应式引用或计算属性,而是会自动收集其执行过程中依赖到的响应式数据。
组件基础
动态组件
<KeepAlive>
在Vue.js中,
<KeepAlive>
是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中。当包裹动态组件时,<KeepAlive>
会缓存不活动的组件实例,而不是销毁它们。当组件在<KeepAlive>
内被切换,它的状态将保留下来——包括实例和子组件,以及计算属性、数据、绑定的事件监听器等。这对于需要频繁切换且不需要重新渲染整个组件的场景特别有用,因为它可以提高应用的性能并减少不必要的渲染。
<template> <div id="app"> <button @click="currentView = 'A'">View A</button> <button @click="currentView = 'B'">View B</button> <keep-alive> <component :is="currentView"></component> </keep-alive> </div> </template> <script> export default { data() { return { currentView: 'A' } }, components: { A: { /* ... */ }, B: { /* ... */ } } } </script>
![]()
监听器
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:const x = ref(0) const y = ref(0) // 单个 ref watch(x, (newX) => { console.log(`x is ${newX}`) }) // getter 函数 watch( () => x.value + y.value, (sum) => { console.log(`sum of x + y is: ${sum}`) } ) // 多个来源组成的数组 watch([x, () => y.value], ([newX, newY]) => { console.log(`x is ${newX} and y is ${newY}`) })
注意,你不能直接侦听响应式对象的属性值,例如:
const obj = reactive({ count: 0 }) // 错误,因为 watch() 得到的参数是一个 number watch(obj.count, (count) => { console.log(`Count is: ${count}`) }) ------------------------------------------- // 提供一个 getter 函数 watch( () => obj.count, (count) => { console.log(`Count is: ${count}`) } )
watch
vs.watchEffect
watch
和watchEffect
都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。在
setup()
或<script setup>
中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。如下方这个例子:
<script setup> import { watchEffect } from 'vue' // 它会自动停止 watchEffect(() => {}) // ...这个则不会! setTimeout(() => { watchEffect(() => {}) }, 100) </script> ----------------------------------------- // 需要异步请求得到的数据 const data = ref(null) watchEffect(() => { if (data.value) { // 数据加载后执行某些操作... } })