获取响应式数据
| API | 传入 | 返回 | 备注 |
|---|---|---|---|
reactive |
plain-object |
对象代理 |
深度代理对象中的所有成员 |
readonly |
plain-object or proxy |
对象代理 |
只能读取代理对象中的成员,不可修改 |
ref |
any |
{ value: ... } |
对value的访问是响应式的如果给value的值是一个对象,则会通过reactive函数进行代理如果已经是代理,则直接使用代理 |
computed |
function |
{ value: ... } |
当读取value值时,会根据情况决定是否要运行函数 |
应用:
- 如果想要让一个对象变为响应式数据,可以使用reactive或ref
- 如果想要让一个对象的所有属性只读,使用
readonly - 如果想要让一个非对象数据变为响应式数据,使用
ref - 如果想要根据已知的响应式数据得到一个新的响应式数据,使用
computed
介绍表格中四种响应式Api
vue3中的数据响应格式就两种
- proxy
- ref object
proxy
reactive
使用es6的代理实现数据的响应式
但是es6d的代理只能代理对象, 原始数据就无法实现响应式
深度代理:即使里面嵌套啦对象,嵌套对象里的属性也是响应式
readonly
传入参数:对象
使用es6的代理实现数据的响应式,只是使用es6代理下getter,所以才能只读
让一个对象的所有属性只读
但是传入的参数为代理,如下面这样
readonly > reactive > obj 如果是这样的话,
readonly无法改变数据
可以在reactive处修改数据,这样再readonly读取数据时就会发生变化
深度代理:即使里面嵌套啦对象,嵌套对象里的属性也是响应式
返回一个新的代理
readonly != reactive
ref object
ref
传入参数:任何数据
让数据成为响应式, 他会将数据放到一个对象中value属性中
即使是原始数据也会成变为响应式
如果给value的值是一个对象,则会通过reactive函数进行代理
如果已经是代理,则直接使用代理
computed
传入参数:函数
当读取值会根据情况运行函数
当连续调用且没有修改值,只会运行一次,因为会有缓存
案例
1.下面的代码输出结果是什么?
import { reactive, readonly, ref, computed } from "vue";const state = reactive({firstName: "Xu Ming",lastName: "Deng",});const fullName = computed(() => {console.log("changed");return `${state.lastName}, ${state.firstName}`;});console.log("state ready");console.log("fullname is", fullName.value);console.log("fullname is", fullName.value);const imState = readonly(state);console.log(imState === state);const stateRef = ref(state);console.log(stateRef.value === state);state.firstName = "Cheng";state.lastName = "Ji";console.log(imState.firstName, imState.lastName);console.log("fullname is", fullName.value);console.log("fullname is", fullName.value);const imState2 = readonly(stateRef);console.log(imState2.value === stateRef.value);/*** 输出结果* state ready* changed* fullname is Deng, Xu Ming* fullname is Deng, Xu Ming* false* true* Cheng Ji* changed* fullname is Ji, Cheng* fullname is Ji, Cheng* false*/
2.按照下面的要求完成函数
function useUser(){// 在这里补全函数return {user, // 这是一个只读的用户对象,响应式数据,默认为一个空对象setUserName, // 这是一个函数,传入用户姓名,用于修改用户的名称setUserAge, // 这是一个函数,传入用户年龄,用户修改用户的年龄}}
答案:
import { readonly, reactive } from "vue";function useUser() {// 在这里补全函数const userOrigin = reactive({});const user = readonly(userOrigin);const setUserName = (name) => {userOrigin.name = name;};const setUserAge = (age) => {userOrigin.age = age;};return {user, // 这是一个只读的用户对象,响应式数据,默认为一个空对象setUserName, // 这是一个函数,传入用户姓名,用于修改用户的名称setUserAge, // 这是一个函数,传入用户年龄,用户修改用户的年龄};}
3.按照下面的要求完成函数
function useDebounce(obj, duration){// 在这里补全函数return {value, // 这里是一个只读对象,响应式数据,默认值为参数值setValue // 这里是一个函数,传入一个新的对象,需要把新对象中的属性混合到原始对象中,混合操作需要在duration的时间中防抖}}
答案:
import { reactive, readonly } from "vue";function useDebounce(obj, duration) {// 在这里补全函数const valueOrigin = reactive(obj);const value = readonly(valueOrigin);let timer = null;const setValue = (newValue) => {clearTimeout(timer);timer = setTimeout(() => {console.log("值改变了");Object.entries(newValue).forEach(([k, v]) => {valueOrigin[k] = v;});}, duration);};return {value, // 这里是一个只读对象,响应式数据,默认值为参数值setValue, // 这里是一个函数,传入一个新的对象,需要把新对象中的属性混合到原始对象中,混合操作需要在duration的时间中防抖};}
监听数据变化
watchEffect
const stop = watchEffect(() => {// 该函数会立即执行,然后追中函数中用到的响应式数据,响应式数据变化后会再次执行// 该函数中用到的响应式数据,变化后会再次执行})// 通过调用stop函数,会停止监听stop(); // 停止监听
watch
// 等效于vue2的$watch// 监听单个数据的变化const state = reactive({ count: 0 })watch(() => state.count, (newValue, oldValue) => {// ...}, options)const countRef = ref(0);watch(countRef, (newValue, oldValue) => {// ...}, options)// 监听多个数据的变化watch([() => state.count, countRef], ([new1, new2], [old1, old2]) => {// ...});
注意:无论是watchEffect还是watch,当依赖项变化时,回调函数的运行都是异步的(微队列)
应用:除非遇到下面的场景,否则均建议选择watchEffect
- 不希望回调函数一开始就执行
- 数据改变时,需要参考旧值
- 需要监控一些回调函数中不会用到的数据
案例
1.下面的代码输出结果是什么
```javascript import { reactive, watchEffect, watch } from “vue”; const state = reactive({ count: 0, }); watchEffect(() => { console.log(“watchEffect”, state.count); }); watch( () => state.count, (count, oldCount) => { console.log(“watch”, count, oldCount); } ); console.log(“start”); setTimeout(() => { console.log(“time out”); state.count++; state.count++; }); state.count++; state.count++;
console.log(“end”); /**
| API | 含义 |
|---|---|
isProxy |
判断某个数据是否是由reactive或readonly |
isReactive |
判断某个数据是否是通过reactive创建的详细:https://v3.vuejs.org/api/basic-reactivity.html#isreactive |
isReadonly |
判断某个数据是否是通过readonly创建的 |
isRef |
判断某个数据是否是一个ref对象 |
转换
unref
等同于:isRef(val) ? val.value : val
应用:
function useNewTodo(todos){todos = unref(todos);// ...}
toRef
得到一个响应式对象某个属性的ref格式
const state = reactive({foo: 1,bar: 2})const fooRef = toRef(state, 'foo'); // fooRef: {value: ...}fooRef.value++console.log(state.foo) // 2state.foo++console.log(fooRef.value) // 3
toRefs
把一个响应式对象的所有属性转换为ref格式,然后包装到一个plain-object中返回
const state = reactive({foo: 1,bar: 2})const stateAsRefs = toRefs(state)/*stateAsRefs: not a proxy{foo: { value: ... },bar: { value: ... }}*/
应用:
setup(){const state1 = reactive({a:1, b:2});const state2 = reactive({c:3, d:4});return {...state1, // lost reactivity 失去响应式 此处展开后为: a:1,b:2 这可不是响应式数据...state2 // lost reactivity 失去响应式}}setup(){const state1 = reactive({a:1, b:2});const state2 = reactive({c:3, d:4});return {...toRefs(state1), // reactivity 此处展开后为:{a:refObj, b:refObj}...toRefs(state2) // reactivity}}// composition functionfunction usePos(){const pos = reactive({x:0, y:0});return pos;}setup(){const {x, y} = usePos(); // lost reactivity 没有响应式const {x, y} = toRefs(usePos()); // reactivity}
降低心智负担
这两种响应式数据格式总会让人混淆,故使用下面的方法
所有的composition function均以ref的结果返回,以保证setup函数的返回结果中不包含reactive或readonly直接产生的数据
示例:
function usePos(){const pos = reactive({ x:0, y:0 });return toRefs(pos); // {x: refObj, y: refObj}}function useBooks(){const books = ref([]);return {books // books is refObj}}function useLoginUser(){const user = readonly({isLogin: false,loginId: null});return toRefs(user); // { isLogin: refObj, loginId: refObj } all ref is readonly}setup(){// 在setup函数中,尽量保证解构、展开出来的所有响应式数据均是refreturn {...usePos(),...useBooks(),...useLoginUser()}}
