用vue写一个音频播放器的组件(二)
针对音频播放器我们接着上一篇进行分析
const songUrl = computed(() => store.getters.songUrl);
使用 `computed` 函数创建了一个计算属性 `songUrl` 。 `computed` 函数接收一个函数作为参数,在这个例子中,内部函数 `() => store.getters.songUrl` 会根据 `store` 中的 `getters.songUrl` 的值来计算并返回结果。 每当 `store.getters.songUrl` 的值发生变化时,`songUrl` 的值也会自动更新。这样可以在组件中方便地使用 `songUrl` 来获取最新的歌曲 URL 。
你还记得在vue中computed的作用吗?
1. 基于现有响应式数据进行计算和派生新的值:`computed` 可以根据已有的 `data` 、`props` 或其他 `computed` 的值进行计算,并返回一个派生的值。
2. 缓存结果:`computed` 的结果会被缓存,只有当它依赖的数据发生变化时,才会重新计算。这有助于提高性能,避免不必要的重复计算。
3. 简洁和可维护的代码:将复杂的计算逻辑封装在 `computed` 中,可以使组件的代码更加清晰、简洁和易于维护。
4. 自动更新视图:当计算属性的值发生变化时,会自动触发视图的更新,确保视图始终显示最新的计算结果。 例如,如果有一个组件需要根据用户的年龄计算是否成年,就可以使用 `computed` 来实现,而不需要在模板或方法中重复编写计算逻辑。
那store.getters是什么?
`store.getters` 用于获取 Vuex 存储中的派生数据(derived data)或计算属性(computed properties)。 `
getters` 可以对 `state` 中的数据进行加工、转换或计算,然后以一种更方便使用的方式暴露给组件。它的主要作用是:
1. 提供对状态数据的只读访问,同时可以进行一些额外的处理或计算。
2. 可以将多个组件中需要使用的相同派生数据进行集中管理和复用,避免在每个组件中重复计算。
3. 其返回值会被缓存起来,只有当它依赖的 `state` 数据发生变化时才会重新计算,提高性能。 例如,假设有一个关于考试分数的数组存储在 `state` 中,而多个组件都需要使用不及格的成绩。那么可以在 `getters` 中定义一个方法来过滤出不及格的成绩:
const store = new Vuex.Store({
state: {
scores: [80, 50, 70, 40, 90]
},
getters: {
failedScores: state => state.scores.filter(item => item < 60)
}
});
在组件中,就可以通过 `store.getters.failedScores` 来获取不及格的成绩列表。
`getters` 还支持接受其他 `getters` 作为第二个参数,以便进行更复杂的计算。
例如: `
getters: {
passedScoresCount: (state, getters) => {
return getters.passedScores.length;
}
}
此外,还可以使用 `mapGetters` 辅助函数将 `store` 中的 `getters` 映射到组件的局部计算属性中,方便在组件中使用。例如:
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters([
'failedScores',
'passedScoresCount'
])
}
}
` 这样,在组件中就可以直接使用 `failedScores` 和 `passedScoresCount` 来获取相应的派生数据。
如果 `getters` 返回的是一个函数,还可以实现给 `getter` 传参。
例如:
getters: {
getScoreById: (state) => (id) => {
return state.scores.find(score => score.id === id);
}
}
然后通过 `store.getters.getScoreById(2)` 这样的方式来根据传入的参数 `id` 获取对应的成绩。 通过使用 `store.getters`,可以更好地组织和管理 Vuex 存储中的数据,提高代码的复用性和可维护性。同时,利用其缓存和计算特性,也能优化性能,避免不必要的重复计算。 在你的代码中,`const songUrl = computed(() => store.getters.songUrl);` 这行代码通过计算属性 `computed` 获取了 `store.getters.songUrl` 的值,并将其赋值给 `songUrl`。这样,当 `store.getters.songUrl` 发生变化时,`songUrl` 也会自动更新。具体的 `songUrl` 的值是根据 Vuex 中定义的 `getters` 里的 `songUrl` 方法对状态数据进行计算或转换得到的结果。
const isPlay = computed(() => store.getters.isPlay); // 播放状态 const volume = computed(() => store.getters.volume); // 音量 const changeTime = computed(() => store.getters.changeTime); // 指定播放时刻 const autoNext = computed(() => store.getters.autoNext); // 用于触发自动播放下
在 Vuex 中,`getters` 可以被认为是 store 的计算属性。它具有以下特点和用法:
- **通过属性访问**:`getter` 会暴露为 `store.getters` 对象,你可以以属性的形式访问这些值。例如,`store.getters.doneTodos` 可以获取到相应的结果。通过这种方式访问 `getter` 时,其返回值会被 Vue 的响应式系统缓存起来,只有当它的依赖值(通常是 state 中的数据)发生改变时才会重新计算。在你的代码中,`const isPlay = computed(() => store.getters.isPlay);` 就是通过这种方式获取 `isPlay` 的值,并且这个值是具有响应性的,会随着 `store` 中 `isPlay` 的变化而自动更新。
- **接受参数**:`getter` 也可以接受其他 `getter` 作为第二个参数,以便进行更复杂的计算。
- **通过方法访问**:让 `getter` 返回一个函数,可以实现给 `getter` 传参。这在对 store 里的数组或对象进行查询等操作时非常有用。例如,定义 `gettodobyid: (state) => (id) => { return state.todos.find(todo => todo.id === id) }` 后,就可以使用 `store.getters.gettodobyid(2)` 来根据传入的参数 `2` 获取相应的数据。但通过方法访问 `getter` 时,每次都会去进行调用,而不会缓存结果。在你的代码中,没有体现通过方法访问 `getter` 的情况。
- **mapGetters 辅助函数**:`mapGetters` 辅助函数用于将 store 中的 `getter` 映射到局部计算属性。例如,`import { mapGetters } from 'vuex'` 后,可以在组件的 `computed` 选项中使用 `...mapGetters(['doneTodosCount', 'anotherGetter'])` 来将指定的 `getter` 映射为组件的计算属性。如果想给 `getter` 属性另取一个名字,可以使用对象形式,如 `...mapGetters({ doneCount: 'done TodosCount' })` ,即将 `store.getters.done TodosCount` 映射为组件中的 `doneCount` 属性。
通过使用 `getters`,可以更方便地对 store 中的数据进行处理和获取,同时避免了在多个组件中重复编写相同的数据处理逻辑,提高了代码的复用性和可维护性。并且,由于 `getter` 的结果可以被缓存(通过属性访问时),在一定程度上也提高了性能。
在你的代码中,`isPlay`、`volume`、`changeTime` 和 `autoNext` 都是通过 `computed` 函数结合 `store.getters` 来获取的具有响应性的数据。当 `store` 中对应的 `isPlay`、`volume`、`changeTime` 和 `autoNext` 值发生变化时,这些计算属性的值也会自动更新。这样,组件就可以实时获取到这些状态的最新值,并进行相应的处理或展示。例如,可以根据 `isPlay` 的值来控制音乐的播放或暂停,根据 `volume` 的值来设置音量大小等。
watch(isPlay, () => togglePlay());+ // 跳到指定时刻播放 watch(changeTime, () => (divRef.value.currentTime = changeTime.value)); watch(volume, (value) => (divRef.value.volume = value));
然后用三个watch监听播放状态,播放时间和播放音量。
watch
用于在数据变化时执行特定的回调函数,以实现对数据变化的响应和相应的处理逻辑。
function togglePlay() { isPlay.value ? divRef.value.play() : divRef.value.pause(); }
定义了刚才提到的触发播放的函数
它会检查 isPlay.value
的值,如果 isPlay.value
为真(即 isPlay
的值表示正在播放),就会调用 divRef.value
(即音频元素)的 play()
方法来开始播放;如果 isPlay.value
为假(即表示暂停),就会调用 divRef.value
的 pause()
方法来暂停播放。
function canplay() { // 记录音乐时长 proxy.$store.commit("setDuration", divRef.value.duration); // 开始播放 if (muted.value) { divRef.value.muted = false; muted.value = false; }
这个是为了回应最开始绑定<audio>t元素的 @canplay="canplay"方法,
-
左边的
@canplay
:这是 Vue 框架中用于监听音频元素的canplay
事件的语法。@
符号表示监听事件,canplay
是要监听的具体事件名称。当音频处于加载过程中,并且浏览器预计可以开始播放该音频时(但可能还没有加载足够的数据来流畅播放到结束,后续可能需要暂停以进一步缓冲内容),就会触发这个canplay
事件。 -
右边的
canplay
:这是你在 Vue 组件中定义的一个方法名。当canplay
事件被触发时,会调用这个名为canplay
的方法来执行相应的操作。你需要在组件的方法中实现canplay
方法,以处理音频可以开始播放时的逻辑。例如,你可能在这个方法中进行一些相关的状态更新、数据处理或其他与音频播放开始相关的操作 -
也就是类似
<button @click="handleClick">点击我</button>
@click是点击触发,那么@canplay是在什么情况下触发的呢
当浏览器能够开始播放指定的音频或视频时,就会触发`canplay`事件。但此时估计还没有加载足够的数据来流畅播放到结束,后续可能需要暂停以进一步缓冲内容。 音频或视频在加载过程中,会依次发生如下事件:loadstart→durationchange→loadedmetadata→loadeddata→progress→canplay→canplaythrough。 `canplay`事件不冒泡,也不可取消。所有主流浏览器都支持该事件,但 Internet Explorer 8 及之前的版本不支持。 例如,使用`addEventListener()`方法为音频元素的`canplay`事件添加一个事件监听器的示例代码如下:
const video = document.querySelector("video");
video.addEventListener("canplay", (event) => {
console.log("video can start, but not sure it will play through.");
});
` 或者通过设置`oncanplay`事件处理器属性来实现相同的功能:
const video = document.querySelector("video");
video.oncanplay = (event) => {
console.log("video can start, but not sure it will play through.");
};
言归正传,继续解析这个方法
function canplay() { // 记录音乐时长 proxy.$store.commit("setDuration", divRef.value.duration); // 开始播放 if (muted.value) { divRef.value.muted = false; muted.value = false; } // divRef.value.play(); proxy.$store.commit("setIsPlay", true); }
在 canplay
函数中,通过 proxy.$store.commit("setDuration", divRef.value.duration)
来提交一个 mutations,这可能是将音乐的时长信息更新到 Vuex 或类似的状态管理库中,以便在整个应用的其他部分可以访问和使用这个时长数据。
proxy.$store.commit("setDuration", divRef.value.duration)
这行代码的作用是将音频的时长信息提交到 Vuex 的 store 中。
在 Vuex 中,commit 用于触发 mutations(变更)来同步地修改 store 中的状态。这里的 "setDuration" 是一个 mutation 的名称,通过 commit 调用这个 mutation,并将音频的时长 divRef.value.duration
作为参数传递给它。
这样做的目的通常是为了在整个应用的多个组件之间共享和管理音频时长这个状态数据。其他组件可以通过访问 Vuex 的 store 来获取这个时长信息,从而实现对音频相关状态的统一管理和使用。例如,可能在其他地方需要根据音频的时长进行一些显示、计算或其他逻辑处理。
为什么以上代码要用proxy?
proxy
是用于实现数据响应式的一种机制。它是 ES6 中新增的特性,翻译为“代理”。
具体来说,proxy
可以理解为在目标对象之前设置的一层“拦截”。当对该对象进行访问时,都必须经过这层拦截,这意味着可以在拦截中进行各种操作,例如对原对象进行处理并返回想要的数据结构。
在给定的代码中,通过 const { proxy } = getCurrentInstance();
获取到了 proxy
对象。然后使用 proxy.$store.commit("setDuration", divRef.value.duration);
来提交数据到 Vuex 的 store 中。这里借助 proxy
来调用 $store.commit
方法,以实现将音频时长信息存储到 Vuex 状态管理中的功能。
使用 proxy
的主要优点是可以更方便地处理一些非核心逻辑,如数据的读取、设置、验证、访问控制等,从而让对象只需关注核心逻辑,达到关注点分离、降低对象复杂度等目的。
在 Vue 3 中,利用 proxy
可以更高效地实现对数据的响应式处理,解决了 Vue 2 中在处理数据响应式时存在的一些问题,例如无法监听对象属性的添加和删除、不能实时监控数组下标的变化以及数据较多且层级深时的性能问题等。
function timeupdate() { proxy.$store.commit("setCurTime", divRef.value.currentTime); } // 音乐播放结束时触发 function ended() { proxy.$store.commit("setIsPlay", false); proxy.$store.commit("setCurTime", 0); proxy.$store.commit("setAutoNext", !autoNext.value); }
`timeupdate` 函数的作用是在音乐播放时,记录音乐的当前播放位置(以秒计)并将其提交到 Vuex 的 store 中进行存储。 具体来说,通过 `proxy.$store.commit("setCurTime", divRef.value.currentTime);`
这行代码,将获取到的音频当前播放时间 `divRef.value.currentTime` ,提交到 store 中,以便在应用的其他部分可以访问和使用这个实时的播放位置信息。 而 `ended` 函数则是在音乐播放结束时被触发。它主要进行了以下操作: - `proxy.$store.commit("setIsPlay", false);`:将播放状态设置为false,表示音乐已停止播放,并提交到 store 中更新状态。 - `proxy.$store.commit("setCurTime", 0);`:将当前播放时间设置为 0,即重置播放进度。 - `proxy.$store.commit("setAutoNext",!autoNext.value);`:根据 `autoNext` 的值来设置是否自动播放下一首。如果 `autoNext.value` 原本为 true,则将其设置为 false,即不再自动播放下一首;如果原本为 false,则设置为 true,即开始自动播放下一首。这样可以实现根据应用的设置来决定是否自动切换到下一首音乐的播放。
最后是
return { songUrl, player, canplay, timeupdate, ended, muted, attachImageUrl: HttpManager.attachImageUrl, };
这部分代码是 `setup` 函数的返回值。 在 Vue 组件的 `setup` 函数中,返回的对象中的属性会暴露给组件的模板使用。 在这里:
- `songUrl` :计算属性,可能用于获取音乐链接。
- `player` :用于将音频元素引用赋值的函数。
- `canplay` 、 `timeupdate` 、 `ended` :处理音频相关事件的函数。
- `muted` :可能表示音频是否静音的状态。
- `attachImageUrl: HttpManager.attachImageUrl` :可能是用于处理图片链接的函数。 通过这样的返回,在组件的模板中就可以使用这些属性和函数来实现与音频播放相关的功能和交互。
`return` 的作用是将 `songUrl`、`player`、`canplay`、`timeupdate`、`ended`、`muted` 以及 `attachImageUrl: HttpManager.attachImageUrl` 这些属性和方法暴露出来,使得它们可以在组件的模板中被访问和使用。
例如,在模板中可以使用 `{{ songUrl }}` 来显示 `songUrl` 的值,或者通过 `@canplay="canplay"` 来绑定 `canplay` 方法作为音频的 `canplay` 事件处理函数。 总结来说,`setup` 函数通过 `return` 暴露组件所需的属性和方法,从而实现组件的逻辑设置和状态管理,使得组件的代码更加清晰、可维护和可复用。
这里是属于setup的结尾了,return返回的都是什么? setup函数的定义的属性和函数
在 Vue 3 中,
`setup` 函数是一个用于设置组件初始状态和逻辑的重要函数。它具有以下特点和作用:
- 特点: - `setup` 函数会在 `created` 之前执行,是“领先”所有钩子执行的。
- 在 `setup` 函数中访问 `this` 是 `undefined`,因为此时组件实例尚未完全创建。
- 函数中的数据和方法需要通过 `return` 暴露出来(在使用 `<script setup>` 语法糖时无需 `return`),以便在组件的模板中使用。
- 作用:
- 更灵活地组织逻辑:可以将相关逻辑按照功能进行分组,而不受 `options API` (选项式API)中选项的限制,使组件更加清晰和易于维护。
- 实现逻辑复用:能够将逻辑抽取为可复用的函数,并在 `setup` 函数中调用,避免了在 `options API` 中通过 `mixins` 或混入对象实现逻辑复用时可能出现的问题。
- 提供更好的类型推断:由于 `setup` 函数本身是一个普通的 JavaScript 函数,可以更好地与 TypeScript 配合,提供更好的类型推断和代码提示。
- 更细粒度地控制生命周期钩子:可以使用 `onMounted`、`onUpdated`、`onUnmounted` 等函数注册组件的生命周期钩子。
- 更好地管理响应式数据:可以使用 `ref`、`reactive` 等函数创建响应式数据,方便地处理组件的状态并实现数据的动态更新。
<template>
<audio :src="attachImageUrl(songUrl)" controls="true" :ref="player" preload="true" @canplay="canplay" @timeupdate="timeupdate" @ended="ended">
<!--(1)属性:controls,preload(2)事件:canplay,timeupdate,ended(3)方法:play(),pause() -->
<!--controls:向用户显示音频控件(播放/暂停/进度条/音量)-->
<!--preload:属性规定是否在页面加载后载入音频-->
<!--canplay:当音频/视频处于加载过程中时,会发生的事件-->
<!--timeupdate:当目前的播放位置已更改时-->
<!--ended:当目前的播放列表已结束时-->
</audio>
</template>
<script lang="ts">
import { defineComponent, ref, getCurrentInstance, computed, watch } from "vue";
import { useStore } from "vuex";
import { HttpManager } from "@/api";
export default defineComponent({
setup() {
const { proxy } = getCurrentInstance();
const store = useStore();
const divRef = ref<HTMLAudioElement>();
const player = (el) => {
divRef.value = el;
};
const muted = ref(true); // 添加一个 reactive 的 muted 属性
const audioDom = document.querySelector('audio');
if (audioDom) {
// 设置为静音并尝试自动播放
audioDom.muted = true;
audioDom.play()
.then(() => {
// 自动播放成功
})
.catch(error => {
// 自动播放失败,可能是因为没有用户交互
console.error('自动播放失败,需要用户交互。', error);
});
}
const songUrl = computed(() => store.getters.songUrl); // 音乐链接
const isPlay = computed(() => store.getters.isPlay); // 播放状态
const volume = computed(() => store.getters.volume); // 音量
const changeTime = computed(() => store.getters.changeTime); // 指定播放时刻
const autoNext = computed(() => store.getters.autoNext); // 用于触发自动播放下一首
// 监听播放还是暂停
watch(isPlay, () => togglePlay());+
// 跳到指定时刻播放
watch(changeTime, () => (divRef.value.currentTime = changeTime.value));
watch(volume, (value) => (divRef.value.volume = value));
// 开始 / 暂停
function togglePlay() {
isPlay.value ? divRef.value.play() : divRef.value.pause();
}
// 获取歌曲链接后准备播放
function canplay() {
// 记录音乐时长
proxy.$store.commit("setDuration", divRef.value.duration);
// 开始播放
if (muted.value) {
divRef.value.muted = false;
muted.value = false;
}
// divRef.value.play();
proxy.$store.commit("setIsPlay", true);
}
// 音乐播放时记录音乐的播放位置
function timeupdate() {
proxy.$store.commit("setCurTime", divRef.value.currentTime);
}
// 音乐播放结束时触发
function ended() {
proxy.$store.commit("setIsPlay", false);
proxy.$store.commit("setCurTime", 0);
proxy.$store.commit("setAutoNext", !autoNext.value);
}
return {
songUrl,
player,
canplay,
timeupdate,
ended,
muted,
attachImageUrl: HttpManager.attachImageUrl,
};
},
});
</script>
<style scoped>
audio {
display: none;
}
</style>
更多推荐
所有评论(0)