学黄轶老师vue3音乐APP(6-10章)

更新时间: 2021-12-23 13:39:15

# 第六章 歌单详情页与排行榜页面开发

歌单详情页和排行榜详情页还有歌手详情页的页面和功能都几乎是一样的,只是数据不太一样,所以一样的功能不要写重复的代码,可以把之前写好的singer-detail组件封装一下:

// create-detail-components.js

import MusicList from '@/components/music-list/music-list'
import storage from 'good-storage'
import { processSongs } from '@/service/song'

export default function createDetailComponent(name, key, fetch) {
    return {
        name,
        components: { MusicList },
        props: {
            data: Object
        },
        data() {
            return {
                songs: [],
                loading: true
            }
        },
        computed: {
            computedData() {
                let ret = null
                const data = this.data
                if (data) {
                    ret = data
                } else {
                    const cachedData = storage.session.get(key)
                    if (cachedData && (cachedData.mid || cachedData.id + '') === this.$route.params.id) {
                        ret = cachedData
                    }
                }
                return ret
            },
            pic() {
                const data = this.computedData
                return data && data.pic
            },
            title() {
                const data = this.computedData
                return data && (data.name || data.title)
            }
        },
        async created() {
            const data = this.computedData
            if (!data) {
                const path = this.$route.matched[0].path
                this.$router.push(path)
                return
            }
            const result = await fetch(data)
            this.songs = await processSongs(result.songs)
            this.loading = false
        }
    }
}

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

这三个组件不同的地方只有name, key, fetch三处,所以改完之后的singer-detail组件应该是这样:

<template>
    <div class="singer-detail">
        <music-list
            :songs = "songs"
            :title = "title"
            :pic = "pic"
            :loading="loading"
        ></music-list>
    </div>
</template>

<script>
    import createDetailComponent from '@/assets/js/create-detail-component'
    import { getSingerDetail } from '@/service/singer'
    import { SINGER_KEY } from '@/assets/js/constant'

    export default createDetailComponent('singer-detail', SINGER_KEY, getSingerDetail)
</script>

<style lang="scss" scoped>
    .singer-detail {
        position: fixed;
        z-index: 10;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        background: $color-background;
    }
</style>
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

剩下的歌单详情页与排行榜详情组件也类似这样去写。

黄老师强调多次的

  • 重复的东西不要写重复的代码,要考虑封装
  • 响应式数据在同一个函数内访问超过两次的,要用变量先存储,不然效率会很低下,经历多次依赖收集

# 第七章 搜索页面开发

# 搜索输入框组件

默认情况下,组件上的 v-model 使用 modelValue 作为 propupdate:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称。所以自定义的输入框组件应该这样封装:

<template>
    <div class="search-input">
        <input
            class="input-inner"
            v-model="query"
        />
    </div>
</template>

<script>
export default {
    name: 'search-input',
    props: {
        modelValue: String
    },
    data() {
        return {
            query: this.modelValue
        }
    },
    watch: {
        query(newQuery) {
            this.$emit('update:modelValue', newQuery)
        }
    }
}
</script>
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

# 防抖

输入框输入应该有个防抖功能,当连续输入的时候,延缓300ms再触发update:modelValue事件。节流用了第三方库throttle-debounce,使用这个第三方库需要使用$watch方法,不然内部this指向会有问题,同时监听modelValue的值,实现真正的双向绑定。














 
 
 
 
 
 
 








import { debounce } from 'throttle-debounce'

export default {
    name: 'search-input',
    props: {
        modelValue: String
    },
    data() {
        return {
            query: this.modelValue
        }
    },
    created() {
        this.$watch('query', debounce(300, (newQuery) => {
            this.$emit('update:modelValue', newQuery.trim())
        }))

        this.$watch('modelValue', (newVal) => {
            this.query = newVal
        })
    },
    methods: {
        clear() {
            this.query = ''
        }
    }
}
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

# 上拉加载

上拉加载使用的是@better-scroll/pull-up,需要注意的是,从数据到dom变化,需要await nextTick()千万要注意!!

这个教程因为过滤了一些付费歌曲,所以请求到的歌曲有时候不足一屏,这种情况下想要用户体验好点可以多请求几次接口:

async function searchMore() {
    // 如果没有更多歌曲了就不请求了
    if (!hasMore.value) {
        return
    }
    page.value++
    //请求数据
    const result = await search(props.query, page.value, props.showSinger)
    songs.value = songs.value.concat(await processSongs(result.songs))
    singer.value = result.singer
    hasMore.value = result.hasMore
    //一定要await nextTick!!!
    await nextTick()
    //判断需不需要再请求
    await makeItScrollMore()
}

async function makeItScrollMore() {
    // 判断能不能滚动,不能就再加载
    if (scroll.value.maxScrollY >= -1) {
        await searchMore()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 第八章 添加歌曲与用户中心页面开发

# 一级路由用户中心页面怎么加过渡动画

添加动画可以使用之前给二级路由添加动画的方法,同时加上命名视图控制只有用户中心界面有过渡动画:

<router-view :style="viewStyle"></router-view>
<router-view
    :style="viewStyle"
    name="user"
    v-slot="{Component}">
      <transition appear name="slide">
          <component :is="Component"/>
      </transition>
</router-view>
1
2
3
4
5
6
7
8
9

# 命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
1
2
3

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 第九章 性能优化与项目部署

# keep-alive 组件应用

vue3中使用,必须通过 v-slot API在 RouterView 内部使用:

<router-view v-slot="{ Component }">
  <transition>
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </transition>
</router-view>
1
2
3
4
5
6
7

需要注意的是,使用了keep-alive后,要加上钩子onActivatedonDeactivated

# 路由组件异步加载

首先分析一下打包文件,vue.config.js配置如下:




















 
 
 
 
 
 


const registerRouter = require('./backend/router')

module.exports = {
    css: {
        loaderOptions:{
            sass: {
                //全局引入变量和 mixin
                prependData: `
                    @import "@/assets/scss/variable.scss";
                    @import "@/assets/scss/mixin.scss";
                `
            }
        }
    },
    devServer: {
        before(app) {
            registerRouter(app)
        }
    },
    configureWebpack: (config) => {
        if(process.env.npm_config_report) {
            const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
            config.plugins.push(new BundleAnalyzerPlugin())
        }
    }
}
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

高亮部分的配置意思是,当执行 npm run build --report的时候,加上这个分析插件。来看下插件分析出来的东西:

  • chunk-vendors其实就是我们依赖的第三方库
  • app就是我们自己写的代码,实际上我们打包是把所有的js打包成了一个js文件

但是对于我们的APP而言,我们首屏加载并不需要加载那么多的组件和资源,这些组件可以在点击的时候再动态加载的。

# 异步加载组件

将router.js中组件的引入改成:

const Recommend = () => import('@/views/recommend')
1

这样每个对象都是一个工厂函数,通过这种方式就可以实现异步组件加载

现在点击页面,加载的就是0.js,1.js,2.js这样的js文件了,但是这种命名方式不太友好,可以加上魔术注释,/* webpackChunkName: "recommend" */

const Recommend = () => import('@/views/recommend'/* webpackChunkName: "recommend" */)
1

这个注释算是webpack的一个潜规则,加上之后点击页面加载的就是 recommend.js了

# 性能优化

其实说性能优化主要有两种:

  1. 一种是首屏的性能优化,刚刚完成的就是首屏的性能优化
  2. 还有一种就是交互的性能优化,在交互过程中,尽量避免js执行时间过长,这里放上黄老师的文章 揭秘 Vue.js 九个性能优化技巧 (opens new window)

# 项目部署

  1. 首先应该去购买服务器
  2. 申请域名
  3. 安装环境
  4. 修改代码 在vue.config.js中加两个配置:
//生产环境下不希望开启sourceMap
productionSourceMap: false,
//如果部署到子路径就要对应的设置子路径的名称
publicPath: process.env.NODE_ENV === 'production' ? '/music-next/' : '/'
1
2
3
4

base.js做一下修改,如果是生产环境前面要配上全路径:




 
















import axios from 'axios'

const ERR_OK = 0
const baseURL = process.env.NODE_ENV === 'production' ? 'http://daodao.com/music-next/' : '/'

axios.defaults.baseURL = baseURL

export function get(url, params) {
    return axios.get(url, {
        params
    }).then((res) => {
        const serverData = res.data
        if (serverData.code === ERR_OK) {
            return serverData.result
        }
    }).catch((e) => {
        console.log(e)
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. 拉取代码
  2. 配置nginx

# 第十章 课程总结

学完这门课,应该要做到以下几点:

  • 对代码要精益求精
    要想办法提升代码的可读性,维护性和扩展性,并且要合理的处理各种边界条件,从逻辑上更加严谨
  • 不盲目崇拜某项技术
    技术知识辅助开发的工具,我们千万不要过度迷恋某种工具,比如vuejs3.0新出了compositionAPI,那么我们应该因地制宜的去使用它,而不是无脑all in,而且我们还应该了解compositionAPI产生的背景,适合什么样的需求场景,甚至是它的缺点,这样我们才能更好的让技术为我们服务,而不是让自己成为技术的奴隶
  • 学以致用
    希望你能够把课程中学到的知识,开发思想,以及遇到问题如何解决问题的思路方法和技巧,运用到自己的实际工作中。并且还希望你能够常去探究问题的本质,从原理层面彻底搞明白,这样不仅能锻炼你解决问题的能力,还能让你在技术的道路上走得更远