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