VUE3学习 第三章 认识 ref全家桶、Reactive全家桶、to系列全家桶、computed计算属性、watch侦听器、watchEffect高级侦听器

一、ref全家桶

1. ref

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。

案例:
我们这样操作是无法改变message 的值 应为message 不是响应式的无法被vue 跟踪要改成ref

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
<script setup lang="ts">
let message: string = "我是message"
 
const changeMsg = () => {
   message = "change msg"
}
</script>
 
<style>
</style>

需要改为 ref 、 Ref TS对应的接口


interface Ref<T> {
  value: T
}

注意被ref包装之后需要.value 来进行赋值

2. isRef

用来 判断是不是一个ref对象

import {ref, Ref, isRef} from 'vue'
// 这里 ref 是做双向绑定用的   Ref 是TS的用法
let message: Ref<string | number> = ref("111")
let notRef: number = 222
const changeMsg = () => {
  console.log(isRef(message)); //true
  console.log(isRef(notRef)); //false
}

3. ref浏览器显示配置

ref在浏览器的console输出显示过于复杂,可以配置一下浏览器设置
在F12中 点击设置 勾选 启动自定义格式设置工具即可
次数 显示的 log 为 Ref<“111”>

4. shallowRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的 ( 注意! 一定不要和 ref 一起写, 不然他的值也会变成响应式)

例子:
修改其属性是非响应式的这样是不会改变的


<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "小满"
})
 
const changeMsg = () => {
  message.value.name = '大满' // 这样修改无法改变
}

const changeMsg = () => {
  message.value = { name: "大满" } // 修改他的 value 才会被监听到 从而发送改变
}
</script>
 
<style>
</style>

5. triggerRef

强制更新页面DOM

这样也是可以改变值的 (可以使shallowRef的值强制刷新,从而刷新dom )

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
<script setup lang="ts">
import { Ref, shallowRef,triggerRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "小满"
})
 
const changeMsg = () => {
  message.value.name = '大满'
 triggerRef(message)
}
</script> 
 
 
<style>
</style>

6. customRef 和 ref 直接获取dom元素

自定义ref

customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的

<template>

 <div ref="dom">   // ref 可以获取dom元素  ,但是 ref = "dom"
    wsh
 </div>
<hr>
<div>
  {{name}}
</div>
<hr>
<button @click="change">修改</button>
</template>

<script setup lang='ts'>
import {ref, reactive, onMounted, shallowRef, customRef} from 'vue'

function myRef<T>(value: T) {
  let timer:any;
  return customRef((track, trigger) => {
    return{
      get() {
        track()
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          console.log('dianjiset')
          value = newVal
          trigger()
        }, 500)
      }
    }
  })

}

const name = myRef<string>('1111')
const change = () => {
  name.value = '222'
}

const dom = ref<HTMLDivElement>()
console.log(dom.value?.innerText) // 记得 做 value? 判断 不然可能报错

</script>

<style scoped>

</style>

二、Reactive全家桶

1. reactive

reactive 用来绑定复杂的数据类型(Object); 例如 对象 数组

如果绑定了普通的数据类型会报错 ( 源码进行了 ts 的约束 )

import { reactive} from 'vue'
const person = reactive('1111') // 这样是报错的

绑定普通的数据类型 我们可以 使用昨天讲到ref

你如果用ref去绑定对象 或者 数组 等复杂的数据类型 源码里面其实也是 去调用reactive

使用reactive 去修改值无须.value,而 ref 取值 赋值 都需要加.value

1. reactive 基础用法

import { reactive } from 'vue'
let person = reactive({
   name:"小满"
})
person.name = "大满"

2. 数组异步赋值问题
reactive proxy 不能直接赋值,否则破坏响应式对象的

let person = reactive<number[]>([])
setTimeout(() => {
  person = [1, 2, 3] // 这里不能直接赋值
  console.log(person);
  
},1000)

1. 解决方案一  使用 push 叫 结构赋值
const arr = [1,2,3] 
person.push(...arr)

2.方案二  在外面包一层对象
type: Person = {
	list?: Array<number>
}
let person = reactive<Person>(
	{
		list: []
	}
)
setTimeout(() => {
	const arr = [1,2,3]
	person.list = arr;  // 以这种方式
})

2. readonly

拷贝一份proxy对象将其设置为只读


import { reactive ,readonly} from 'vue'
const person = reactive({count:1})
const copy = readonly(person)
 
 //person.count++  但是 修改了 person  copy会发生改变

 copy.count++  直接修改 copy 是无法修改的

3. shallowReactive

只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图 (跟 ref 的 shallow 一个意思)

案例:

<template>
  <div>
    <div>{{ state }}</div>
    <button @click="change1">test1</button>
    <button @click="change2">test2</button>
  </div>
</template>
 
 
 
<script setup lang="ts">
import { shallowReactive } from 'vue'
 
 
const obj = {
  a: 1,
  first: {
    b: 2,
    second: {
      c: 3
    }
  }
}
 
const state = shallowReactive(obj)
 
function change1() {
  state.a = 7  // 这个会修改dom展示
}
function change2() {
  state.first.b = 8  // 这个只改数据  不改显示
  state.first.second.c = 9
  console.log(state);
}
 
 
 
 
</script> 
 
 
<style>
</style>

三、to系列全家桶(toRef toRefs toRaw)

1. toRef

如果原始对象是非响应式的就不会更新视图 数据是会变的

如果原始对象是响应式的是会更新视图并且改变数据的


<template>
   <div>
      <button @click="change">按钮</button>
      {{state}}
   </div>
</template>
 
<script setup lang="ts">
import { reactive, toRef } from 'vue'
 
const obj = {
   foo: 1,
   bar: 1
}
 
 
const state = toRef(obj, 'bar')
// bar 转化为响应式对象
 
const change = () => {
   state.value++
   console.log(obj, state);
 
}
</script>

源码解析 toRef

  1. 如果是ref 对象直接返回 否则 调用 ObjectRefImpl 创建一个类ref 对象
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]> {
  const val = object[key]
  return isRef(val)    // 主要是这里
    ? val
    : (new ObjectRefImpl(object, key, defaultValue) as any)
}
  1. 类ref 对象只是做了值的改变 并未处理 收集依赖 和 触发依赖的过程 所以 普通对象无法更新视图
class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true
 
  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}
 
  get value() {
    const val = this._object[this._key]
    return val === undefined ? (this._defaultValue as T[K]) : val
  }
 
  set value(newVal) {
    this._object[this._key] = newVal   // 这里只改了值,没有做其他处理
  }
}

2. toRefs

可以帮我们批量创建ref对象主要是方便我们解构使用

import { reactive, toRefs } from 'vue'
const obj = reactive({
   foo: 1,
   bar: 1
})
 
let { foo, bar } = toRefs(obj)
 
foo.value++
console.log(foo, bar);

toRefs 源码解析
其实就是把reactive 对象的每一个属性都变成了ref 对象循环 调用了toRef


export type ToRefs<T = any> = {
  [K in keyof T]: ToRef<T[K]>
}
export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}

3. toRaw

将响应式对象转化为普通对象

import { reactive, toRaw } from 'vue'
 
const obj = reactive({
   foo: 1,
   bar: 1
})
 
 
const state = toRaw(obj)
// 响应式对象转化为普通对象
 
const change = () => {
 
   console.log(obj, state);
 
}

toRaw 源码解析
通过 ReactiveFlags 枚举值 取出 proxy 对象的 原始对象

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}
 
export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

四、computed计算属性

1. computed用法

计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值

  1. 函数形式
import {computed, reactive, ref } from 'vue'
let price = ref(0)

let m = computed<string>(() => {
	return '111' + price.value
})

price.value = 222
  1. 对象形式
<template>
   <div>{{ mul }}</div>
   <div @click="mul = 100">click</div>
</template>
 
<script setup lang="ts">
import { computed, ref } from 'vue'
let price = ref<number | string>(1)//$0
let mul = computed({
   get: () => {
      return price.value
   },
   set: (value) => {
      price.value = 'set' + value
   }
})
</script>
 
<style>
</style>

2. computed购物车案例


<template>
   <div>
      <table style="width:800px" border>
         <thead>
            <tr>
               <th>名称</th>
               <th>数量</th>
               <th>价格</th>
               <th>操作</th>
            </tr>
         </thead>
         <tbody>
            <tr :key="index" v-for="(item, index) in data">
               <td align="center">{{ item.name }}</td>
               <td align="center">
                  <button @click="AddAnbSub(item, false)">-</button>
                  {{ item.num }}
                  <button @click="AddAnbSub(item, true)">+</button>
               </td>
               <td align="center">{{ item.num * item.price }}</td>
               <td align="center">
                  <button @click="del(index)">删除</button>
               </td>
            </tr>
         </tbody>
         <tfoot>
            <tr>
               <td></td>
               <td></td>
               <td></td>
               <td align="center">总价:{{ $total }}</td>
            </tr>
         </tfoot>
      </table>
   </div>
</template>
 
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
type Shop = {
   name: string,
   num: number,
   price: number
}
let $total = ref<number>(0)
const data = reactive<Shop[]>([
   {
      name: "袜子",
      num: 1,
      price: 100
   },
   {
      name: "裤子",
      num: 1,
      price: 200
   },
   {
      name: "衣服",
      num: 1,
      price: 300
   },
   {
      name: "毛巾",
      num: 1,
      price: 400
   }
])
 
const AddAnbSub = (item: Shop, type: boolean = false): void => {
   if (item.num > 1 && !type) {
      item.num--
   }
   if (item.num <= 99 && type) {
      item.num++
   }
}
const del = (index: number) => {
   data.splice(index, 1)
}
 
 
 
$total = computed<number>(() => {
   return data.reduce((prev, next) => {
      return prev + (next.num * next.price)
   }, 0)
})
 
 
 
</script>
 
<style>
</style>

五、watch侦听器

watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用

watch第一个参数监听源

watch第二个参数回调函数 cb(newVal,oldVal)

watch第三个参数一个options配置项是一个对象 {
immediate:true //是否立即调用一次
deep:true //是否开启深度监听
}

1. 监听 Ref 案例 (基本类型和数组类型,基本数据和对象类数据)

import { ref, watch } from 'vue'
 
let message = ref({
    nav:{
        bar:{
            name:""
        }
    }
})
 
 
watch(message, (newVal, oldVal) => {
    console.log('新的值----', newVal);
    console.log('旧的值----', oldVal);
},{
    immediate:true,
    deep:true // 如果是 ref定义的响应式 这里需要设置为true  不然无法深度监听,  但是 如果是 reactive的模式, 则不用
})

// 监听多个ref 注意变成数组时

import { ref, watch ,reactive} from 'vue'
 
let message = ref('')
let message2 = ref('')
 
watch([message,message2], (newVal, oldVal) => {
    console.log('新的值----', newVal);
    console.log('旧的值----', oldVal);
})

2. 监听Reactive

使用reactive监听深层对象开启和不开启deep 效果一样


import { ref, watch ,reactive} from 'vue'
 
let message = reactive({
    nav:{
        bar:{
            name:""
        }
    }
})
 
 
watch(message, (newVal, oldVal) => {
    console.log('新的值----', newVal);
    console.log('旧的值----', oldVal);
})

案例2 监听reactive 单一值 ( 这里 watch的第一个参数可以写成函数的形式)

import { ref, watch ,reactive} from 'vue'
 
let message = reactive({
    name:"",
    name2:""
})
 
 
watch(()=>message.name, (newVal, oldVal) => {
    console.log('新的值----', newVal);
    console.log('旧的值----', oldVal);
})

六、watchEffect高级侦听器

1. watchEffect

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

如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次


let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect(() => {
    //console.log('message', message.value);
    console.log('message2', message2.value);
})

2. 清除副作用(oninvalidate)

就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖


import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
     
    })
    console.log('message2', message2.value);
})

停止跟踪 watchEffect 返回一个函数 调用之后将停止更新

const stop =  watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
 
    })
    console.log('message2', message2.value);
},{
    flush:"post",
    onTrigger () {
 
    }
})
stop()

3. 更多的配置项

副作用刷新时机 flush 一般使用post

更新时机
pre 组件更新前执行
synv 强制效果始终同步触发
post 组件更新后执行

onTrigger 可以帮助我们调试 watchEffect (主要用于 debugger 测试)

import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
 
    })
    console.log('message2', message2.value);
},{
    flush:"post",
    onTrigger () {
        debugger
    }
})

你可能感兴趣的:(vue3+ts+vite,学习,javascript,前端)