vue3-响应式核心

​🌈个人主页:前端青山

🔥系列专栏:Vue篇

🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来vue篇专栏内容:vue3-响应式核心

响应式核心

目录

响应式核心

3.1ref()

3.2computed ()

[3.3 reactive()](#3.3 reactive())

[3.4 readonly()](#3.4 readonly())

[3.5 watchEffect()](#3.5 watchEffect())

[3.6 watch()](#3.6 watch())

3.1ref()

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。

如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。

将一个 ref 赋值给为一个 reactive 属性时,该 ref 会被自动解包

javascript 复制代码
const count = ref(0)
console.log(count.value) // 0
​
count.value++
console.log(count.value) // 1
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ref</title>
</head>
<body>
  <div id="app">
    {{ count }}
    <button @click="add">加1</button>
    <br/>
​
    state.count: {{ state.count }}
    <button @click="increment">加10</button>
  </div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
  const { ref, reactive } = Vue
  Vue.createApp({
    setup () {
      const count = ref(10)
      const add = () => {
        count.value += 1
      }
​
      // 如果将ref赋值给 一个 reactive 属性时,该ref会被自动解包 (不需要写.value) - 了解
      const obj = reactive({}) // 剧透  reactive 用于 创建响应式的对象数据
      obj.count = count // 自动解包  不解包 obj.count = count.value 
      console.log(obj.count) // 10
      console.log(obj.count === count.value) // true
​
      // 如果将一个对象赋值给ref,那么这个对象将通过 reactive() 转为具有深层次响应的对象 - 了解
      const state = ref({ count: 100 })
      const increment = () => {
        state.value.count += 10
      }
​
      // 记住: 以后定义对象使用 reactive() 其他使用 ref()
      return {
        count, 
        add,
        state,
        increment
      }
    }
  }).mount('#app')
</script>
</html>

以后创建 非 对象类型的数据 使用 ref, 创建对象类型的数据建议使用 reactive

3.2computed ()

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

创建一个只读的计算属性 ref:

javascript 复制代码
const count = ref(1)
const plusOne = computed(() => count.value + 1)
​
console.log(plusOne.value) // 2
​
plusOne.value++ // 错误

创建一个可写的计算属性 ref:

javascript 复制代码
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})
​
plusOne.value = 1
console.log(count.value) // 0
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>computed</title>
</head>
<body>
  <div id="app">
    {{ count }} -- {{ doubleCount }} - {{ plusOne }}
    <button @click="updateCount">修改plusOne计算属性的值</button>
  </div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
  const { ref, computed } = Vue
  const setup = () => {
    const count = ref(10)
    const doubleCount = computed(() => count.value * 2) // 可读
​
    const plusOne = computed({ // 可读 可写
      set (val) { count.value = val},
      get () { return count.value}
    })
​
    const updateCount = () => {
      plusOne.value = 100 // 调用可写
    }
​
    return {
      count,
      doubleCount,
      plusOne,
      updateCount
    }
  }
​
  Vue.createApp({ setup }).mount('#app')
</script>
</html>

3.3 reactive()

返回一个对象的响应式代理。

响应式转换是"深层"的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。

值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。

返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。

创建一个响应式对象:

javascript 复制代码
const obj = reactive({ count: 0 })
obj.count++

ref 的解包:

javascript 复制代码
const count = ref(1)
const obj = reactive({ count:count })
​
// ref 会被解包
console.log(obj.count === count.value) // true
​
// 会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
​
// 也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3

注意当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包:

javascript 复制代码
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
​
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

将一个 ref 赋值给为一个 reactive 属性时,该 ref 会被自动解包:(讲解ref时已经说明)

javascript 复制代码
const count = ref(1)
const obj = reactive({})
​
obj.count = count
​
console.log(obj.count) // 1
console.log(obj.count === count.value) // true
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>reactive</title>
</head>
<body>
  <div id="app">
    <button @click="add">加1</button> {{ count }} 
    <hr />
    <button @click="increment">加10</button> {{ state.num }}
  </div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
  const { ref, reactive } = Vue
  Vue.createApp({
    setup () {
      const count = ref(0)
      const add = () => {
        count.value += 1
      }
​
      const state = reactive({ num: 10 })
      const increment = () => {
        state.num += 10
      }
​
      return {
        count, add,
        state, increment
      }
​
    }
  }).mount('#app')
</script>
</html>

初始值 对象 reactive 其余用ref

3.4 readonly()

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

javascript 复制代码
const original = reactive({ count: 0 })
​
const copy = readonly(original)
​
watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})
复制代码
​
// 更改源属性会触发其依赖的侦听器
javascript 复制代码
original.count++
复制代码
​
// 更改该只读副本将会失败,并会得到一个警告
javascript 复制代码
copy.count++ // warning
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>readonly</title>
</head>
<body>
  <div id="app">
    <button @click="add">加1</button> {{ count }} 
    <hr />
    <button @click="increment">加10</button> {{ copyCount }}
  </div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
  const { ref, readonly } = Vue
  Vue.createApp({
    setup () {
      const count = ref(0)
      const add = () => {
        count.value += 1
      }
​
      const copyCount = readonly(count)
      const increment = () => {
        copyCount.value += 10
      }
​
      return {
        count, add,
        copyCount, increment
      }
​
    }
  }).mount('#app')
</script>
</html>

3.5 watchEffect()

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求。

返回值是一个用来停止该副作用的函数。

javascript 复制代码
const count = ref(0)
​
watchEffect(() => console.log(count.value))
// -> 输出 0
​
count.value++
// -> 输出 1

副作用清除:

javascript 复制代码
watchEffect((onInvalidate) => {
    console.log(id.value) 
    const timer = setTimeout(() => {
        console.log('请求成功') // 2秒之内点击列表 只显示一次
        data.value = '数据' + id.value
    }, 2000)
    onInvalidate(() => {
        clearTimeout(timer)
    })
})

停止侦听器:

javascript 复制代码
 const stop = watchEffect(() => {
     console.log(count.value)
 })
 // 当不再需要此侦听器时:
 const stopWatch = () => {
     stop()
 }
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>watchEffect</title>
</head>
<body>
  <div id="app">
    {{ count }}
    <button @click="increment">加1</button>
    <button @click="stopWatch">停止监听</button>
​
    <!-- 假设点击第一条数据,2秒之内点击第二条,消除第一条的数据请求 -->
    <ul>
      <li @click="id=1">请求第一条数据</li>
      <li @click="id=2">请求第二条数据</li>
      <li @click="id=3">请求第三条数据</li>
    </ul>
    {{ data }}
  </div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
  const { ref, watchEffect } = Vue
  Vue.createApp({
    setup () {
      const count = ref(0)
      const increment = () => {
        count.value++
      }
​
      const stop = watchEffect(() => {
        console.log('count的值为' + count.value) 
      })
​
      const stopWatch = () => {
        stop()
      }
​
      const id = ref(1)
      const data = ref('')
​
      watchEffect((clear) => { // 自定义取消副作用的函数
        console.log(id.value) //  关键- 引起当前watchEffect的二次执行
        const timer = setTimeout(() => {
          console.log('请求成功') // 2秒之内点击列表 只显示一次
          data.value = '数据' + id.value
        }, 2000)
​
        // 消除副作用
        clear(() => {
          clearTimeout(timer)
        })
      })
​
      return {
        count, increment, stopWatch, id, data
      }
    }
  }).mount('#app')
</script>
</html>

watchEffect没有具体监听哪一个值的变化,只要内部有某一个状态发生改变就会执行

3.6 watch()

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

  • watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。

    第一个参数是侦听器的。这个来源可以是以下几种:

    • 一个函数,返回一个值

    • 一个 ref

    • 一个响应式对象

    • ...或是由以上类型的值组成的数组

    第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

    当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

    第三个可选的参数是一个对象,支持以下这些选项:

    • immediate :在侦听器创建时立即触发回调。第一次调用时旧值是 undefined

    • deep :如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器

    watchEffect() 相比,watch() 使我们可以:

    • 懒执行副作用;

    • 更加明确是应该由哪个状态触发侦听器重新执行;

    • 可以访问所侦听状态的前一个值和当前值。

  • 示例

    侦听一个 getter 函数:

    javascript 复制代码
    const state = reactive({ count: 0 })
    watch(
      () => state.count,
      (count, prevCount) => {
        /* ... */
      }
    )

    侦听一个 ref:

    javascript 复制代码
    const count = ref(0)
    watch(count, (count, prevCount) => {
      /* ... */
    })

    当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

    javascript 复制代码
    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
      /* ... */
    })

    当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

    javascript 复制代码
    const state = reactive({ count: 0 })
    watch(
      () => state,
      (newValue, oldValue) => {
        // newValue === oldValue
      },
      { deep: true }
    )

    当直接侦听一个响应式对象时,侦听器会自动启用深层模式:

    javascript 复制代码
    const state = reactive({ count: 0 })
    watch(state, () => {
      /* 深层级变更状态所触发的回调 */
    })
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>watch</title>
</head>
<body>
  <div id="app">
    {{ count }} <button @click="count++">count加1</button>
    <hr/>
    {{ state.num }} <button @click="state.num++">state.num加1</button>
  </div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
  const {ref, reactive, watch} = Vue
  
  Vue.createApp({
    setup () {
      const count = ref(0)
      watch(count, (newVal, oldVal) => { // 直接使用 自动解包
        console.log('count', newVal, oldVal)
      })
      watch(() => count.value, (newVal, oldVal) => { // 函数形式需要解包
        console.log('count-fn', newVal, oldVal)
      })
​
      const state = reactive({ num: 10 })
      watch(() => state.num, (newVal, oldVal) => { 
        console.log('state-num-fn', newVal, oldVal)
      })
      watch(() => state, (newVal, oldVal) => {  // 手动深度侦听
        console.log('state-num-fn2', newVal, oldVal)
      }, { deep: true })
​
      watch(state, (newVal, oldVal) => { // 直接监听响应式对象,会自动启动深度侦听
        console.log('state-num', newVal, oldVal)
      })
​
      // 监听多个响应式对象
      watch([count, state], ([newCount, newNum], [oldCount, oldNum]) => {
        console.log('newCount', newCount)
        console.log('oldCount', oldCount)
        console.log('newNum', newNum)
        console.log('oldNum', oldNum)
      })
      return {
        count,
        state
      }
    }
  }).mount('#app')
</script>
</html>
复制代码
相关推荐
浮华似水12 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇5 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr5 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui