一、响应式基础
前提:你会使用
setup
函数或<script setup>
语法
1.reactive
我们可以使用 reactive()
函数创建一个响应式对象或数组:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
- 当我们使用 reactive 函数处理我们的数据之后,数据被使用时就会进行依赖收集;
- 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
- 事实上,在使用
Options API
时编写的 data 选项,也是在内部交给了 reactive 函数将其变成响应式对象的;
注意:
reactive 函数只能包裹对象或数组,包裹基本数据类型会丧失响应式,并且控制会打印出警告。
2. ref
reactive()
的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref()
方法来允许我们创建可以使用任何值类型的响应式 ref:
import { ref } from 'vue'
const countRef = ref(0)
// 使用
console.log(countRef.value)
使用ref()
函数定义的响应式数据,是一个带有.value
的Ref对象,在<script>
中使用该值时,需要.value
赋值或取值。
而在template
模板中,则会自动解包,取出.value
的值。
实例代码:
<template>
<h2>composition API</h2>
<p>ref-simple: {{ simple }}</p>
<p>ref-obj: {{ obj }}</p>
<div>
<p><button @click="simple++">更改simple</button></p>
<p><button @click="obj.name += '1'">更改obj.name</button></p>
<p><button @click="editObjName">更改obj.name</button></p>
</div>
<hr />
<p>reactive-state: {{ state }}</p>
<p>reactive-count: {{ count }}</p>
<div>
<p><button @click="state.firstName += '1'">更改reactive-state</button></p>
<p><button @click="count++">更改reactive-count</button></p>
</div>
</template>
<script>
import { reactive, ref } from 'vue';
export default {
name: 'App',
setup() {
const simple = ref(0);
const obj = ref({ name: 'fct' });
// reactive
const state = reactive({ firstName: 'Steven', secordName: 'Diff' });
const count = reactive(0); // 报警告
function editObjName() {
obj.value.name += '1';
}
return {
simple,
obj,
state,
count,
editObjName
};
}
};
</script>
2.1 获取HTML元素或组件实例
使用ref()
函数,要声明一个同名的 ref。
<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
// 如果不使用 <script setup>,需确保从 setup() 返回 ref:
// export default {
// setup() {
// const inputRef = ref(null)
// // ...
// return {
// inputRef
// }
// }
// }
</script>
3. readonly
我们通过reactive()
或者ref()
可以获取到一个响应式的对象,但是某些情况下,我们传入响应式对象给其他地方(组件),希望在另外一个地方(组件)被使用,但是不能被修改,这个时候可以使用readonly()
。
readonly()
接受一个对象 (不论是响应式还是普通的) 或是一个ref
,返回一个原值的只读代理。
实例代码:
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 用来做响应性追踪
console.log(copy.count) // 1.打印 0
})
// 更改源属性会触发其依赖的侦听器
original.count++ // 2. 侦听器打印 1
// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!
二、reactive、ref、readonly相关API
1. reactive判断的API
1.1 isProxy
检查对象是否是由 reactive 或 readonly创建的 proxy
。
1.2 isReactive
- 检查对象
是否是由 reactive 创建的响应式代理
; - 如果
该代理是 readonly 建的
,但包裹了由 reactive 创建的另一个代理,它也会返回 true。
1.3 isReadonly
检查对象是否是由 readonly 创建的只读代理
。
1.4 toRaw
返回 reactive 或 readonly 代理的原始对象
(不建议保留对原始对象的持久引用。请谨慎使用)。
1.5 shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换
(深层还是原生对象)。
1.6 shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
(深层还是可读、可写的)。
2.toRefs
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象,数据都不再是响应式的:
import { reactive } from 'vue';
const state = reactive({
msg: 'fct',
tips: 'vue3'
});
const { msg, tips } = state;
// msg 为非响应式的
Vue为我们提供了一个
toRefs的函数
,可以将reactive返回的对象中的属性都转成ref;那么我们再次进行结构出来的 msg 本身都是 ref的;
import { reactive, toRefs } from 'vue';
const state = reactive({
msg: 'fct',
tips: 'vue3'
});
const { msg, tips } = toRefs(state);
这种做法相当于已经在state.msg
和ref.value
之间建立了链接,任何一个修改都会引起另外一个变化;
3. toRef
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
import { reactive, toRef } from 'vue';
const state = reactive({
msg: 'fct',
tips: 'vue3'
});
const tips = toRef(state, 'tips');
// tips 为响应式数据
4. ref其他的API
4.1 unref
如果我们想要获取一个ref引用中的value,那么也可以通过unref方法
:
-
如果参数是一个 ref,则返回内部值,否则返回参数本身;
-
这是 val = isRef(val) ? val.value : val 的语法糖函数;
import { ref, unref } from 'vue'; const fct = ref(999); console.log(fct.value, unref(fct));
4.2 isRef
判断值是否是一个ref对象。
4.3 shallowRef
创建一个浅层的ref对象。
// 不能监听 info.name 发生的改变,因为是浅层Ref对象
const info = shallowRef({ name: 'fct' });
// 能监听 num 发生的改变
const num = shallowRef(32);
4.4 triggerRef
手动触发和 shallowRef 相关联的副作用。
<template>
<div>
<p>info: {{ info }}</p>
<p>num: {{ num }}</p>
<button @click="info.name += '1'">更改shallowRef-info中的name</button>
<button @click="editInfoName">triggerRef-更改shallowRef-info中的name</button>
<button @click="num++">shallowRef-num++</button>
</div>
</template>
<script setup>
import { shallowRef, triggerRef } from 'vue';
const info = shallowRef({ name: 'fct' });
const num = shallowRef(32);
function editInfoName(params) {
info.value.name += '1';
// 手动触发更新
triggerRef(info);
}
</script>
三、其他API
1. computed计算属性
1.1 基本使用
与Vue2相同,推荐使用计算属性来描述依赖响应式状态的复杂逻辑。
computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value
访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value
。
<template>
<div>
<p>computed: {{ fullName }}</p>
</div>
</template>
<script>
import { computed, reactive } from 'vue';
export default {
setup() {
const state = reactive({
fName: 'Steven',
sName: 'Jiff'
});
const fullName = computed(() => {
return state.fName + '--' + state.sName;
});
return {
fullName
};
}
};
</script>
1.2 可写可读计算属性
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter
和 setter
来创建:
<template>
<div>
<p>computed-fullName: {{ fullName }}</p>
<p>computed-fullName-2: {{ fullName2 }}</p>
<button @click="editFullName2">更改fullname2</button>
</div>
</template>
<script setup>
import { computed, reactive } from 'vue';
const state = reactive({
fName: 'Steven',
sName: 'Jiff'
});
const fullName = computed(() => {
return state.fName + '--' + state.sName;
});
const fullName2 = computed({
get() {
return state.fName + '--' + state.sName;
},
set(value) {
const arr = value.split('--');
state.fName = arr[0];
state.sName = arr[1];
}
});
function editFullName2() {
fullName2.value = 'go--back';
}
</script>
2. 生命周期钩子
在setup函数中,不再需要beforeCreate
和created
两个生命周期函数。
使用前,需要在setup函数
中注册生命周期钩子函数。
举例:onMounted钩子,在组件完成初始渲染并创建 DOM 节点后运行代码,
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
Options API 和 Composition API的生命周期钩子对比:
Options API | Composition API |
---|---|
beforeCreate | not need |
created | not need |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onUnmounted |
unmounted | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
3. provide/inject
3.1 provide
使用provide()
,提供一个值,可以被后代组件注入。
provide()
接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
与注册生命周期钩子的 API 类似,provide()
必须在组件的 setup()
阶段同步调用。
<script>
import { reactive, readonly, ref, watchEffect, onMounted, provide } from 'vue';
import Son from './components/Son.vue';
export default {
name: 'App',
components: {
Son
},
setup() {
// provide/inject
const good = reactive({
id: '001',
name: '小米手机'
});
const goodNum = ref(100);
provide('good', good);
provide('goodNum', goodNum);
return {
good,
goodNum
};
}
};
</script>
3.2 inject
使用inject()
,注入一个由祖先组件或整个应用 (通过 app.provide()
) 提供的值。
参数:
- 第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,
inject()
将返回undefined
,除非提供了一个默认值。 - 第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将
false
作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。 - 第三个参数,用来指定默认值就是一个函数,而不是工厂函数。
<template>
<div>
<p>{{ good }}</p>
<p>{{ goodNum }}</p>
<button @click="good.name = 'huawei'">更改good.name</button>
<button @click="goodNum++">更改goodNum</button>
</div>
</template>
<script setup>
import { inject, watch } from 'vue';
const good = inject('good');
const goodNum = inject('goodNum');
watch(
good,
(newValue, oldValue) => {
console.log(newValue?.name, oldValue?.name);
console.log(newValue, oldValue);
},
{ immediate: true }
);
watch(goodNum, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
</script>
4.侦听器
4.1 watch
在组合式 API 中,我们可以使用 watch()
函数在每次响应式状态发生变化时触发回调函数。
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个ref (包括计算属性)
、一个响应式对象
、一个 getter 函数
、或多个数据源组成的数组
;- 第二个参数是回调函数,回调有两个参数(
newValue
新值,oldValue
旧值); - 第三个参数是配置对象
immediate
是否立即执行一次deep
深层侦听
<script setup>
import { ref, watch, watchEffect } from 'vue';
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}`)
})
</script>
- 直接给
watch()
传入一个响应式对象作为第一个参数,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发。
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
})
obj.count++
4.1.1 注意:
你不能直接侦听响应式对象的属性值:
const person = ref({ name: 'fct', age: 18 });
// 错误,因为 watch() 得到的第一个参数是一个 number
watch(person.value.age, () => {
console.log('age1');
});
// 正确做法:
watch(
// 提供一个 getter 函数
() => person.value.age,
() => {
console.log('age2');
}
);
4.2 watchEffect
watch()
是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。就可以使用watchEffect()
函数。
watchEffect()
会立即执行一遍回调函数,如果这时函数产生了副作用,Vue 会自动追踪副作用的依赖关系,自动分析出响应源。
<template>
<div>
<p>watch-person: {{ person }}</p>
<p>watch-dog: {{ dog }}</p>
<button @click="person.age++">person.age++</button>
<button @click="dog.age++">dog.age++</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const person = ref({ name: 'fct', age: 18 });
const dog = ref({ name: 'lele', age: 3 });
watchEffect(() => {
console.log(person.value, person.value.age);
});
return {
person,
dog
};
}
};
</script>
- 这个例子中,回调会立即执行。在执行期间,它会自动追踪
person.value
作为依赖(和计算属性的行为类似)。每当person.value
变化时,回调会再次执行。 - 当改变
dog.value
中的值时,watchEffect
中的回调函数不会执行。
4.2.1 停止侦听
在 setup()
或 <script setup>
中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。
但一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
手动停止侦听器:
要手动停止一个侦听器,请调用 watch
或 watchEffect
返回的函数:
const num = ref(0)
const unwatch1 = watch(num, () => {})
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
unwatch1()
4.3 回调触发的时机
当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post'
选项:
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
其中后置刷新的 watchEffect()
有个更方便的别名 watchPostEffect()
:
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
4.3.1 实例
<template>
<div>
<p>watch-person: {{ person }}</p>
<p>watch-dog: {{ dog }}</p>
<input ref="inputRef" type="text" v-model.number="person.age" />
<button @click="person.age++">person.age++</button>
<button @click="dog.age++">dog.age++</button>
</div>
</template>
<script>
import { ref, watch, watchEffect, watchPostEffect } from 'vue';
export default {
setup() {
const person = ref({ name: 'fct', age: 18 });
const dog = ref({ name: 'lele', age: 3 });
const inputRef = ref(null);
// 回调触发的时机
// 错误侦听
// watch(person.value.age, () => {
// console.log('age1');
// });
watch(
() => person.value.age,
(newValue, oldValue) => {
// if (inputRef.value) {
console.log('person.value.age--new', newValue);
console.log('person.value.age--old', oldValue);
console.log('input.target.value-组件更新前', inputRef.value.value);
// }
}
// {
// flush: 'post'
// }
);
watchEffect(
() => {
if (person.value.age) {
console.log('watchEffect-flush:post-组件更新后', inputRef.value.value);
}
},
{ flush: 'post' }
);
watchPostEffect(() => {
if (person.value.age) {
console.log('watchPostEffect-组件更新后', inputRef.value.value);
}
});
return {
person,
dog,
inputRef
};
}
};
</script>
标签:const,watch,value,reactive,state,API,Vue3,ref,Composition
From: https://www.cnblogs.com/fuct/p/16832911.html