vue3知识点备忘

127 阅读7分钟

1.setup

使用setup不需要手动暴露响应式数据给模板使用

不需要引入defineProps,defineEmits,可以直接使用

导入的组件可以直接在模板中使用,无需注册

2.创建响应式数据

reactive:创建深层次的响应对象

1.返回的是原始对象的代理,和原始对象是不相等的

2.只有代理对象是响应式的,更改原始对象不会触发响应式

3.只对引用类型有效,对象/数组/set/map,对于值类型无效

4.依靠深层响应性,响应式对象内的嵌套对象依然是代理

shallowReactive:创建浅层次的响应对象

ref:针对任意值类型创建响应式引用,将传入的值包装为一个带value属性的ref对象

1.在模板中作为顶层属性被访问的时候,会被自动解包,即不需要通过value访问

2.如果不是作为顶层属性被访问不会自动解包

3.但是假如文本插值里面只有这一个属性类似{{ object.aaa }}而不是{{ object.aaa+1 }}的形式则又会被自动解包

4.嵌套在reactive创建的响应式对象里面作为属性访问的时候会被自动解包

5.但是嵌套在响应式数组或者map/set里面访问的时候又不会自动解包

computed计算属性返回的是一个ref对象

3.监听响应式的变动

watch和watchEffect

1.watch是懒执行的,仅当数据源发生变化的时候才会执行回调,假如我们想要在创建监听的时候立即执行一遍回调是做不到的,需要用watchEffect,如下

const url = ref('https://...');
const data = ref(null);

async function fetchData() {
  const response = await fetch(url.value);
  data.value = await response.json();
}

// 立即获取
fetchData();
// ...再侦听 url 变化
watch(url, fetchData);

2.watch只追踪明确的数据源,watchEffect会立即执行一遍回调函数,并且在回调函数执行期间追踪所有依赖的响应式数据,代码更简洁但是有时候数据依赖关系会不明确

3.响应式数据的变化,可能会同时触发vue组件更新和侦听器回调函数,默认情况下先执行侦听器回调函数,再触发vue组件的更新。如果想在侦听器回调函数中访问vue组件更新之后的dom,需要指明 flush: 'post' 选项

watch(source, callback, {
  flush: 'post',
});

watchEffect(callback, {
  flush: 'post',
});

后置刷新的watchEffect()有个更方便的别名watchPostEffect()

4.和vue2的一点差异

使用v-for循环渲染template的时候,key是加在template上面的

5.ref引用页面元素或组件

通过ref引用的话需要声明一个ref来存放该元素或组件的引用,并且必须和模板里的ref同名,如下

<script setup>
import { ref, onMounted } from 'vue';

// 声明一个ref来存放该元素的引用,并且必须和模板里的ref同名
const input = ref(null);

onMounted(() => {
  input.value.focus();
});
</script>

<template>
  <input ref="input" />
</template>

<script setup>
import { ref, onMounted } from 'vue';

const list = ref([
  /* ... */
]);

const itemRefs = ref([]);

onMounted(() => console.log(itemRefs.value));
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

但是并不会保证ref数组和数据源数组的顺序是一样的

6.自定义组件相关

1.props传入对象或者数组的时候,是按照引用传递的,所以在子组件内部可能会修改到父组件中的数据

2.组件触发的事件是没有冒泡的,只能在使用组件的地方监听

3.如果一个原生事件的名字(例如click)被定义在emits选项中,则监听器只会监听组件触发的click事件而不会再响应原生的click事件

4.v-model使用在自定义组件上的时候要在组件内自己实现这个语法糖的细节。默认情况下,v-model在组件上都是使用modelValue作为prop,并以update:modelValue作为对应的事件

<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

<!-- CustomInput.vue -->
<script setup>
  defineProps(['modelValue']);
  defineEmits(['update:modelValue']);
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

我们可以通过给v-model指定一个参数来更改这些名字

<MyComponent v-model:title="bookTitle" />

5.自定义组件的事件修饰符可以在组件内部通过modelModifiers访问到

6.传递给组件的属性和事件,假如组件内部通过defineProps,defineEmits声明,则会被组件消费掉;假如组件内部没有声明,则会继续向下透传给子组件

可以通过设置inheritAttrs: false进行禁用,在setup里面要用一个新的script来声明

<script>
// 使用普通的 <script> 来声明选项
export default {
  inheritAttrs: false,
}
</script>

<script setup>
// ...setup 部分逻辑
</script>

7.attribute需要应用在根节点以外的其他元素上时需要设置禁用attribute继承。这些透传进来的attribute可以在模板中的表达式中用$attrs直接访问到,包含声明为prop和事件外的所有其他attribute

foo-bar => attrs\['foo-bar'\]    @click => attrs.onClick

也可以在js中通过useAttrs访问

8.插槽内容可以访问到父组件的数据作用域,但是不能够访问到子组件的作用域

v-slot:header可以简写为#header

9.可以将作用域插槽类比为一个父组件传入子组件的函数,模板部分是由父组件确定的,子组件会将子组件内相应的数据作为参数传给它

MyComponent({
  // 类比默认插槽,将其想成一个函数
  default: (slotProps) => {
    return `${slotProps.text} ${slotProps.count}`;
  }
})

function MyComponent(slots) {
  const greetingMessage = 'hello';
  return `<div>${
    // 在插槽函数调用时传入 props
    slots.default({ text: greetingMessage, count: 1 })
  }</div>`;
}

10.vue3通过provide/inject可以向后代组件注入一个响应式的数据,而这点在vue2中是做不到的

import { ref, provide } from 'vue';

const count = ref(0);
provide('key', count);

注入一个值的时候可以没有提供者,但是需要声明一个默认值,建议使用工厂函数来创建默认值(这样用不到默认值就不会创建)

11.有时候需要在注入数据的组件中更改数据,这种情况下需要在提供数据的组件中提供一个更改数据的方法

<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue';

const location = ref('North Pole');

function updateLocation() {
  location.value = 'South Pole';
}

provide('location', {
  location,
  updateLocation,
});
</script>

<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue';

const { location, updateLocation } = inject('location');
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

如果要确保提供的数据不能被注入数据的组件更改,可以使用readonly()来包装下

<script setup>
import { ref, provide, readonly } from 'vue';

const count = ref(0);
provide('read-only-count', readonly(count));
</script>

12.使用defineAsyncComponent定义异步组件

import { defineAsyncComponent } from 'vue';

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */);
  })
});
// ... 像使用其他一般组件一样使用 `AsyncComp`

defineAsyncComponent方法接收一个返回Promise的加载函数,ES模块动态导入也会返回一个Promise,所以多数情况下我们会将它和defineAsyncComponent搭配使用

import { defineAsyncComponent } from 'vue';

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue');
);

这里注意import函数和import语句的区别

最后得到的AsyncComp是一个包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。可以使用这个异步的包装组件无缝替换原始组件,实现延迟加载

7.逻辑复用

1.组合式函数,封装有状态的逻辑

1.举个栗子

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue';

// 按照惯例,组合式函数名以 use 开头
export function useMouse() {
  // 封装和管理的状态
  const x = ref(0);
  const y = ref(0);

  // 修改状态的函数
  function update(event) {
    x.value = event.pageX;
    y.value = event.pageY;
  }

  // 挂靠在所属组件的生命周期上
  onMounted(() => window.addEventListener('mousemove', update));
  onUnmounted(() => window.removeEventListener('mousemove', update));

  // 返回管理的状态
  return { x, y };
};

<script setup>
import { useMouse } from './mouse.js';

const { x, y } = useMouse();
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

2.约定组合式函数使用use来开头,组合式函数可以调用其他的组合式函数,可以用多个较小且逻辑独立的单元来组合形成复杂的逻辑

3.看一个组合式函数携带参数的栗子

// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);

  function doFetch() {
    // 在请求之前重设状态...
    data.value = null;
    error.value = null;
    // unref() 解包可能为 ref 的值
    fetch(unref(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  if (isRef(url)) {
    // 若输入的 URL 是一个 ref,那么启动一个响应式的请求
    watchEffect(doFetch);
  } else {
    // 否则只请求一次
    // 避免监听器的额外开销
    doFetch();
  }

  return { data, error };
}

unref(maybeRef):若maybeRef是一个 ref,会返回maybeRef.value,否则maybeRef会被原样返回

4.如果组合式函数在接收ref为参数时会产生响应式副作用,可以使用watch()显式地监听此 ref,或者在watchEffect()中调用unref()来进行正确的追踪

5.最佳实践:输入要兼顾ref和原始值,输出为正常对象包裹的ref

2.自定义指令,复用涉及普通元素的底层DOM访问的逻辑

1.在<script setup>中,任何以v开头的驼峰式命名的变量都可以被用作一个自定义指令。举个栗子,vFocus即可以在模板中以v-focus的形式使用

2.自定义指令一个很常见的情况是仅仅需要在mounted和updated上实现相同的行为,除此之外并不需要其他钩子。这种情况下可以直接用一个函数来定义指令,如下

app.directive('color', (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value;
});

<div v-color="color"></div>

3.在自定义组件上使用自定义指令时,它会始终应用于组件的根节点,和透传attributes类似

3.插件 (Plugins) ,是一种能为Vue添加全局功能的工具代码

8.一些实践

对象使用reactive,其余的使用ref