Skip to content

Setup 是 Vue 3 响应式 API 的 metod data component 的封装

html
<script lang="ts">//这是Vue 2的语法
    export default {
        name : "person",
        setup(){
            let name = "114514";
            let age = 18;
            let tel = "1380000";
            function changeName(){
                name = "114514";
            }
            function showName(){
                alert(name);
            }
            function addAge(){
                console.log(age);
                age++;
            }
            return { name , age , showName , addAge }
            
           
        }
    }
</script>

返回值

通常 setup 的返回值是其中的各个属性及方法 但是 setup 也可以返回一个渲染函数,例如:

js
return a()=>'114514'

此时 <template> 标签下的内容不会被渲染,而是渲染这个渲染函数返回的内容

简化 return

  1. 将 setup 写在另一个 setup script 标签中:
html
<script lang="ts" setup>
    let name = "114514";
    let age = 18;
    let tel = "1380000";
    function changeName(){
        name = "114514";
    }
    function showName(){
        alert(name);
    }
    function addAge(){
        console.log(age);
        age++;
    }
</script>

上面这种方法需要两个 script 标签完成,通过引入 vite-plugin-vue-setup-extend 模块,可以在一个 script 中同时定义组件名和 setup 声明:

html
<script lang="ts" name="person" setup>
    let name = "114514";
    let age = 18;
    let tel = "1380000";
    function changeName(){
        name = "114514";
    }
    function showName(){
        alert(name);
    }
    function addAge(){
        console.log(age);
        age++;
    }
</script>

将这个包添加到开发环境而非生产环境:

shell
npm install vite-plugin-vue-setup-extend -D

修改 vite.config.ts,启用模块:

ts
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import SetupExtend from 'vite-plugin-vue-setup-extend' //导入插件

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    SetupExtend() //调用插件
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
  1. 也可以直接:
ts
import { defineOptions } from 'vue'
defineOptions({ name: 'persossn111' })
  1. Vue 3.2 原生支持了这个写法,所以其实啥都不用干

与 Vue 2 的兼容性

属性可以不被封装在 setup 容器中,依然可以被 template 调用 虽然 setup 中不能用 this,但是之外的内容是可以用 this 访问到 setup 中的数据的(因为 setup 的生命周期比 data 早,此时数据已经被创建,所以可以被读取到)

响应式数据创建

首先导入 vue 模块中的 ref

ts
import { ref } from "vue";

其后,响应式数据用 ref 包装:

ts
let name = ref("张三");

此时是将 name 包装成 RefImpL 的实例对象:

Ref 响应式对象

ref 的实质其实是对 reactive 对象的又一次封装(响应式包装器),这样 _value 就可以是对象或者是基础类型:

创建基本类型数据

修改要修改对象的 value 属性, 插值表达式会自动引用 value 属性的值,不需要加 .value

html
<template>
    <div class="person">
        <h2>name: {{name}}</h2>
        <h2>age: {{age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="addAge">增加年龄</button>
    </div>
</template>
ts
function changeName(){
	//name.value = "李四";
	name.value = "李四";
}

不能在修改的时候用一个新的 ref 对象实例去覆盖原有对象,前端响应元素和对象是相互绑定的(在 setup 第一次创建 ref 实例时),如果覆盖对象会失去绑定导致元素不刷新。 开发过程中不肯呢个全是 .value 来一个个修改,使用 Object.assign() 将传入对象的值追加/覆盖到源对象中:

js
Object.assign(game_list.value[0], {name : 'dota2', price: 100})
直接在@click 隐式调用方法中修改属性
html
<button @click="car.price += 10">+</button>
创建响应式对象

参数是对象时,修改数据要先 .value

ts
function change_first_name(){
	game_list.value[0].name = 'lol'
}
特例

如果 ref 是定义在 reactive 下的,那么解包的时候不需要加 .value(因为在读取 reactive 对象下的 ref 属性的时候,会被自动解包加上 .value)。

Reactive 对象类型响应式数据

ref 接受的是一个标量值,当然它也可以接受一个对象,但 Vue 不会追踪并更新其中的属性变化,reactive 创建的对象中所有属性都是响应式的。 reactive 操作的是原始对象的 proxy,只有操作这个代理对象才具有响应式。

ref 和 reactive 的选择

使用这个设置可以自动在 ref 属性修改的时候加上 .value

  • ref 的数据具有深层响应性,可使用 shallowref 来放弃深层响应
  • reactive 本身具有响应式,而 ref 是其中的数据具有响应式
  • reactive 只适用于对象类型,不适用于原始类型 因此,使用 ref 处理基本数据类型,而使用 reactive 处理对象和数组。

toRefs 与 toRef

如果对 car 的属性进行单独的解构赋值,结果不是响应式的。

ts
let {price,name} = toRefs(car.value)

此时 toRefs 返回的是一个对象,其内容是对 car 中每个属性的响应式引用(也就是说改变这个值也会改变原本对象的值)。 至于 toRef,则是返回源对象中单个属性的响应式引用。

js
const state = reactive({ foo: 'bar' })
const fooRef = toRef(state, 'foo')

要使用这俩,需要从 vue 中导入这两个函数。

ref 的本质

vue 的 ref 实质上是在当对象的属性发生变化的时候自动更新相应的 DOM,但是原生 JS 并不能做到这一点。ref 实际上是为数据对象提供了 setter,getter 函数,当调用 setter 更新其值时,触发 DOM 更新,在使用 getter 时,更新其数据依赖。

js
// 伪代码,不是真正的实现
const myRef = {
  _value: 0,
  get value() {
    track()
    return this._value
  },
  set value(newValue) {
    this._value = newValue
    trigger()
  }
}

v-bind 由数据单向绑定 DOM

html
<input type="text" :value="lastName"><br>

单向绑定就是数据修改会引起 DOM 更新,但 DOM 更新不会引起数据更改v-bind:value 可以简写为 :value

v-model 双向绑定

将输入框中的内容和 text 变量的内容绑定:

html
<input v-model="text">

也可以绑定非文本内容,比如一个对象。

html
<template>
    <select v-model="x">
        <option :value="{number:123}">A</option>
    </select>
</template>

<script setup lang='ts' name='test'>
import { ref, reactive } from 'vue'
let x = ref(123)
</script>

底层实现

html
<input :value="text" @input="event => text = event.target.value">

input 拥有一个属性 value,用于控制显示内容,这里将显示的内容和 text 变量单向绑定(text-->value),然后在有输入事件的时候将新输入的数据传给 text 以实现表单数据的双向绑定。 输入法 需要输入法的语言在拼字阶段不会触发 v-model 的更新,如果此时要求实时更新的话需要通过底层实现的形式完成绑定。 html 原生组件 在原生组件上 v-model 会默认绑定属性,这时候不要手动指定绑定的属性名称。

html
<input type="checkbox" id="checkbox" v-model="checked" />

此外,还支持 select,select multiple 等(最好提供一个禁用的空值选项 <option disabled value="">Please select one</option>复选框v-model 搭配使用的还有两个属性,可以定义不同状态时绑定回去的文本值。

html
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />

单选按钮

html
<template>
    <input type="radio" v-model="pick" :value="first" />
    <input type="radio" v-model="pick" :value="second" />
</template>

<script setup lang='ts' name='test'>
import { ref, reactive } from 'vue'

const pick = ref('')
const first = ref('first')
const second = ref('second')
</script>

注意这里的 value 属性一定要绑定到响应式对象上去,不然一次绑定后就会失去响应式。 多选项目绑定 ref 这里选中的数据的 value 会被绑定到 checkedNames 中的项中,不需要显式声明绑定标签属性的原因同上。

html
<template>
    <div>Checked names: {{ checkedNames }}</div>

    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
    <label for="jack">Jack</label>

    <input type="checkbox" id="john" value="John" v-model="checkedNames" />
    <label for="john">John</label>

    <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
    <label for="mike">Mike</label>
</template>

<script setup lang='ts' name='test'>
import { ref, reactive } from 'vue'
const checkedNames = ref([])
</script>

修饰符

  1. . lazy 只有当焦点从输入框移开后才更新
  2. .number 只有当输入内容是数字的时候才会更新
  3. .trim 默认自动去除用户输入内容中两端的空格

异步 DOM 更新

ts
import { nextTick } from 'vue'

async function increment() {
  count.value++
  await nextTick()
  // 现在 DOM 已经更新了
}