响应式基础
模板语法
目的:在 dom 标签中,直接插入内容
又叫:声明式渲染/文本插值
语法:{{ 表达式 }}
<script setup>
// 3.2 之后的 setup 语法,可以使 vue 开发变得更简单
// 1. 定义变量
const msg = "hello, vue";
const obj = {
name: "正心",
age: 18,
};
</script>
<template>
<div>
<!-- 2. 把值赋予到标签 -->
<h1>{{ msg }}</h1>
<h2>{{ obj.name }}</h2>
<!--3. 可以使用 JavaScript 表达式-->
<h3>{{ obj.age > 18 ? "成年" : "未成年" }}</h3>
</div>
</template>
<style scoped>
a {
color: #42b983;
}
</style>
总结:dom
中插值表达式赋值,vue
的变量必须在 script
标签里声明
setup
- 新的
option
, 所有的组合API
函数都在此使用,只在初始化时执行一次 - 函数如果返回对象,对象中的属性或方法,模板中可以直接使用
原始 HTML
双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html
指令:
let rawHtml = '<span style="color: red">This should be red.</span>';
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
这里我们遇到了一个新的概念。这里看到的 v-html
attribute 被称为一个指令。指令由 v-
作为前缀,表明它们是一些由 Vue 提供的特殊 attribute,你可能已经猜到了,它们将为渲染的 DOM 应用特殊的响应式行为。这里我们做的事情简单来说就是:在当前组件实例上,将此元素的 innerHTML 与 rawHtml
属性保持同步。
span
的内容将会被替换为 rawHtml
属性的值,插值为纯 HTML——数据绑定将会被忽略。注意,你不能使用 v-html
来拼接组合模板,因为 Vue 不是一个基于字符串的模板引擎。在使用 Vue 时,应当使用组件作为 UI 重用和组合的基本单元。
JavaScript 表达式
Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
{{ number + 1 }}
{{ ok ? "YES" : "NO" }}
{{ message.split("").reverse().join("") }}
<div :id="`list-${id}`"></div>
响应式状态
我们可以使用 reactive()
函数创建一个响应式对象或数组
- 作用:定义多个数据的响应式
const proxy = reactive(obj)
: 接收一个普通对象然后返回该普通对象的响应式代理器对象- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
import {reactive} from "vue";
const state = reactive({count: 0});
响应式对象其实是 JavaScript Proxy ,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作。
要在组件模板中使用响应式状态,需要在 setup()
函数中定义并返回。
<script setup>
import {reactive} from "vue";
const state = reactive({count: 0});
function increment() {
state.count++;
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
DOM 更新时机
当你更改响应式状态后,DOM 会自动更新。然而,你得注意 DOM 的更新并不是同步的。相反,Vue 将缓冲它们直到更新周期的“下个时机” 以确保无论你进行了多少次状态更改,每个组件都只更新一次。
若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:
import {nextTick} from "vue";
function increment() {
state.count++;
nextTick(() => {
// 访问更新后的 DOM
});
}
深层响应性
在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。
import {reactive} from "vue";
const obj = reactive({
nested: {count: 0},
arr: ["foo", "bar"],
});
function mutateDeeply() {
// 以下都会按照期望工作
obj.nested.count++;
obj.arr.push("baz");
}
案例:数据更新
<script setup>
import {reactive} from "vue";
/*
reactive:
作用:定义多个数据的响应式
const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
*/
/*定义响应式数据对象*/
const state = reactive({
name: "tom",
age: 25,
wife: {
name: "marry",
age: 22,
},
});
console.log(state, state.wife);
const update = () => {
state.name += "-";
state.age += 1;
state.wife.name += "+";
state.wife.age += 2;
};
</script>
<template>
<h2>name: {{ state.name }}</h2>
<h2>age: {{ state.age }}</h2>
<h2>wife: {{ state.wife }}</h2>
<hr/>
<button @click="update">更新</button>
</template>
响应式变量
reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的“引用”机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:
- 作用:定义一个数据的响应式
- 语法:
const xxx = ref(initValue)
:- 创建一个包含响应式数据的引用 (reference) 对象
- js 中操作数据:xxx.value
- 模板中操作数据:不需要.value
- 一般用来定义一个基本类型的响应式数据
import {ref} from "vue";
const count = ref(0);
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:
const count = ref(0);
console.log(count); // { value: 0 }
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value。
一个包含对象类型值的 ref 可以响应式地替换整个对象:
const objectRef = ref({count: 0});
// 这是响应式的替换
objectRef.value = {count: 1};
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:
const obj = {
foo: ref(1),
bar: ref(2),
};
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo);
// 仍然是响应式的
const {foo, bar} = obj;
简言之,ref() 让我们能创造一种对任意值的“引用”,并能够在不丢失响应性的前提下传递这些引用。
ref 在模板中的解包
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:
<script setup>
import {ref} from "vue";
const count = ref(0);
function increment() {
count.value++;
}
</script>
<template>
<button @click="increment">
{{ count }}
<!-- 无需 .value -->
</button>
</template>
请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。例如,object 是顶层属性,但 object.foo 不是。
所以我们给出以下 object:
const object = {foo: ref(1)};
下面的表达式将不会像预期的那样工作:
{{ object.foo + 1 }}
渲染的结果会是一个 [object Object]1
,因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:
const {foo} = object;
{{ foo + 1 }}
现在渲染结果将是 2。
需要注意的是,如果一个 ref 是文本插值(即一个 {{ }} 符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1:
{
{
object.foo;
}
}
这只是文本插值的一个方便功能,相当于 {{ object.foo.value }} 。
ref 在响应式对象中的解包
当一个 ref
被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:
const count = ref(0);
const state = reactive({
count,
});
console.log(state.count); // 0
state.count = 1;
console.log(count.value); // 1
如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:
const otherCount = ref(2);
state.count = otherCount;
console.log(state.count); // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value); // 1
只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。
案例
<script setup>
import {ref} from "vue";
/* 使用 vue3 的 composition API */
const count = ref(0);
// 更新响应式数据的函数
const update = () => {
count.value = count.value + 1;
};
</script>
<template>
<h2>{{ count }}</h2>
<button @click="update">更新</button>
</template>
vue 调试工具
Vue DevTools