vue3 -- 通过几行示例代码,聊一聊响应式

Vue3 中关于响应式的 API (@vue/reactivity)有以下几个,下面通过使用不同的 Api 实现下述示例,来做一个对比和总结 :

  • ref
  • reactive
  • computed
  • readonly
  • watchEffect
  • watch
    在这里插入图片描述
    App.vue
初始值:<input v-model.number="initial"/>
<count :initial="initial"></count>

Count.vue

<div>当前值: {{count}}</div>
<button @click="add">+1</button>

<script>
<script>
export default {
  name: "Count",
  props: {
    initial: Number
  },
  setup (props) {
    
    /* 后续使用 computed、watchEffect、watch 不同方式补充实现*/
    // ...
    
    function add () {
      count.value++
    }

    return {
      count,
      add
    }
  }
} 
</script>

Props: 注意 props 对象是响应式的,watchEffect 或 watch 会观察和响应 props 的更新;然而不要解构 props 对象,那样会使其失去响应性

ref

赋予原始数据类型 响应式的特性

接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value。如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

function add (initial) {
  initial++
}

如果 initial 是一个例如 number 的基础类型,那么当被返回时,它与内部逻辑之间的关系就丢失了!这是由于 JavaScript 中基础类型是值传递而非引用传递。

let initial = 1
add(initial)
console.log(initial) // 1

在把值作为 property 赋值给某个对象时也会出现同样的问题。一个响应式的值一旦作为 property 被赋值或从一个函数返回,而失去了响应性之后,也就失去了用途。我们可以将这个值上包裹到一个对象中再返回。

// 模拟实现
function ref (initial) {
  return {value: initial}
}

function add (initial) {
  initial.value++
}
let initialRef = ref(1)
add(initialRef)
console.log(initialRef.value)	// 2

现在我们可以通过引用来传递计算值,也不需要担心其响应式特性会丢失了。当然代价就是:为了获取最新的值,我们每次都需要写 .value

注意:当 ref 作为渲染上下文的属性返回(即在 setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value

<template>
  <div>Hello {{countRef}}</div>
  <button @click="add">增加</button>
</template>
<script>
import { ref } from '@vue/reactivity'
export default {
  name: "Test1",
  setup () {
    let countRef = ref(0)
    function add () {
      countRef.value++
    }

    return {
      countRef,
      add
    }
  }
};
</script>
  • 模板中可以直接使用 {{countRef}}
  • script 中需要使用 countRef.value
  • 区别「响应式值引用」与普通的基本类型值与对象:所有的 ref 名可以加类似 xxxRef 的后缀。

reactive

赋予对象(Object) 响应式的特性

接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()响应式转换是“深层的”:会影响对象内部所有嵌套的属性。

<template>
  <div>Hello {{state.count}}</div>
  <div>double {{state.double}}</div>
  <button @click="add">增加</button>
</template>
<script>
import { reactive, computed } from '@vue/reactivity'
export default {
  name: "Test2",
  setup () {
    let count = ref(0)
    const state = reactive({
      count,	 // count: 0
      double: computed(() => state.count * 2),
    })
    function add () {
      state.count++
    }

    return {
      state,
      add
    }
  }
};
</script>
  • 当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值

Ref vs. Reactive

使用 refreactive 的区别,主要源于其编写代码的风格。

// 风格 1: 将变量分离
let x = 0
let y = 0

function updatePosition(e) {
  x = e.pageX
  y = e.pageY
}

// --- 与下面的相比较 ---

// 风格 2: 单个对象
const pos = {
  x: 0,
  y: 0,
}

function updatePosition(e) {
  pos.x = e.pageX
  pos.y = e.pageY
}
  • 如果使用 ref,我们实际上就是将风格 (1) 转换为使用 ref (为了让基础类型值具有响应性) 的更细致的写法。
  • 使用 reactive 和风格 (2) 一致。我们只需要通过 reactive 创建这个对象。

注意事项

function useMousePosition() {
  const pos = reactive({
    x: 0,
    y: 0,
  })
  return pos
}

// 这里会丢失响应性!
const { x, y } = useMousePosition()
// 这里会丢失响应性!
{ ...useMousePosition() }
// 保持响应式
{ pos: useMousePosition() }
function useMousePosition() {
  // ...
  return toRefs(pos)
}

// x & y 现在是 ref 形式了!
const { x, y } = useMousePosition()
  1. 就像你在普通 JavaScript 中区别声明基础类型变量与对象变量时一样区别使用 refreactive。我们推荐你在此风格下结合 IDE 使用类型系统。
  2. 所有的地方都用 reactive,然后记得在组合函数返回响应式对象时使用 toRefs。这降低了一些关于 ref 的心智负担,但并不意味着你不需要熟悉这个概念。

computed

传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象

// 完善开头的 setup 函数
setup (props) {
  const count = computed(() => props.initial)
  
  // ...
}

count 不可修改,因此导致 add() 函数不可用;此种方式行不通!

readonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。

可以使用该属性来包裹项目中的字典数据!

watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

// 完善开头的 setup 函数
setup (props) {
  const count = ref(null)
  watchEffect(() => {
    count.value = props.initial
  })
  
  // ...
}

watch

watch API 完全等效于 2.x this.$watch (以及 watch 中相应的选项)。watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。

侦听器的数据源可以是一个拥有返回值的 getter 函数,也可以是 ref

// 完善开头的 setup 函数
setup (props) {
  const count = ref(null)
  // isRef(props.initial) => false
  watch(() => props.initial, (initial, prvInitial) => {
    count.value = initial
  }, {immediate: true})
  
  // ...
}

通过 toRef

// 完善开头的 setup 函数
setup (props) {
  const count = ref(null)
  watch(toRef(props, 'initial'), (initial, prvInitial) => {
    count.value = initial
  }, {immediate: true})
  
  // ...
}

toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。

通过 toRefs

/ 完善开头的 setup 函数
setup (props) {
  const count = ref(null)
  // isReactive(props) => true
  const {initial} = toRefs(props)
  watch(initial, (initial, prvInitial) => {
    count.value = initial
  }, {immediate: true})
  
  // ...
}

toRefs 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。

watchEffect vs. watch

  • watch 懒执行副作用;
  • watch 更明确哪些状态的改变会触发侦听器重新运行副作用;
  • watch 访问侦听状态变化前后的值。

参考地址

  • https://vue-composition-api-rfc.netlify.app/zh/#api-%E4%BB%8B%E7%BB%8D
  • https://v3.vuejs.org/api/computed-watch-api.html#watching-a-single-source
奋飛 CSDN认证博客专家 技术管理 前端工程化
乐观、勇气、专注、果断、好奇、公正、慎思、真诚、追求极致追求完美、诚信!独立撰写了多个前端专题模块,访问量达百万级。多次负责组织大数据可视化前端架构平台开发工作。对前端新技术、新潮流具有很强的敏锐力和洞察力!
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页