背景
还记的那天,产品经理给我提了个需求,要我做一个组件。要求也是蛮常规的,就是做一个声音选择的下拉单选框,选中啥放啥声音(这里还有一个,就是需要上上传功能,但后续讨论只需要配置默认声音,提供选择,就没做了。做起来也不麻烦,只需要上传之后,返回一个可下载的链接使用useObjectUrl()钩子)。心想这不so easy分分钟就给你搞定。当然,你觉得一件事很简单的时候,他就越容易出问题。
问题阐述
由于我非常喜欢vueuse,所以一接到这个需求的时候,我就想到了vueuse的useSound。既然如此那就开干。
1 2
| const selectValue = ref<number | undefined>() const { play } = useSound(selectValue)
|
1
| <a-select v-model="selectValue" />
|
结果我把网页开打一看,好家伙,啥效果都没有,我寻思不可能吧,这么大个库也能出bug?
让我来瞅瞅哪里出问题了, 先来看看这个函数的ts类型
1
| declare function useSound(url: MaybeRef<string>, { volume, playbackRate, soundEnabled, interrupt, autoplay, onload, ...delegated }?: ComposableOptions): ReturnedValue;
|
嗯….只能传非空的url,然后他的源码并没有对非空url进行处理。我们实际开发者,肯定会遇到空值的情况,所以对我来说这个函数不太能符合我的需求。
怎么办呢。。。我灵机一动,我把它的源码拿下来自己改改不就行了。
具体实现
由于我现在都使用vue3来写,所以就没考虑兼容vue2,需要的童鞋自己使用vue-demi实现下把
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| <!-- index.js --> import howler from 'howler' function useSound(url, { volume = 1, playbackRate = 1, soundEnabled = true, interrupt = false, autoplay = false, onload, ...delegated } = {}) { const HowlConstructor = ref(null); const isPlaying = ref(false); const duration = ref(null); const sound = ref(null); if(unref(url)) { sound.value = new HowlConstructor.value({ src: unref(url), volume: unref(volume), rate: unref(playbackRate), onload: handleLoad, ...delegated }); } function handleLoad() { if (typeof onload === "function") onload.call(this); duration.value = (duration.value || sound.value?.duration() || 0) * 1e3; if (autoplay === true) { isPlaying.value = true; } } watch( () => [unref(url)], () => { if (unref(url)) { sound.value = new HowlConstructor.value({ src: unref(url), volume: unref(volume), rate: unref(playbackRate), onload: handleLoad, ...delegated }); } else { sound.value = void 0 } } ); watch( () => [unref(volume), unref(playbackRate)], () => { if (sound.value) { sound.value.volume(unref(volume)); sound.value.rate(unref(playbackRate)); } } ); const play = (options) => { if (typeof options === "undefined") { options = {}; } if (!sound.value || !soundEnabled && !options.forceSoundEnabled) { return; } if (interrupt) { sound.value.stop(); } if (options.playbackRate) { sound.value.rate(options.playbackRate); } sound.value.play(options.id); sound.value.once("end", () => { if (sound.value && sound.value && !sound.value.playing()) { isPlaying.value = false; } }); isPlaying.value = true; }; const stop = (id) => { if (!sound.value) { return; } sound.value.stop(typeof id === "number" ? id : void 0); isPlaying.value = false; }; const pause = (id) => { if (!sound.value) { return; } sound.value.pause(typeof id === "number" ? id : void 0); isPlaying.value = false; }; const returnedValue = { play, sound, isPlaying, duration, pause, stop }; return returnedValue; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <!-- index.d.ts --> import { HowlOptions, Howl } from 'howler'; import { Ref, ComputedRef } from 'vue';
type MaybeRef<T> = T | Ref<T> | ComputedRef<T>; interface SpriteMap { [key: string]: [number, number]; } type ComposableOptions = { volume?: MaybeRef<number>; playbackRate?: MaybeRef<number>; interrupt?: boolean; soundEnabled?: boolean; autoplay?: boolean; sprite?: SpriteMap; onload?: () => void; } & Omit<HowlOptions, 'src'>; interface PlayOptions { id?: number; forceSoundEnabled?: boolean; playbackRate?: number; } type PlayFunction = (options?: PlayOptions) => void; interface ReturnedValue { play: PlayFunction; sound: Ref<Howl | null>; stop: (id?: number) => void; pause: (id?: number) => void; isPlaying: Ref<boolean>; duration: Ref<number | null>; }
declare function useSound(url: MaybeRef<string | undefined>, { volume, playbackRate, soundEnabled, interrupt, autoplay, onload, ...delegated }?: ComposableOptions): ReturnedValue;
export { useSound };
|
把这两段代码自己封装下hook,就可以在项目中使用啦,亲测好用
总结
虽然有些需求看上去简单,实现起来也简单,但里面还是有坑的,必须小心别踩坑了。
最后想感叹句,站在巨人的肩膀上,真的舒服啊。
代码参考: vueuse useSound