vue3 关于组合的知识点

121 阅读3分钟

组合式api

setup

// setup 的结构,
setup(props, { slots, attrs, emit }) {
    let nameRef = ref('dj')
    
    // 里面的数据需要 return 出去,才能在外面使用
    return {
        name: nameRef
    }
}

ref知识点

  • ref
    一般用于定义基本数据类型数据 返回了 { value: xxx } 这样的结构的响应式数据 在 setup 中使用 .value 来访问 在视图中直接使用,不用加 .value
  • isRef 检查某个值是否为 ref
  • unref 如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。
const count = ref(0);  // { value: 0 }
isRef(count);  // true
unref(count); // 0
unref("world"); // world

customRef

customRef 可以创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

在get中可以调用 track() 方法

在set中可以调用 trigger() 方法

<template>
<input v-model="name" />
</template>

<script lang="ts" setup>
import { customRef, onRenderTracked, onRenderTriggered } from "vue";

const name = customRef((track, trigger) => {
  let val = "";
  return {
    get() {
      track(); // 如果有人访问name,就执行 track() 方法
      return val;
    },
    set(v) {
      val = v;
      trigger(); // 如果有人修改name,就执行 trigger() 方法
    },
  };
});
// 仅供开发时调试 ref 时使用
onRenderTracked((e) => {
  console.log('name被访问了 ‘, e);
});
onRenderTriggered((e) => {
  console.log('name被修改了 ', e)
});
</script>

reactive

定义响应式变量,一般用于定义引用数据类型。如果是基本数据类型,建议使用ref来定义。

reactive 生成的响应式的对象,修改里面的值时,不需要加value了

// computed中使用data.count会造成类型推理的循环,由于ts的局限性vue3暂时无法解决这个问题;解决方案是我们需要显式的给data指定一个类型
const data: DataProps = reactive({
  count: 0,
  increase: () => {
    data.count++;
  }, 
  double: computed(() => data.count * 2),
});

toRef

把一个 reactive对象中的某个属性变成 ref 变量

import { toRef, reactive, isRef } from 'vue';
const obj = reactive({
  name: 'dj',
  age: 18
});
const name= toRef(obj, 'name');
console.log(isRef(name)); // true

toRefs

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

可以用来解决父组件传过来的props数据解构问题。

import { toRefs, reactive, isRef } from 'vue';
const obj = reactive({
  user: 'dj',
  age: 18
});

const { user, age } = toRefs(obj);
console.log(isRef(user));
console.log(isRef(age));

shallowRef

对复杂层级的对象,只将其第一层变成 ref 响应。 (性能优化)

<template>
 <button @click="handle">修改</button>
 <div>{{ obj.a }}</div
</template>

<script lang="ts" setup>
import { shallowRef, isRef, triggerRef } from 'vue';

let obj = shallowRef({ a: { b: { c: 1 } }, d: 2 });
console.log(isRef(obj));
console.log(isRef(obj.value.a));
console.log(isRef(obj.value.a.b));

const handle = () => {
  obj.value.a.b.c ++;
  triggerRef(obj); // 修改加上这一句才会更新
}
</script>

triggerRef

强制更新一个 shallowRef 对象的渲染,例子看上一个知识点。

computed

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

重点:computed 的返回值是一个 ref 对象

import { ref, computed } from 'vue';

const count = ref(1);
// 默认只有get方法
const double = computed(() => count.value * 2);

// 可以添加 get set 方法
const doubleCount = computed({
  get() {
    return count.value * 2;
  },
  set(val) {
    count.value = val;
  }
})

watch

可以侦听单一源和多个源

// 一个源
import { ref, watch } from 'vue';

const count = ref(0);

watch(count, (newCount, oldCount) => {})
// 多个源
import { ref, watch } from 'vue';

const a = ref(0);
const b = ref(1);

watch([a, b], ([newA, newB], [oldA, oldB]) => {})

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

  • immediate 在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
  • deep 如果源是对象,强制深度遍历,以便在深层级变更时触发回调
  • flush 调整回调函数的刷新时机。
  • onTrack / onTrigger 调试侦听器的依赖。

watch 的返回值是一个停止监听的方法

const stop = watch(count, (newCount, oldCount) => {
  console.log(count, newCount, oldCount);
})

// 当已不再需要该侦听器时
stop()

watchEffect

相当于是 react中的 useEffect(),用于执行各种副作用。

const stop = watchEffect(fn),默认其 flush:'pre',前置执行的副作用。

watchPostEffect,等价于 watchEffect(fn, {flush:'post'}),后置执行的副作用。

watchSyncEffect,等价于 watchEffect(fn, {flush:'sync'}),同步执行的副作用。

watchEffect 会自动收集其内部响应式依赖,当响应式依赖发变化时,这个watchEffect将再次执行,直到你手动 stop() 掉它。

<template>
  <h1 v-text='num'></h1>
  <button @click='stopAll'>停止掉所有的副作用</button>
</template>

<script setup>
  import { ref, watchEffect } from 'vue'
  let num = ref(0)

  // 等价于 watchPostEffect
  const stop1 = watchEffect(()=>{
    // 在这里你用到了 num.value
    // 那么当num变化时,当前副作用将再次执行
    // 直到stop1()被调用后,当前副作用才死掉
    console.log('---effect post', num.value)
  }, { flush:'post'} )

  // 等价于 watchSyncEffect
  const stop2 = watchEffect(()=>{
    // 在这里你用到了 num.value
    // 那么当num变化时,当前副作用将再次执行
    // 直到stop2()被调用后,当前副作用才死掉
    console.log('---effect sync', num.value)
  }, { flush:'sync'})

  const stop3 = watchEffect(()=>{
    // 如果在这里用到了 num.value
    // 你必须在定时器中stop3(),否则定时器会越跑越快!
    // console.log('---effect pre', num.value)
    setInterval(()=>{
      num.value++
    }, 1000)
  })

  const stopAll = () => {
    stop1()
    stop2()
    stop3()
  }
</script>

provide / inject

在组件树中通信

// 祖先组件
<script setup>
  import { ref, provide } from 'vue'
  const msg = ref('Hello World')
  // 向组件树中注入数据
  // 第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值
  provide('msg', msg)
</script>

// 后代组件
<script setup>
  import { inject } from 'vue'
  /* 第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。
   * 如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。
   * 如果没有能通过 key 匹配到值,`inject()` 将返回 `undefined`,除非提供了一个默认值。
   * 第二个参数是默认值
   */
  const msg = inject('msg', 'Hello Vue')
</script>

通信

父子组件通信

在父组件申明数据,通过v-bind传到子组件,子组件通过emit通知父组件。

方法一:

// 父组件
<template>
   <Children :num="num" @changeNum="changeNum">    </Children>
</template>

<script>
setup() {
    let num = ref(0);
    const changeNum = (val) => {
      num.value = val;
    };
    
    return {
     num,
     changeNum
    }
}
</script>

// 子组件
<template>
    <div>{{ num }}</div>
    <button @click="handleChange">改变num的值</button>
</template>

<script>
props: {
    num: {
      typeNumber,
    },
},

setup(props, context) {
    const handleChange = () => {
      context.emit("changeNum", props.num + 1);
    };
    
    return {
      handleChange,
    };
  },
 </script>

方法二:

// 父组件
<template>
  <Children :num="num" @changeNum="changeNum"></Children>
</template>

<script>
setup() {
const obj = reactive({
   num0,
   changeNum(val) => {
     console.log("父组件触发: ", val);
     obj.num = val;
  },
});

return {
    ...toRefs(obj),
 }
}
</script>

// 子组件
<template>
<div>{{ num }}</div>
<button @click="handleChange">改变num的值</button>
</template>

<script>
props: {
    num: {
      typeNumber,
    },
},

setup(props, context) {
  
  const obj = reactive({
       handleChange() => {
         console.log(props.num);
         context.emit("changeNum", props.num + 1);
       },
   });

  return {
    ...toRefs(obj),
  }
},
</script>