接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
案例:
我们这样操作是无法改变message 的值 应为message 不是响应式的无法被vue 跟踪要改成ref
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
let message: string = "我是message"
const changeMsg = () => {
message = "change msg"
}
</script>
<style>
</style>
需要改为 ref 、 Ref TS对应的接口
interface Ref<T> {
value: T
}
注意被ref包装之后需要.value 来进行赋值
用来 判断是不是一个ref对象
import {ref, Ref, isRef} from 'vue'
// 这里 ref 是做双向绑定用的 Ref 是TS的用法
let message: Ref<string | number> = ref("111")
let notRef: number = 222
const changeMsg = () => {
console.log(isRef(message)); //true
console.log(isRef(notRef)); //false
}
ref在浏览器的console输出显示过于复杂,可以配置一下浏览器设置
在F12中 点击设置 勾选 启动自定义格式设置工具即可
次数 显示的 log 为 Ref<“111”>
创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的 ( 注意! 一定不要和 ref 一起写, 不然他的值也会变成响应式)
例子:
修改其属性是非响应式的这样是不会改变的
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "小满"
})
const changeMsg = () => {
message.value.name = '大满' // 这样修改无法改变
}
const changeMsg = () => {
message.value = { name: "大满" } // 修改他的 value 才会被监听到 从而发送改变
}
</script>
<style>
</style>
强制更新页面DOM
这样也是可以改变值的 (可以使shallowRef的值强制刷新,从而刷新dom )
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef,triggerRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "小满"
})
const changeMsg = () => {
message.value.name = '大满'
triggerRef(message)
}
</script>
<style>
</style>
自定义ref
customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的
<template>
<div ref="dom"> // ref 可以获取dom元素 ,但是 ref = "dom"
wsh
</div>
<hr>
<div>
{{name}}
</div>
<hr>
<button @click="change">修改</button>
</template>
<script setup lang='ts'>
import {ref, reactive, onMounted, shallowRef, customRef} from 'vue'
function myRef<T>(value: T) {
let timer:any;
return customRef((track, trigger) => {
return{
get() {
track()
return value
},
set(newVal) {
clearTimeout(timer)
timer = setTimeout(() => {
console.log('dianjiset')
value = newVal
trigger()
}, 500)
}
}
})
}
const name = myRef<string>('1111')
const change = () => {
name.value = '222'
}
const dom = ref<HTMLDivElement>()
console.log(dom.value?.innerText) // 记得 做 value? 判断 不然可能报错
</script>
<style scoped>
</style>
reactive 用来绑定复杂的数据类型(Object); 例如 对象 数组
如果绑定了普通的数据类型会报错 ( 源码进行了 ts 的约束 )
import { reactive} from 'vue'
const person = reactive('1111') // 这样是报错的
绑定普通的数据类型 我们可以 使用昨天讲到ref
你如果用ref去绑定对象 或者 数组 等复杂的数据类型 源码里面其实也是 去调用reactive
使用reactive 去修改值无须.value,而 ref 取值 赋值 都需要加.value
1. reactive 基础用法
import { reactive } from 'vue'
let person = reactive({
name:"小满"
})
person.name = "大满"
2. 数组异步赋值问题
reactive proxy 不能直接赋值,否则破坏响应式对象的
let person = reactive<number[]>([])
setTimeout(() => {
person = [1, 2, 3] // 这里不能直接赋值
console.log(person);
},1000)
1. 解决方案一 使用 push 叫 结构赋值
const arr = [1,2,3]
person.push(...arr)
2.方案二 在外面包一层对象
type: Person = {
list?: Array<number>
}
let person = reactive<Person>(
{
list: []
}
)
setTimeout(() => {
const arr = [1,2,3]
person.list = arr; // 以这种方式
})
拷贝一份proxy对象将其设置为只读
import { reactive ,readonly} from 'vue'
const person = reactive({count:1})
const copy = readonly(person)
//person.count++ 但是 修改了 person copy会发生改变
copy.count++ 直接修改 copy 是无法修改的
只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图 (跟 ref 的 shallow 一个意思)
案例:
<template>
<div>
<div>{{ state }}</div>
<button @click="change1">test1</button>
<button @click="change2">test2</button>
</div>
</template>
<script setup lang="ts">
import { shallowReactive } from 'vue'
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowReactive(obj)
function change1() {
state.a = 7 // 这个会修改dom展示
}
function change2() {
state.first.b = 8 // 这个只改数据 不改显示
state.first.second.c = 9
console.log(state);
}
</script>
<style>
</style>
如果原始对象是非响应式的就不会更新视图 数据是会变的
如果原始对象是响应式的是会更新视图并且改变数据的
<template>
<div>
<button @click="change">按钮</button>
{{state}}
</div>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const obj = {
foo: 1,
bar: 1
}
const state = toRef(obj, 'bar')
// bar 转化为响应式对象
const change = () => {
state.value++
console.log(obj, state);
}
</script>
源码解析 toRef
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]> {
const val = object[key]
return isRef(val) // 主要是这里
? val
: (new ObjectRefImpl(object, key, defaultValue) as any)
}
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
get value() {
const val = this._object[this._key]
return val === undefined ? (this._defaultValue as T[K]) : val
}
set value(newVal) {
this._object[this._key] = newVal // 这里只改了值,没有做其他处理
}
}
可以帮我们批量创建ref对象主要是方便我们解构使用
import { reactive, toRefs } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = toRefs(obj)
foo.value++
console.log(foo, bar);
toRefs 源码解析
其实就是把reactive 对象的每一个属性都变成了ref 对象循环 调用了toRef
export type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]>
}
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
将响应式对象转化为普通对象
import { reactive, toRaw } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
const state = toRaw(obj)
// 响应式对象转化为普通对象
const change = () => {
console.log(obj, state);
}
toRaw 源码解析
通过 ReactiveFlags 枚举值 取出 proxy 对象的 原始对象
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。
import {computed, reactive, ref } from 'vue'
let price = ref(0)
let m = computed<string>(() => {
return '111' + price.value
})
price.value = 222
<template>
<div>{{ mul }}</div>
<div @click="mul = 100">click</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
let price = ref<number | string>(1)//$0
let mul = computed({
get: () => {
return price.value
},
set: (value) => {
price.value = 'set' + value
}
})
</script>
<style>
</style>
<template>
<div>
<table style="width:800px" border>
<thead>
<tr>
<th>名称</th>
<th>数量</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key="index" v-for="(item, index) in data">
<td align="center">{{ item.name }}</td>
<td align="center">
<button @click="AddAnbSub(item, false)">-</button>
{{ item.num }}
<button @click="AddAnbSub(item, true)">+</button>
</td>
<td align="center">{{ item.num * item.price }}</td>
<td align="center">
<button @click="del(index)">删除</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<td align="center">总价:{{ $total }}</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
type Shop = {
name: string,
num: number,
price: number
}
let $total = ref<number>(0)
const data = reactive<Shop[]>([
{
name: "袜子",
num: 1,
price: 100
},
{
name: "裤子",
num: 1,
price: 200
},
{
name: "衣服",
num: 1,
price: 300
},
{
name: "毛巾",
num: 1,
price: 400
}
])
const AddAnbSub = (item: Shop, type: boolean = false): void => {
if (item.num > 1 && !type) {
item.num--
}
if (item.num <= 99 && type) {
item.num++
}
}
const del = (index: number) => {
data.splice(index, 1)
}
$total = computed<number>(() => {
return data.reduce((prev, next) => {
return prev + (next.num * next.price)
}, 0)
})
</script>
<style>
</style>
watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用
watch第一个参数监听源
watch第二个参数回调函数 cb(newVal,oldVal)
watch第三个参数一个options配置项是一个对象 {
immediate:true //是否立即调用一次
deep:true //是否开启深度监听
}
import { ref, watch } from 'vue'
let message = ref({
nav:{
bar:{
name:""
}
}
})
watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
},{
immediate:true,
deep:true // 如果是 ref定义的响应式 这里需要设置为true 不然无法深度监听, 但是 如果是 reactive的模式, 则不用
})
// 监听多个ref 注意变成数组时
import { ref, watch ,reactive} from 'vue'
let message = ref('')
let message2 = ref('')
watch([message,message2], (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
使用reactive监听深层对象开启和不开启deep 效果一样
import { ref, watch ,reactive} from 'vue'
let message = reactive({
nav:{
bar:{
name:""
}
}
})
watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
案例2 监听reactive 单一值 ( 这里 watch的第一个参数可以写成函数的形式)
import { ref, watch ,reactive} from 'vue'
let message = reactive({
name:"",
name2:""
})
watch(()=>message.name, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect(() => {
//console.log('message', message.value);
console.log('message2', message2.value);
})
就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
})
停止跟踪 watchEffect 返回一个函数 调用之后将停止更新
const stop = watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
}
})
stop()
副作用刷新时机 flush 一般使用post
更新时机
pre 组件更新前执行
synv 强制效果始终同步触发
post 组件更新后执行
onTrigger 可以帮助我们调试 watchEffect (主要用于 debugger 测试)
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
debugger
}
})