Composition API 可以更方便的抽取共通逻辑,但是不要过于在意逻辑代码复用,以功能提取代码也是一种思路。
setup
setup 是组合Composition API 中的入口函数,也是第一个要使用的函数。
setup 只在初始化时执行一次,所有的Composition API 函数都在此使用。
setup 是在beforeCreate 生命周期之前执行的,由此可以推断出setup 执行的时候,组件对象还没有创建,组件实例对象this 还不可用,此时this 是undefined ,不能通过this 来访问data/computed/methods/props 。
返回对象中的属性会与data 函数返回对象的属性合并成为组建对象的属性,返回对象中的方法会与methods 中的方法合并成为组件对象的方法,如果有重名,setup 优先。因为在setup 中this 不可用,methods 中可以访问setup 提供的属性和方法,但在setup 方法中不能访问data 和methods 里面的内容。
注意 setup 不能是一个async 函数,因为async 返回值是promise,不再是return的对象,模板中就不可以使用return 中返回对象的数据了。
setup的参数(props,context)
props:是一个对象,里面有父级组件向子级组件传递的数据,并且是在子级组件中使用props 接收到的所有的属性。
context:上下文对象,可以通过es6 语法解构setup(props,{attrs,slots,emit})
- attrs:获取当前组件标签上所有没有通过
props 接收的属性的对象,相当于this.$attrs - slots:包含所有传入的插槽内容的对象,相当于
this.$slots - emit:用来分发自定义事件的函数,相当于
this.$emit
演示props 和attrs
<template>
<h2>父组件</h2>
<Child :msg="msg" msg2="lalala" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import Child from './Child.vue';
export default defineComponent({
components: {
Child,
},
setup() {
const msg = ref('hahaha');
return { msg };
},
});
</script>
<template>
<h2>子组件</h2>
<h3>msg:{{ msg }}</h3>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
msg: {
type: String,
default: '',
},
},
setup(props, { attrs }) {
console.log('props', props);
console.log('attrs', attrs);
return {};
},
});
</script>
演示emit
<template>
<h2>父组件</h2>
<Child @show="show" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Child from './Child.vue';
export default defineComponent({
components: {
Child,
},
setup() {
const show = (data) => {
console.log(data);
};
return { show };
},
});
</script>
<template>
<h2>子组件</h2>
<n-button @click="emitFn">事件分发</n-button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
emits: ['show'],
setup(props, { emit }) {
const emitFn = () => {
emit('show', '我是子组件传过来的值');
};
return { emitFn };
},
});
</script>
ref
作用 定义一个响应式的数据(一般用来定义一个基本类型的响应式数据Undefined 、Null 、Boolean 、Number 和String )
在Vue2 中我们通过this.$refs 来获取dom 节点,Vue3 中我们通过ref 来获取节点
首先需要在标签上添加ref='xxx' ,然后在setup中定义一个初始值为null 的ref 类型,名字要和标签的ref 属性一致。
代码演示
<template> <n-input type="text" ref="inputRef" /> </template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
export default defineComponent({
setup() {
const inputRef = ref<HTMLElement | null>(null);
onMounted(() => {
inputRef.value && inputRef.value.focus();
});
return { inputRef };
},
});
</script>
reactive
作用
定义多个数据的响应式,接收一个普通对象然后返回该普通对象的响应式代理对象(proxy),响应式转换是“深层的”:会影响对象内部所有嵌套的属性,所有的数据都是响应式的。
<template>
<h3>姓名:{{ user.name }}</h3>
<h3>年龄:{{ user.age }}</h3>
<h3>wife:{{ user.wife }}</h3>
<n-button @click="updateUser">更新</n-button>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
setup() {
const user = reactive({
name: 'hw',
age: 20,
wife: {
name: 'cl',
age: 18,
books: ['小王子', '一生一世美人骨', '杨绛传'],
},
});
const updateUser = () => {
user.name = 'xxx';
user.age += 2;
user.wife.books[0] = '了不起的盖茨比';
};
return {
user,
updateUser,
};
},
});
</script>
computed
<template>
<div> </div>
</template>
<script lang="ts">
import { defineComponent, computed, reactive } from 'vue';
export default defineComponent({
setup() {
const user = reactive({
firstName: 'cl',
lastName: 'chelsy',
});
const fullName = computed(() => {
return user.firstName + user.lastName;
});
const fullName2 = computed({
get() {
return user.firstName + '_' + user.lastName;
},
set(val: string) {
const name = val.split('_');
user.firstName = name[0];
user.lastName = name[1];
},
});
return { user, fullName, fullName2 };
},
});
</script>
watch
watch的参数:
作用
监听指定的一个或多个响应式数据,一旦数据发生变化,就自动执行监听回调。默认初始时不执行回调,但可以通过配置immediate 为true ,来指定初始时立即执行一次,通过配置deep 为true ,来指定深度监听。
侦听来源类型 watch 的第一个参数可以是不同形式的来源:它可以是一个ref(包括计算属性)、一个响应式对象、一个getter函数、或多个来源组成的数组:
<template>
<div> </div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, watch } from 'vue';
export default defineComponent({
setup() {
const x = ref(0);
const y = ref(0);
watch(x, (newX) => {
console.log(`x is ${newX}`);
});
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`);
}
);
watch([x, () => y.value], (newX, newY) => {
console.log(`x is ${newX} and y is ${newY}`);
});
const obj = reactive({
count: 0,
});
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`);
}
);
return {};
},
});
</script>
watchEffect
watch() 是懒执行的:仅在侦听源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举个例子,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。我们可以这样写:
import { defineComponent, ref, watch } from 'vue';
export default defineComponent({
setup() {
const url = ref('https://xxx');
const data = ref(null);
async function fetchData() {
const res = await fetch(url.value);
data.value = await res.json();
}
fetchData();
watch(url, fetchData);
return {};
},
});
watchEffect 会立即执行一遍回调函数,如果这时函数产生了副作用,Vue会自动追踪副作用的依赖关系,自动分析响应源。上面的例子可以重写为:
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue';
export default defineComponent({
setup() {
const url = ref('https://xxx');
const data = ref(null);
async function fetchData() {
const res = await fetch(url.value);
data.value = await res.json();
}
watchEffect(async () => {
const res = await fetch(url.value);
data.value = await res.json();
});
return {};
},
});
</script>
这个例子中,回调会立即执行。在执行期间,它会自动追踪url.value作为依赖(近似于计算属性)。每当url.value变化时,回调会再次执行。
watch vs. watchEffect
watch 和 watchEffect 都能响应式地执行有副作用的回调。他们之间的主要区别是追踪响应式依赖的方式:
watch 只追踪明确侦听的源。它不会追踪任何在回调中访问到的东西。另外,仅在响应源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect ,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式property。这更方便,而且代码往往更简洁,但其响应性依赖关系不那么明确。
生命周期对比
注意:3.0中的生命周期钩子要比2.x中相同生命周期的钩子要快
toRefs
作用
把一个响应式对象转换成普通对象,该普通对象的每个属性都是一个ref 。
应用
我们使用reactive 创建的对象,如果想在模板中使用,就必须得使用xxx.xxx 的形式,如果大量用到的话还是很麻烦的,但是使用es6 解构以后,会失去响应式,那么toRefs 的作用就体现在这,利用toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的ref 属性。
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
export default defineComponent({
setup() {
const state = reactive({
name: 'cl',
});
const state2 = toRefs(state);
setInterval(() => {
state.name += '===';
}, 1000);
return {
...state2,
};
},
});
</script>
provide 与 inject
作用
实现跨层组件(祖孙)间通信
代码演示
<template>
<n-h1>父组件</n-h1>
<p>当前颜色: {{ color }}</p>
<n-button @click="color = 'red'">红</n-button>
<n-button @click="color = 'yellow'">黄</n-button>
<n-button @click="color = 'blue'">蓝</n-button>
<Son />
</template>
<script lang="ts">
import { ref, provide } from 'vue';
import Son from './Son.vue';
export default {
components: { Son },
setup() {
const color = ref('red');
provide('color', color);
return {
color,
};
},
};
</script>
<template>
<n-h2>子组件</n-h2>
<GrandSon />
</template>
<script lang="ts">
import GrandSon from './GrandSon.vue';
export default {
components: { GrandSon },
setup() {
return {};
},
};
</script>
<template>
<n-h3 :style="{ color }">孙子组件: {{ color }}</n-h3>
</template>
<script lang="ts">
import { inject } from 'vue';
export default {
setup() {
const color = inject('color');
return {
color,
};
},
};
</script>
跨组件通讯mitt.js
vue2 中怎么实现跨组件通讯呢?很多人第一想法就是event bus 。但是vue3 移除了$on 、$once 、$off 导致不能使用这个方法。但是vue 官方给大家推荐了mitt.js ,它的原理就是event bus 。
代码演示
- 安装:
npm i mitt -s - 封装一个hook
import mitt from 'mitt'
const emitter = mitt();
export default emitter;
<template>
<n-h2>父组件</n-h2>
<Child1 />
<Child2 />
</template>
<script lang="ts" setup>
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
</script>
<template>
我是子组件1
<n-h1>{{ msg }}</n-h1>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import emitter from './mitt';
export default defineComponent({
setup() {
const msg = ref('hello');
const changeMsg = () => {
msg.value = 'world';
};
onMounted(() => {
emitter.on('change-msg', changeMsg);
});
onUnmounted(() => {
emitter.off('change-msg', changeMsg);
});
return { msg, changeMsg };
},
});
</script>
<template>
我是子组件2
<n-button @click="changeMsg">点击修改msg</n-button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import emitter from './mitt.js';
export default defineComponent({
setup() {
const changeMsg = () => {
emitter.emit('change-msg');
};
return { changeMsg };
},
});
</script>
script setup语法糖
Vue3 官方提供了script setup 语法糖,只需要在script 标签中添加setup ,组件只需引入不用注册,属性和方法也不用返回,setup 函数也不需要,甚至export default 都不用写了,不仅是数据,计算属性和方法,甚至是自定义指令也可以在我们的template 中自动获得。
setup script语法糖提供了五个新的API来供我们使用:
- defineProps() & defineEmits()
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
</script>
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
例子:
<template>
<n-h2>父组件{{ son }}</n-h2>
<Child ref="son" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Child from './Child.vue';
const son = ref(null);
</script>
<template> 子组件{{ msg }} </template>
<script lang="ts" setup>
import { ref, defineExpose } from 'vue';
const msg = ref('hello');
defineExpose({
msg,
});
</script>
|