Vue3.0之初体验
上周,Vue3.0 正式版发布,相对之前的 RC 版,变化不是很大,这几天我也体验了一下 3.0 的正式版,这里就以最近的一个小 demo 看一下 Vue3.0 带来的新特性。
这里实现一个搜索框的效果。
模板部分,和 Vue2.x 无太大差别
<template>
<div class="search-wrapper" ref="searchRef">
<input
v-model="searchText"
:class="{ 'has-result': matchList.length }"
@keydown.down.prevent="handleKeydown"
@keydown.up.prevent="handleKeyUp"
@keydown.enter.prevent="handleSelect"
@keydown.esc.prevent="handleExit"
@focus="handleFocus"
/>
<ul v-if="canShowResult" class="result-wrapper">
<li
v-for="(item, index) in matchList"
:key="index"
:class="{ active: activeIndex === index }"
@mouseover="activeIndex = index"
>
<span v-html="highlight(item.code, searchText)" class="fund-code" />
<span v-html="highlight(item.name, searchText)" />
<span class="fund-type" :style="{ color: fundTypeColors.get(item.type) || '#3e3a3a' }">{{ item.type }}</span>
</li>
</ul>
</div>
</template>
逻辑部分,我使用 Vue3.0 带来的 composition api 新特性来实现,下面是逻辑部分的全部代码
import { defineComponent, ref, watch, watchEffect, computed, onUnmounted } from 'vue';
import useDebounceRef from './useDebounceRef';
import usePage from './usePage';
import data from './data';
type MatchItem = { code: string; shortcut: string; name: string; type: string };
export default defineComponent({
setup() {
const searchText = useDebounceRef('');
const matchList = ref<MatchItem[]>([]);
const showResult = ref(false);
const searchRef = ref<HTMLDivElement>();
watch(searchText, () => {
const preList = searchText.value
? data.filter((d) => d.some((input) => input.includes(searchText.value.toUpperCase()))).slice(0, 8)
: [];
matchList.value = preList.map((item) => {
const [code, shortcut, name, type] = item;
return { code, shortcut, name, type };
});
});
watchEffect(() => {
if (searchText.value) {
showResult.value = true;
}
});
const handleClick = (e: any) => {
if (e.target !== searchRef.value && !searchRef.value?.contains(e.target)) {
showResult.value = false;
}
};
document.body.addEventListener('click', handleClick, false);
onUnmounted(() => {
document.body.removeEventListener('click', handleClick, false);
});
const canShowResult = computed(() => showResult.value && matchList.value.length);
const { prev, next, page } = usePage(matchList, true);
const fundTypeColors = new Map([
['混合型', '#d08a31'],
['债券型', '#318fbb'],
['股票型', '#ef0505'],
['货币型', '#67bf43'],
['定开债券', '#32af86'],
['联接基金', '#8e26a7'],
['QDII', '#d03077'],
['QDII-指数', '#f562a4'],
['股票指数', '#ce2222'],
['混合-FOF', '#317929'],
]);
const highlight = (text: string, keyword: string) =>
text.replace(new RegExp(keyword, 'ig'), `<span class='active'>${keyword}</span>`);
const handleSelect = () => {};
const handleExit = () => {
showResult.value = false;
};
const handleFocus = () => {
showResult.value = true;
};
return {
searchText,
matchList,
fundTypeColors,
highlight,
handleKeydown: next,
handleKeyUp: prev,
handleExit,
handleSelect,
handleFocus,
showResult,
activeIndex: page,
canShowResult,
searchRef,
};
},
});
为了让 typescript 更好的类型推断,这里使用defineComponent
api 来定义组件,如果你在 Vue3.0 的源码中查看这个函数的定义,你会发现它其实接近一个空的函数定义,如下所示,主要目的就是为了 ts 推断。
// Vue3.0源码
export function defineComponent(options: unknown) {
return isFunction(options) ? { setup: options, name: options.name } : options;
}
继续向下看。
const searchText = useDebounceRef('');
这里使用useDebounceRef
自定义 hook,它用来给 input
添加防抖功能。这也是 Vue3.0 带来的新的代码书写方式,将可以复用的逻辑抽离到自定义 hook 中。
useDebounceRef
实现代码如下
import { customRef } from 'vue';
export default function useDebounceRef<T = unknown>(value: T, delay = 200) {
let timeout: number;
/**
* 创建具有自定义ref,显式控制它的依赖跟踪(track)和触发更新(trigger)
* 它需要一个工厂函数customRef,该函数接收track和trigger作为参数,返回带有get和的set对象
*/
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue: T) {
clearTimeout(timeout);
timeout = window.setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
};
});
}
useDebounceRef
使用了 Vue3.0 中的customRef
api 来实现效果。通过使用customRef
,你可以手动的控制依赖的收集和更新的触发时机。当输入框在输入的时候,v-model
触发set
操作,此时通过setTimeout
定时器可以很容易做到输入防抖。
继续向下
const matchList = ref<MatchItem[]>([]);
const showResult = ref(false);
const searchRef = ref<HTMLDivElement>();
这里用到了ref
这个新的 api,可以看一下官方的介绍:https://v3.vuejs.org/api/refs-api.html。
通过ref
将值包裹,Vue3.0 就可以追踪值的变化,不论值是引用类型还是基本数据类型,不过,ref
包装后的值都在value
属性下。
const number = ref(0);
// 访问的时候应该为number.value
const number1 = number.value + 1;
继续向下看
watch(searchText, () => {
const preList = searchText.value
? data.filter((d) => d.some((input) => input.includes(searchText.value.toUpperCase()))).slice(0, 8)
: [];
matchList.value = preList.map((item) => {
const [code, shortcut, name, type] = item;
return { code, shortcut, name, type };
});
});
watchEffect(() => {
if (searchText.value) {
showResult.value = true;
}
});
这里用到了watch
和watchEffect
两个 api,它们有什么区别?
watch
和 Vue2.x 中区别不大,都是用来watch
属性值的变化执行一些操作,有一点不同是它的第一个参数,第一个参数表示要watch
的值,当值变化的时候,执行watch
的回调,它可以传递 Vue3.0 中的可响应对象或者一个函数,在 Vue2.x 中watch
可以是字符串,但是在这里则不行。
export default defineComponent({
props: {
propValue: String,
},
setup(props) {
const refValue = ref(0);
const state = reactive({ count: 0 });
watch(refValue, () => {
// bala bala...
});
watch(
() => state.count,
() => {
// bala bala...
},
);
watch(
() => props.propValue,
() => {
// bala bala...
},
);
},
});
watchEffect
则不用传递第一个参数,它会自动追踪它的回调函数中的响应式数据的变化,当数据变化的时候,它的回调会自动执行。这和 Vue2.x 中的computed
有点像,但是computed
无法执行副作用,而watchEffect
可以。
// 官网例子
const count = ref(0);
watchEffect(() => console.log(count.value));
// -> logs 0
setTimeout(() => {
count.value++;
// -> logs 1
}, 100);
继续向下看
const handleClick = (e: any) => {
if (e.target !== searchRef.value && !searchRef.value?.contains(e.target)) {
showResult.value = false;
}
};
document.body.addEventListener('click', handleClick, false);
onUnmounted(() => {
document.body.removeEventListener('click', handleClick, false);
});
Vue3.0 中提供了一系列的生命周期函数,具体查看:https://v3.vuejs.org/api/options-lifecycle-hooks.html,这里onMounted
表示页面初始渲染完毕时,onUnmounted
表示页面卸载时。
最后,将上述定义的函数在setup
中return
出去,之后在模板中就可以访问到了。
// ...
return {
searchText,
matchList,
fundTypeColors,
highlight,
handleKeydown: next,
handleKeyUp: prev,
handleExit,
handleSelect,
handleFocus,
showResult,
activeIndex: page,
canShowResult,
searchRef,
};
结语
Vue3.0 带来的 composition api 让页面组织代码更灵活,也解决了 Vue2.0 中mixin
代码复用命名空间的问题。
相对于 React 而言,使用 Vue3.0,你不用去考虑依赖的问题,也不用去考虑 React 中组件每次传参重复传染的问题,减少了不少的心智负担。不过呢,对于使用者来说,针对不同的项目,选择合理的框架才是关键。
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=2661