前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue3 -- 通过几行示例代码,聊一聊响应式

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

作者头像
奋飛
发布2021-08-30 09:58:18
3460
发布2021-08-30 09:58:18
举报
文章被收录于专栏:Super 前端

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

  • ref
  • reactive
  • computed
  • readonly
  • watchEffect
  • watch

App.vue

代码语言:javascript
复制
初始值:<input v-model.number="initial"/>
<count :initial="initial">count>

Count.vue

代码语言:javascript
复制
<div>当前值: {{count}}div>
<button @click="add">+1button>

<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 方法进行深层响应转换。

代码语言:javascript
复制
function add (initial) {
  initial++
}

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

代码语言:javascript
复制
let initial = 1
add(initial)
console.log(initial) // 1

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

代码语言:javascript
复制
// 模拟实现
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

代码语言:javascript
复制
<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()响应式转换是“深层的”:会影响对象内部所有嵌套的属性。

代码语言:javascript
复制
<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 的区别,主要源于其编写代码的风格。

代码语言:javascript
复制
// 风格 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 创建这个对象。
注意事项
代码语言:javascript
复制
function useMousePosition() {
  const pos = reactive({
    x: 0,
    y: 0,
  })
  return pos
}

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

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

computed

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

代码语言:javascript
复制
// 完善开头的 setup 函数
setup (props) {
  const count = computed(() => props.initial)
  
  // ...
}

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

readonly

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

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

watchEffect

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

代码语言:javascript
复制
// 完善开头的 setup 函数
setup (props) {
  const count = ref(null)
  watchEffect(() => {
    count.value = props.initial
  })
  
  // ...
}

watch

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

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

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

通过 toRef

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

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

通过 toRefs

代码语言:javascript
复制
/ 完善开头的 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
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/11/18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ref
  • reactive
  • Ref vs. Reactive
    • 注意事项
    • computed
    • readonly
    • watchEffect
    • watch
    • watchEffect vs. watch
    • 参考地址
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档