Skip to content

响应式基础

模板语法

目的:在 dom 标签中,直接插入内容

又叫:声明式渲染/文本插值

语法:{{ 表达式 }}

vue

<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 指令

js
let rawHtml = '<span style="color: red">This should be red.</span>';
vue
<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 表达式:

vue
{{ number + 1 }}

{{ ok ? "YES" : "NO" }}

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

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

响应式状态

我们可以使用 reactive() 函数创建一个响应式对象或数组

  • 作用:定义多个数据的响应式
  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
js
import {reactive} from "vue";

const state = reactive({count: 0});

响应式对象其实是 JavaScript Proxy ,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作。

要在组件模板中使用响应式状态,需要在 setup() 函数中定义并返回。

vue

<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:

js
import {nextTick} from "vue";

function increment() {
    state.count++;
    nextTick(() => {
        // 访问更新后的 DOM
    });
}

深层响应性

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

js
import {reactive} from "vue";

const obj = reactive({
    nested: {count: 0},
    arr: ["foo", "bar"],
});

function mutateDeeply() {
    // 以下都会按照期望工作
    obj.nested.count++;
    obj.arr.push("baz");
}

案例:数据更新

vue

<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
  • 一般用来定义一个基本类型的响应式数据
js
import {ref} from "vue";

const count = ref(0);

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

js
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 可以响应式地替换整个对象:

js
const objectRef = ref({count: 0});

// 这是响应式的替换
objectRef.value = {count: 1};

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

js
const obj = {
    foo: ref(1),
    bar: ref(2),
};

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo);

// 仍然是响应式的
const {foo, bar} = obj;

简言之,ref() 让我们能创造一种对任意值的“引用”,并能够在不丢失响应性的前提下传递这些引用。

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:

vue

<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:

js
const object = {foo: ref(1)};

下面的表达式将不会像预期的那样工作:

vue
{{ object.foo + 1 }}

渲染的结果会是一个 [object Object]1,因为 object.foo 是一个 ref 对象。我们可以通过将 foo 改成顶层属性来解决这个问题:

js
const {foo} = object;
vue
{{ foo + 1 }}

现在渲染结果将是 2。

需要注意的是,如果一个 ref 是文本插值(即一个 {{ }} 符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1:

js
{
    {
        object.foo;
    }
}

这只是文本插值的一个方便功能,相当于 {{ object.foo.value }}

ref 在响应式对象中的解包

当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:

js
const count = ref(0);
const state = reactive({
    count,
});

console.log(state.count); // 0

state.count = 1;
console.log(count.value); // 1

如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:

js
const otherCount = ref(2);

state.count = otherCount;
console.log(state.count); // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value); // 1

只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。

案例

vue

<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

https://devtools.vuejs.org/