【vue3入门到入土】-- 10个响应式api用法及应用场景

705 阅读6分钟

ref

创建一个响应式数据,一般来说用于创建简单类型的响应式对象,比如StringNumber类型

const name = ref('tom')

image-20211014112620953

可以看到,ref方法将这个字符串进行了一层包裹,返回的是一个RefImpl类型的对象,译为引用的实现(reference implement),在该对象上设置了一个不可枚举的属性value,所以使用name.value来读取值。

正如上面所说,ref通常用于定义一个简单类型,那么是否可以定义一个对象或者数组

const obj = ref({
  age: 12,
  sex: 'man',
})

image-20211014141726434

控制台可以看到,对于复杂的对象,值是一个被proxy拦截处理过的对象,但是里面的属性agesex不是RefImpl类型的对象,proxy代理的对象同样被挂载到value上,所以可以通过obj.value.age来读取属性,这些属性同样也是响应式的,更改时可以触发视图的更新

reactive

通过上面ref的使用案例,起始不管是复杂引用类型,如array,object等,亦或者是简单的值类型string,number都可以使用ref来进行定义,但是,定义对象的话,通常还是用reactive来实现

const person = reactive({
  height: 180,
})

image-20211014151714533

可以看到返回的person是一个proxy代理的对象,使用时person.height即可

那么能否使用reactive来定义普通类型?

const id = reactive('id是1')

image-20211014161124406

可以看到**reactive只能被用来定义对象**

ref与reactive的区别与联系

一般来说,ref被用来定义简单的字符串或者数值,而reactive被用来定义对象数组等

ref定义对象时,value返回的是proxyreactive定义对象时返回的也是proxy,而这确实存在一些联系

ref来定义数据时,会对里面的数据类型进行一层判断,当遇到复杂的引用类型时,还是会使用reactive来进行处理

class RefImpl {
    constructor(value, _shallow) {
        this._shallow = _shallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = _shallow ? value : toRaw(value);
        this._value = _shallow ? value : toReactive(value);
    }
    ////......
}
const toReactive = (value) => isObject(value) ? reactive(value) : value;// 判断参数是否为对象,是对象调用reactive创建,否则返回原始值

shallowRef与shallowReactive

refreactive都是对数据深度监听,不管是简单类型还是复杂的对象,只要发生改变时都出触发视图更新,对于深层次的对象来说,如果只是存在某些极少的属性容易发生更改,那么仍然监听这个庞大的对象整体属性无疑是对性能的浪费,这种情况可以使用 shallowRef或者shallowReactive来实现浅层次的监听

shallowRef

只监听.value属性的值的变化,对象内部的某一个属性改变时并不会触发更新,只有当更改value为对象重新赋值时才会触发更新

const foo = shallowRef({
  c: 1,
})
const change = () => {
  foo.value.c = 2 // 视图不更新
  foo.value={a:1} // 视图更新
}

shallowReactive

只监听对象的第一层属性,对嵌套的对象不做响应式处理

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})
const change = () => {
  state.foo = 2 // 视图更新
  state.nested={count:2}// 视图更新
  state.nested.bar =3 // 视图不更新
}

triggerRef

用于手动强制更新shallowRef有关的副作用,更新视图,通过上面总结看到shallowRef只监听value属性,内部嵌套的对象改变时不触发更新。而triggerRef的作用就是某些情况下能够强制触发刷新shallowRef,比如某些情况下一个对象嵌套了几百层数据,但是需要对其中一层的数据做更改,其他的都不动,全变成响应式浪费性能,非响应式又无法更新视图,这时就需要更改这层数据之后,再使用triggerRef来更新

const shallow = shallowRef({
  greet: 'Hello, world'
})
shallow.value.greet = 'Hello, universe' // 视图不会更新
triggerRef(shallow) // 手动更新相关副作用,更新视图

readonly与shallowReadonly

将对象变成只读对象

readonly

接收一个普通对象或者经过reactiveref处理过的响应式对象,使其变为只读对象,对其中的任何数据都不能进行更改

const original = reactive({ count: 0 })
const copy = readonly(original)

original.count++ // 仍然可以更改响应式对象
copy.count++ // 被readonly包裹后再更改会报警告

shallowReadonly

对象的第一层属性被设置成只读,嵌套的属仍然可以被更改

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})
state.foo++ //警告,不可被更改

state.nested.bar++ // 嵌套的属性仍然可以被更改

toRaw与markRaw

将响应式对象重新变成普通对象

toRaw

返回reactive或者readonly 代理过的proxy对象 ,或者ref定义对象时的value

const count = ref({
  a: 1,
})
const c = toRaw(count.value) // ref的value才是proxy对象
const count = reactive({
  a: 1,
})
const c = toRaw(count) // reactive 对象不需要value
const roCount = readonly(count)
const c = toRaw(roCount)  //readonly对象

markRaw

标记一个对象,使其永远不会变成响应式proxy,比如挂载一个第三方类库,那么这个类库不需要创建响应式,比如为一个响应式对象额外添加了一个属性用来展示列表,那么这个列表仅作展示使用,就不需要创建响应式,以避免性能的浪费

在vue2中。如果对一个响应式数对象foo追加一个属性bar,并不会触发视图中foo.bar的更新,这种情况需要使用$set来为foo追加属性

this.foo.bar =1 // 不会更新
this.$set(this.foo,'bar',1) // 触发更新

但是在vue3中,使用的时proxy来拦截数据,他的强大之处在于如果定义完一个响应式对象之后,再对这个对象的属性进行增删时,所追加的属性仍是响应式的,仍可以触发视图的更新

const foo =reactive({})
foo.bar =1 // 触发视图更新

但是vue3这样的做法又会存在一个问题--->某些情况下并不想让追加的数据变成响应式,这种情况需要使用markRaw来把包裹对象

const foo =reactive({a:1})
foo.bar ={b:1} // bar属性仍是响应式
foo.bar =markRaw({c:1}) // 不是响应式

customRef

用于自定义ref

  • 创建一个函数包裹customRef,这个函数用于传递初始值以及其他形参配置

  • customRef为一个函数,接收两个参数track跟踪器和trigger触发器

  • customRef返回一个带有get函数和set函数的对象,这两个函数中编写读取和写入值得操作逻辑

<input v-model="text" />

// 创建参数包裹customRef传递初始值
function useDebouncedRef(value, delay = 200) {
  let timeout
  // 返回customRef
  return customRef((track, trigger) => {
  // 返回一个带有get和set函数得对象
    return {
      // 读取值操作
      get() {
        // 设置跟踪器,跟踪这个值得改变 
        track()
        return value // 取值
      },
      // 写入值操作
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue // 渎赋值
          trigger() // 触发器,通知视图去获取数据
        }, delay)
      }
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello') // 使用
    }
  }
}