当前位置: 首页 > article >正文

vue3-响应式核心

​🌈个人主页:前端青山
🔥系列专栏:Vue篇
🔖人终将被年少不可得之物困其一生

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

响应式核心

目录

响应式核心

3.1ref()

3.2computed ()

3.3 reactive()

3.4 readonly()

3.5 watchEffect()

3.6 watch()

3.1ref()

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

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

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

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

const count = ref(0)
console.log(count.value) // 0
​
count.value++
console.log(count.value) // 1
<!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:

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

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

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
<!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 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。

创建一个响应式对象:

const obj = reactive({ count: 0 })
obj.count++

ref 的解包:

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 的解包:

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时已经说明)

const count = ref(1)
const obj = reactive({})
​
obj.count = count
​
console.log(obj.count) // 1
console.log(obj.count === count.value) // true
<!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() 相同,但解包得到的值是只读的。

const original = reactive({ count: 0 })
​
const copy = readonly(original)
​
watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})
​
// 更改源属性会触发其依赖的侦听器

original.count++
​
// 更改该只读副本将会失败,并会得到一个警告

copy.count++ // warning
<!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()

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

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

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

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

副作用清除:

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

停止侦听器:

 const stop = watchEffect(() => {
     console.log(count.value)
 })
 // 当不再需要此侦听器时:
 const stopWatch = () => {
     stop()
 }
<!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 函数:

    const state = reactive({ count: 0 })
    watch(
      () => state.count,
      (count, prevCount) => {
        /* ... */
      }
    )

    侦听一个 ref:

    const count = ref(0)
    watch(count, (count, prevCount) => {
      /* ... */
    })

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

    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
      /* ... */
    })

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

    const state = reactive({ count: 0 })
    watch(
      () => state,
      (newValue, oldValue) => {
        // newValue === oldValue
      },
      { deep: true }
    )

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

    const state = reactive({ count: 0 })
    watch(state, () => {
      /* 深层级变更状态所触发的回调 */
    })

<!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>

                

http://www.kler.cn/news/136474.html

相关文章:

  • 爱奇艺大数据多 AZ 统一调度架构
  • 借助Agent让大模型应用思考、决策并执行任务
  • ffmpeg视频滤镜: 色温- colortemperature
  • Github优质项目推荐(第八期)
  • 合合信息亮相PRCV大会,探讨生成式AI时代的内容安全与系统构建加速
  • pycharm 中提示ModuleNotFoundError: No module named ‘distutils‘
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • 如何在公网环境下使用内网穿透工具实现用ipad pro进行代码开发
  • 上位机与下位机通讯方式(转载)
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • 【草料】uni-app ts vue 小程序 如何如何通过草料生成对应的模块化二维码
  • 命令执行无回显的判断方法及dnslog相关例题的讲解
  • git提交时会将target也提交
  • Django学习日志06
  • 007 OpenCV霍夫变换
  • vscode设置代码模板
  • 七,vi和vim
  • 基于单片机PM2.5监测系统仿真设计
  • SQL引擎子系统的工作原理
  • Leetcode—876.链表的中间结点【简单】
  • 4.3 Windows驱动开发:监控进程与线程对象操作
  • Python算法——树的序列化与反序列化
  • 【Python】 Python 操作PDF文档
  • 基于安卓android微信小程序的好物分享系统
  • 【shell】 1、bash语法超详细介绍
  • EtherCAT从站EEPROM分类附加信息详解:RXPDO(输入过程数据对象)