05-uniapp 开发 ImoocBlog
1)开篇
经过前面四个章节,我们已经完成了 微信小程序 的学习。那么从这一章开始我们就进入 uniapp
的项目开发之中。
整个 uniapp
阶段我们会完成一个正式的项目 慕课热搜 ,以这个项目来作为 uniapp
学习阶段的的最终产出,同时通过这个项目来贯穿所有的 uniapp
知识点,可以让大家在学习的过程中不至于那么枯燥。
目前我们的项目已经上线了:
- 微信小程序:微信 -> 发现 -> 小程序 -> 搜索《慕课热搜》
H5
:https://imooc.blog.lgdsunday.club/#/
那么现在:
- html、css、js、微信小程序,等基础知识
- 接口文档、最终效果、等各种项目需求
各种前置条件已经全部准备就绪,项目开发即可开始!
2)uniapp 难吗?
1、引言
《慕课热搜》基于 uniapp
进行开发,关于 uniapp
的优点 -> 在【课程导学】阶段已经描述过了,如果你忘了,那么你可以回过头去看一下。
这一小节我们来点实在的,光知道它好,不行。因为不是你的,它再好,对你而言也是没啥用的……
那么怎么才能学会它呢?或者说它难学吗?这才是这一小节我们需要说明的内容。
2、内容
想要学习 uniapp
那么需要有三个前置条件:
具备了这 3 个条件,学习 uni-app ,对你而言就是小菜一碟
html + css + js
: 这个相信大家都没有问题- 微信小程序:这个我们已经在前面的章节非常详细的为大家讲解过了 -> uni-app 借鉴了很多小程序的内容
vue
:可能有很多同学一看这个,心就凉了一截。我不会vue
咋办啊。.. 没有关系! 我敢把这个列出来,肯定就已经为大家想到了这么一点。vue
的理念和 微信小程序 的理念有非常多相同的地方,在我们后面进行项目开发的过程中,遇到一些个别的语法时,我会为大家进行介绍的。
总之,对于大家来说,这三个条件,如果你全部具备,那自然是最好的。
如果你只具备前两个条件,也不要担心,甚至可以说是更加幸运 -> 为啥这么说? -> 因为接下来你将会在学会 uniapp
的同时,也会掌握 vue
的核心使用!
学会 uni-app 难吗? -> uni-app 的语法可以简单理解成「vue 语法」和「小程序语法」的结合体 -> 这显然不难!
3)配置 uniapp 开发环境
之前开发微信小程序之前,需要:
- 申请小程序账号
- 下载微信小程序开发工具
同样,在进行 uniapp 开发之前,我们也需要做类似这样的事情,也就是需要配置项目的开发环境
uniapp 项目的开发环境主要有两点:
- 下载并安装开发者工具 HBuilder X
- 安装 sass 依赖 -> 项目需要用到 sass 语法
1、下载并安装开发工具
uniapp
同样提供了一个专门的开发工具HBuilder X
-> HBuilderX 下载页面- 点击
DOWNLOAD
- 选择
App 开发版本
(因为我们要实现是慕课热搜) -> 推荐使用正式版 Windows
版本下载完成之后会得到一个zip
的压缩包文件,解压完成即可使用 -> 是一个便携版MacOS
版本下来完成会得到一个dmg
的安装包,直接安装即可
sass
依赖
2、安装 因为我们的项目开发会使用 sass
,所以需要为 HBuilder X
安装 sass 编译器
。
- 打开
HBuilder X
- 打开插件地址:https://ext.dcloud.net.cn/plugin?id=2046 -> 要登录账号,没有账号,那就注册
- 点击【使用
HBuilderX
导入插件】 -> 一定要去掉你浏览器的广告插件,不然,这是不会出现这个按钮的 - 在弹出框中点击【打开
HBuilderX
】
- 点击【使用
- 点击【是】
- 此时会在
HBuilderX
右下角,提示你【正在下载】 - 等待完成即可
4)创建 imooc-blog
本小节做两件事:
- 创建项目 -> 开发工具会自动帮我们生成目录结构,就像微信小程序开发者工具一样
- 了解项目基本组成结构
1、创建 uni-app 项目
打开 HBuilderX -> 文件 -> 新建 -> 项目:
效果:
2、项目目录介绍
├─pages // 页面存放文件夹,等同于 微信小程序中的 pages │ └─index.vue //
默认生成的页面 ├─static // 静态资源存放文件夹 └─uni_modules // uni-app 组件目录
│ └─uni-xxx // uni-app 所提供的业务组件,等同于 微信小程序中的组件 ├─App.vue //
应用配置文件,用来配置全局样式、生命周期函数等,等同于 微信小程序中的 app.js
└─main.js // 项目入口文件 -> 初始化 Vue ├─mainfest.json //
配置应用名称、appid、logo、版本等打包信息, └─pages.json //
配置页面路径、窗口样式、tabBar 等页面类信息,等同于 微信小程序中的 app.json
└─uni.scss // uni-app 内置的常用样式变量
5)运行项目到 微信开发者工具
uniapp
支持 10 个平台,我们以 微信小程序 和 h5
平台为例子,进行演示。
为啥只适配这两个端? -> 因为其它平台很少使用,比如 360 小程序
1、运行到 微信小程序
- 配置【微信开发工具】路径:工具 -> 设置
- 设置【微信开发工具路径】:运行配置 -> 微信开发者工具路径 -> 输入微信开发者工具的安装路径,如「
D:/微信 web 开发者工具
」 - 切记: 一定要在
HBuilder X
中双击打开你项目中的某一个文件(比如:App.vue
) - 运行到微信小程序:运行 -> 运行到小程序模拟器 -> 微信开发者工具
- 底部会提示编译
- 编译成功,微信小程序自动启动
第一个问题:
在小程序开发者工具里边,把安全设置的服务端口给开启了:
打开 IDE 成功:
效果:
2、运行到浏览器
- 切记: 一定要在
HBuilder X
中双击打开你项目中的某一个文件(比如:App.vue
) -> 不然浏览器是不认识的 - 无需配置,直接运行:运行 -> 运行到浏览器 -> Chrome
- 编译完成,浏览器自动打开,运行成功
一般切换到手机调试模式
在实际项目开发中,微信小程序占据了绝大多数的用户,而 H5 端是我们 web 前端常见的一种形式,所以,在以后的正常开发之中,我们项目多以微信小程序和 H5 端的适配为主
6)使用 VSCode 开发 uniapp
虽说 HBuilder X
开发体验还算不错,但是有时候金窝银窝不如自己的狗窝,当我们习惯了 VSCode
之后,有时候不太愿意换开发工具。
那么怎么使用 VSCode
来开发 uniapp
呢? 其实是有办法的。
- 使用
HBuilder X
运行项目(运行方式,参考上一小节) - 使用
VSCode
打开项目 - 在
VSCode
中安装插件:- uni-helper - 让开发者在 VSCode 中开发
uni-*
的体验尽可能好。 - uni-app-snippets - 支持 uni-app 基本能力的代码片段,包括组件和 API
- uni-app-schemas - 支持 uni-app
pages.json
和manifest.json
简单的格式校验 - uni-ui-snippets - 支持 uni-ui 组件代码片段
- uni-helper - 让开发者在 VSCode 中开发
- 在
VSCode
中修改代码,运行结果自动发生变化
让
HBuilder X
作为中介,我们在 VSCode 中写代码 -> 为了提高自己的开发体验(比如 API 提示等),可以安装插件 -> 安装一个uni-helper
插件,其它三个插件自动安装
💡:uniapp 的 uni_modules 目录需要提交到 Git 吗?
需要提交!
7)创建与配置 tabBar
目前,项目的运行以及开发环境都搞定了,就下来我们要做的就是开发这个项目!
先搞定这个项目的结构 -> 也就是 tabBar 切换 -> uniapp 模仿微信小程序,也就是创建姿势,跟微信小程序一样
1、创建页面
把默认的页面删了,由于安装了插件,VSCode 也可以右键目录名创建页面 -> 但建议还是用 HBuilder 创建页面
- 删除
pages
下的index
文件夹 - 在
pages
文件夹处,右键 -> 选择新建页面 - 确认新建页面的信息
- 点击创建按钮完成新建
- 循环以上顺序,依次完成
hot
、hot-video
、my
三个页面的创建
效果:
pages.json
2、配置 删除
index
路径新建
tabBar
节点复制 资源 文件夹下
tab-icons
文件夹到static
文件夹中 -> 删掉原先存在的图片编写
tabBar
代码如果修改完成之后,依然得到了以下错误,那么可以在
HBuilder X
中重新运行项目到微信开发者工具解决 -> 这是小程序开发者工具的 bug(无法更新已经删除了文件这种情况,似乎有缓存啊) -> 重新运行项目就可以解决这个 bug 了
完整tabBar
代码:
效果:
8)警告与错误处理
1、解决 【sitemap 索引情况提示】的警告问题
微信小程序默认开启了索引功能,但是因为我们没有配置索引策略,导致出现了这么一个警告的问题。具体情况可以参考:https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html
而如果想要解决这个警告也非常简单,因为一般情况项目不需要被微信索引,所以我们只需要 关闭 默认索引功能即可!
双击打开
manifest.json
-> 点击源码视图 -> 下翻找到mp-weixin
配置节点 -> 在settings
下新增"checkSiteMap" : false
Cannot read property 'forceUpdate' of undefined
的错误
2、解决 这个错误的原因非常简单 -> 因为我们没有为项目配置 AppID
的原因,所以只需要完成 AppID
配置即可。 -> 我们说过开发一个微信小程序项目,首先得有微信开发者工具,其次得有AppId
错误处理完成后,就可以进入到我们的页面开发了!
9)热搜页面分析
整个项目分为三大模块,我们首先去开发第一个模块——热搜模块
我们会把「热搜模块」分成四部分去进行开发:
.vue
文件结构与 logo 图片展示
10)认识 .vue
文件结构
1、template
:定义当前页面的结构。相当于wxml
script
:定义当前页面的逻辑。相当于js
style
:定义当前页面的样式。相当于wxss
- 为
style
标签增加scoped
属性:表示当前样式只在当前页面生效
- 为
2、logo 图片展示
注意点:
- 路径以
@
开头 ->@
表示项目的根路径,也就是从项目的根路径开始找文件 ->src="@/static/images/logo.png"
- 可以用全局样式变量,因为我们用了
scss
-> 无须导入这个uni.scss
-> uniapp 帮我们自动引入了,我们可以直接使用这些全局样式变量 - logo 图片 -> 不要拉伸 ->
mode="aspectFit"
代码:
效果:
👇:logo 处理完成,处理搜索框
11) 创建搜索框组件
- 创建自定义组件的方式跟在微信小程序开发者工具里边所创建的姿势是一模一样的
- 组件的结构和页面的结构是一样的 -> 都是
.vue
文件,里边分为三块 - 组件定义好后,可以直接使用,无需注册 -> 微信小程序需要在页面配置里边注册,而 uni-app 则不需要
做法:
创建
components
文件夹右键
components
文件夹 -> 新建组件明确当前
my-search
组件的能力(暂时不需要考虑太多之后的能力)- 具备输入框的样式
- 不可进行输入 -> 本质上是一个按钮,只是看起来像输入框罢了
placeholder
内容可以在父组件定义 -> 不是在组件内写死的
代码实现
- 第一点和第二点的能力 -> 用 CSS 即可实现
- 第三点能力 -> 父向子传值 -> 使用
props
-> 小程序是用properties
代码:创建搜索框组件 · ppambler/imooc-uni-app@651bcb6
目前这个my-search
组件的功能是非常非常简单的,后边会赋予my-search
更多的一些能力,这样它就会变得非常非常的复杂了
比如:
my-tabs
组件
12)tabs 组件 - 创建并分析 - 创建
my-tabs
组件 - 分析
my-tabs
组件的能力 -> 希望创建一个通用的my-tabs
组件,可以满足各个应用中的需求 -> 既然是通用的,内容就不能写死了- 可在父组件中定制
my-tabs
样式 -> 比如下划线颜色 - 可在父组件中设置展示数据 -> 每个
tab
的内容是啥 - 可在父组件中设置默认的激活项(选中项) -> 选中高亮等
- 由此分析,定义出以下代码:
- Demo
- 可在父组件中定制
啥叫通用的组件? -> 这个项目里边能用,其它项目里边导入该组件也能用它 -> 说白了就是「轮子」呗! -> 既然想要把
my-tabs
开发成一个轮子 -> 那么my-tabs
的复杂端将远远超过我们之前所开发的my-search
组件 -> 你可以认为my-tabs
是我们这个项目中第一个比较复杂的自定义组件
至此,我们指定了三个可定制的内容
本小节我们创建了my-tabs
组件 -> 分析了my-tabs
组件中所具备的能力
👇:my-tabs
组件中的内容开发
13)tabs 组件 - 封装网络请求
要使用my-tabs
,就需要把tabs
的数据给展示出来 -> 想要展示tabs
数据,就得调用封装的接口来获取数据 -> 接口文档:热搜 -> 热搜文章类型
发请求在微信小程序里边是wx.request
,那在 uni-app 里边呢? -> uni.request
- 创建
utils
文件夹 - 创建
request.js
,封装请求对象 - 创建
api
文件夹 -> 放置所有的网络请求的相关方法 - 创建
hot.js
文件,封装hot
相关的请求方法:getHotTabs
- 在
hot.vue
里边使用getHotTabs
-> 在loadHotTabs
方法里边发起请求- 在哪里调用
loadHotTabs
方法? -> 在created
里边:组件实例配置完成,但 DOM 未渲染,我们可以在这个钩子里边进行网络请求,配置响应式数据 -> 这跟 Vue 是一样的
- 在哪里调用
至此,一个基本的网络请求代码就已经完成了!
14)tabs 组件 - 进行基本的数据展示
scroll-x
:允许横向滚动 -> 默认值是false
scroll-with-animation
:在设置滚动条位置时使用动画过渡 -> 默认值是false
- 在
hot
中使用my-tabs
组件 -> 父子通信 -> 传递两个参数:tabData
:tabs 数据源defaultIndex
:当前的切换 index
- 在
my-tabs
组件中展示scroll-view
<block v-for="(item, index) in tabData" :key="index"></block>
-> uni-app 遵循 vue 的v-for
指令,小程序是wx:for="🟡🟡arr🟡🟡"
,默认变量名是item
,下标是index
效果:
15)tabs 组件 - 美化样式
这个样式的实现过程是很丑陋的…… -> 我很难理解为啥要嵌套那么多层……
头两个能力已经实现了,并且把数据给展示出来了,那么现在第三个能力「在父组件中选中项」 -> 这该如何实现呢?目前,我们已经给了这个激活项一个对应的props
(defaultIndex
)了 -> 有了这个props
该如何实现激活项呢?
在开始下一小节前,请先自己去实现一遍(唯有自己尝试实现一遍,你才会发现这里边所存在的问题) -> 实现失败有失败的问题,实现成功也有成功的问题 -> 带着问题去看下一小节,唯有这样你才会收获更多!
要实现的效果:
16)tabs 组件 - 设置激活项
自己实现一遍:
- 判断
defaultIndex
是否和index
相等 -> 相等即给item
添加一个active
类 - 写样式 -> 如何才能有下划线?
- 点击某个 tab ,更新
defaultIndex
的值 -> 如何为my-tabs
组件绑定点击事件? -> 子向父传参 - 下划线的滑动效果如何实现呢?
注意点:
- 父组件传递的数据,我们不应该在子组件中进行修改
- uni-app 的点击事件用
@click
,微信小程序则是用bind:tap
- 用来更新
activeIndex
的值 - 并且向外界通知一个
tabClick
事件
- 用来更新
- 数据监听器用
watch
,微信小程序则是用observers
- 监听
defaultIndex
这个数据 -> 必须添加immediate: true
,表示defaultIndex
第一次赋值(默认值或者父传递过来的数据)也要被监听到 -> 为了更新activeIndex
的值而服务 -> 这就是「父组件传递的数据」不应该在子组件中进行修改
- 监听
- 发送事件通知用
$emit
,微信小程序则是用triggerEvent
关于老师的代码实现:active
的切换是在子组件内部通过修改activeIndex
来完成的,而我的是在父组件通过修改defaultIndex
的值来完成的
实现效果:
👇:实现激活项下边的滑块效果
17)tabs 组件 - 定义滑块
- 使用
style
添加内联样式 - 定义了一个
slider
数据,它旗下有个left
属性,用来指定这个滑块距离左侧的距离是多少,默认是0
👇:实现滑块的滚动效果
18)tabs 组件 - 实现滑块的滚动 01
想要实现滑块的滚动:
- 确定滚动的时机 -> 也就是滑块什么时候发生滚动 -> 监听激活项的变化 + tab 的点击事件处理时
- 计算滑块滚动的距离 -> 也就是
this.slider.left
的值
如何计算滑块滚动的距离?
我们要知道tabItem
的宽度,tabItem
的left
,slider 的width
一个标准的数学公式:
left = tabItem.left + (tabItem.width - slider.width) / 2
这个公式似曾相识
19)tabs 组件 - 实现滑块的滚动 - 02
- slider 的
width
很容易确定,因为这是固定的 -> 添加默认配置数据:defaultConfig
- 配置下划线的宽高以及颜色 -> 添加到
view.underLine
的style
- 配置下划线的宽高以及颜色 -> 添加到
- 创建一个内部维护的数据对象
tabList
,相较于父组件传过来的tabData
,每个item
多了一个_slider
属性,该属性有个left
属性,存储的是滑块距离左侧的距离值 - 监听
tabData
的变化,再次强调,设置了immediate:true
,第一次的初始值,也是会执行它的handler
的,所以在updateTabWidth
和tabToIndex
会有一个判断tabList
是否为空数组的语句- 第一次执行
updateTabWidth
没啥反应 -> 因为此时tabData
为空,所以tabList
也为空 - 第二次执行
updateTabWidth
,获取每个tabItem
DOM 元素的信息,计算每个tabItem
距离左侧的距离,也就是给tabList
里边的每个item
添加一个_slider.left
-> 因为父组件传递了tabData
,所以tabData
不为空 -> 默认会有一次计算「滑块」的位置,比如初始的第一次
- 第一次执行
- 切换
tabItem
-> 点击事件触发 -> 更新this.slider.left
的值,好让view.underLine
的transform
根据这个this.slider.left
产生水平位移
一些小技巧:
- 什么时候可以获取 DOM 元素? -> 可以在
handler
里边用setTimeout
->$nextTick()
兼容性不好 - 获取 DOM 元素时:给每个
tabItem
添加了:id="'_tab_' + index"
- 获取 DOM 的固定写法:
const query = uni.createSelectorQuery().in(this);
query.select("#_tab_" + index).boundingClientRect((res) => { // 获取每个元素在这个页面的空间信息 }).exec()
效果:
20)tabs 组件 - scrollView 的点击位移
需求:当【选中项】发生变化时,希望 scrollView
也进行对应的位移 -> 说白了,想选最后一个,不让用自己拖动这个 scroll
视图容器
用代码模拟我们用鼠标滑动的滚动视图容器的效果
关键代码:
:scroll-left="scrollLeft"
this.scrollLeft = this.activeIndex * this.defaultConfig.underLineWidth;
21)tabs 组件 - 增加可配置项
需求:可在父组件中定制 my-tabs
样式
做法:
- 给
data
的defaultConfig
追加几项有关样式的配置 - 监听
props
——config
-> 让内部维护的数据对象defaultConfig
跟父组件传递过来的数据合并一下 -> 说白了,子组件内部默认的样式配置作为兜底值 - 用
style
属性指定每个tabItem
的样式
至此,我们就已经完成了tabs
组件的开发了 -> 整个tabs
组件从创建开始到最后完成,总共经历了 9 小节内容
tabs
组件是我们这个项目中第一个复杂的组件,并且我们希望把这个tabs
组件做成一个轮子,可以满足更多应用的需求 -> 所以我们不光要实现功能就完事儿了,我们还要提供定制化的一些特性(比如样式),这样的话,才能让我们这个组件来满足各个项目的各个场景里边去
22)List 组件 - 分析 List 组件
这是首页的最后一个功能!
分析完成品里边 List 组件的效果,得出我们要做以下几个步骤才能实现它:
- 使用 mock 数据(假数据),构建 List 的基本结构 -> 此时后端那边还没有接口
- 美化 item 样式
- 根据 tab 的切换,获取真实数据
- 渲染真实数据
- 让 List 具备左右切换的能力 -> 通过 swiper 改造 List
- 完成 list 与 tabs 的联动的能力 -> 也就是完成 swiper 和 tabs 的联动效果 -> tabs 切换,list 视图也切换,list 视图左右滑,tabs 也会自动跟着切换
23)List 组件 - 使用 mock 数据,构建 List 的基本结构
组件的根元素 -> 控制同类盒子之间的间隙;盒子容器 -> 控制盒子的外观;内容容器 -> 控制元素
有多个子元素就用一个容器把它们给包裹了
把每个item
项抽离成单独的组件——hot-list-item
使用假数据把组件的基本结构给渲染出来
item
的结构划分:
左侧展示的索引图标,而且每个item
都有,并且都不一样,所以我们可以把它封装成一个组件——hot-ranking
注意,这个图标只是图片,这个图片是包含数字内容的
目前这个结构有了,就是样式长得太丑了:
24)List 组件 - 美化 item 样式
效果:
25)List 组件 - 根据 tab 的切换,获取真实数据
所谓的真实数据指的是后端的接口已经写好了!你可以根据这个接口的定义去获取数据,之前的假数据只是为了完成这个
List
组件的结构以及样式!
- 监听
tab
切换my-tabs
组件 -> 发送了一个tabClick
通知- 父组件接收这个通知 -> 修改激活项 -> 让子组件此刻激活的
index
和父组件的currentIndex
绑定
- 发送数据请求 -> 获取热搜文章列表
hot.js
封装请求接口- 父组件定义一个方法
loadHotListFromTab
-> 该方法的作用是「获取 List 列表数据」 -> 什么时候调用这个方法 -> 在调用loadHotTabs
的时候,因为,我们获取 list 数据时,需要 tab 中对应的 id -> 这是初始化列表的第一次数据请求
- 缓存请求得到的数据 -> 第一次请求,那就把这次请求得来的数据给缓存下来 -> 切换旧的,不会再次发送请求
- 这个 tab 从未获取过列表数据 -> loading
- 这个 tab 已经获取过列表数据 -> 直接渲染
关键点:
- 缓存数据源的定义:
listData: {}
->key
是tabItem
的id
,value
是这个tabItem
所对应的list
数据
注意点:
- 父组件要监听子组件发布过来的事件 -> 微信小程序通过
bind
监听事件,uniapp 遵循 vue 规则,通过@
监听事件 - 加载动画 -> uni-app 提供了一个
uni-load-more
组件 -> 用来专门展示加载动画的组件
效果:
👇:渲染真实数据
26)List 组件 - 渲染真实数据
- 替换掉假的数据源:
v-for="(item, index) in 50"
->v-for="(item, index) in listData[currentIndex]"
- 把每个
list
数据传给 List 组件 -> 给 List 组件定义两个props
:data
(循环列表的item
数据)、ranking
(排名次序数据) - 父组件
hot
传递数据给了子组件list
-> 子组件得把数据给显示出来 -> 根据接口文档来确定有哪些数据需要被展示:比如title
、nickname
- 样式美化 -> 简介最多展示两行 -> 这个样式经常被用到 -> 抽离出一个
styles
文件夹,在这个文件夹下边创建一个global.scss
-> 定义公共样式的地方 -> 展示两行的样式是固定的 CSS 写法 -> 需要做兼容处理 -> 在main.js
引入公共样式 -> 谁要添加这个类?- 标题
- 简介
- 处理排名,也就是给
hot-ranking
组件添加props
- 根据不同的排名,显示不同的背景图片,图片分为:1、2、3、其它,这四种
- 使用计算属性 -> 根据传递过来的排名数据,来决定渲染的是哪张背景图 -> JS 加载图片用
require
- 使用计算属性 -> 根据传递过来的排名数据,来决定渲染的是哪张背景图 -> JS 加载图片用
- 处理排名的文本颜色 -> 用
:class
- 前三名是白色
- 非前三名是黑色
- 根据不同的排名,显示不同的背景图片,图片分为:1、2、3、其它,这四种
效果:
前四步已经完成:
- 使用 mock 数据(假数据),构建 List 的基本结构 -> 此时后端那边还没有接口
- 美化 item 样式
- 根据 tab 的切换,获取真实数据
- 渲染真实数据
接下来实现:
- 让 list 拥有左右切换的能力
- 让 list 和 tabs 有联动效果
💡:关于文本的截断
27)List 组件 - 通过 swiper 改造 List
想要让 list
具备【横向翻页】的效果,那么可以使用 swiper
对其进行改造!
- 定义一个
swiper
组件 -> 具备滚动能力:- 每一个
swiper-item
下边都是一个遍历完成的一个个hot-list-item
-> 循环次数由listData
决定 -> 此时用的是tabIndex
属性(tabData
的0~6
),而不是之前的currentIndex
属性,因为swiper
滑动的时候,也是需要变化的 -> 需要给swiper
组件指定current
属性(值是currentIndex
,也就是我们点击某个tab
可以切换列表数据),它决定当前展示哪个swiperItem
,不然这个tabIndex
永远为0
swiper-item
的数量取决于my-tabs
里边的tabItem
数量 ->tabData
决定 ->v-for
处理一下
- 每一个
current
属性:默认值是0
,表示当前所在滑块的index
效果:
swiper-item
默认展示第一个 list
的数据,其它 6 个 swiper-item
,你在滑动的时候,你会发现这是空的:
当你第一次点击其它tab
时,会加载这个tab
对应的list
数据,这也就意味着,这个「空」就被填上了这个list
数据!
要把这些「空」都填完,你得把把其余的没有点过的tab
都给点一遍……这样左右滑的时候都会看到有数据存在了! -> 为啥这样点就会有数据? -> 因为我们点击tab
,就是在请求这个tab
所对应的list
数据啊,而这写数据会被缓存到listData
中
目前存在的问题:
list
列表的高度展示错误 -> 也就是列表数据没有展示全- 切换 tab 时,list 的渲染出现卡顿问题
应该是 list 的数据渲染完再滑?还是滑完后再渲染? -> 等大风小了再走,还是不管大风直接走呢?
28)List 组件 - 解决列表高度展示错误的问题
- 原因:没有给
swiper
指定高度 - 解决方案:指定高度即可
可这个高度该给多少呢?1000px
?2000px
?
你要知道每个tabs
所对应的list
数据的高度是不一样的! -> 这个接口给出来的每个tab
都是20
条数据,不过我们假设都不一样!
最终的解决方案:计算出每个 listItem
的高度,然后叠加到一起,就可以得到 swiper
的高度了!
这个方案的实现逻辑:
- 定义数据
currentSwiperHeight
:当前swiper
的高度swiperHeightData
:缓存高度的计算结果:以index
为key
,对应的swiper
的高度 为val
-> 每次计算太耗性能了,你得把计算结果给缓存下来
- 定义一个方法:
getCurrentSwiperHeight
-> 用来帮我们计算当前swiper
的高度- 拿到所有的
item
-> 这是一个异步操作(节点信息 - uni-app 官网)- 获取节点信息有固定的代码写法
- 拿到所有
item
的高度 -> 来自一个个的节点信息 - 把所有的高度累加
- 拿到所有的
- 什么时候调用这个方法? -> 当渲染完成数据之后,再去计算高度
this.$nextTick
存在一定的兼容性问题,所以更加推荐使用传统的方式setTimeout
- 在
getHotListFromTab
里边的setTimeout
的回调里调用- 拿到
getCurrentSwiperHeight
的结果值,也就是说swiper
的高度 - 把这个高度放入
swiperHeightData
缓存中 -> 当前选中的tabItem
索引值作为key
,而对应的value
就是这个高度值
- 拿到
- 使用这个缓存高度:
currentSwiperHeight
注意:
效果:
tab
时的 list
的卡顿问题
29)List 组件 - 解决 切换 💡:为什么会出现这个卡顿问题?
原因:swiper 动画未完成时,就获取数据,渲染 DOM
具体来说就是:点击 tab 切换 -> 修改currentIndex
-> currentIndex
绑定到了swiper
的current
属性 -> 也就说 tab 一切换,swiper
就会发生对应的切换/滑动效果 -> 在发生切换效果时,swiper
会执行一个动画效果,但是我们的切换动画和我们的数据获取渲染列表是同步进行的,也就说 swiper 动画未完成时,就获取数据,渲染 DOM -> 所以这就导致了卡顿问题?
💡:如何解决呢?
有了原因之后,这解决方案就非常简单了!
解决方案:swiper 动画完成之后,再去获取数据,渲染 DOM
💡:如何监听 swiper 的动画完成?
- 对
swiper
绑定一个animationfinish
事件 -> 表示我们当前动画完成之后的回调 ->onSwiperEnd
onSwiperEnd
这个方法的逻辑:- 在这个方法里边请求列表数据,原先在
onTabClick
里边的获取列表数据的方式就不需要了 - 判断是否有缓存再去获取列表数据 ->
loadHostListFromTab
里边的判断缓存不需要了,直接获取数据即可 - 没有缓存:获取数据,直接
return
->e.detail.current
也可以拿到当前切换的currentIndex
- 有缓存:未
return
,则证明存在数据缓存,存在数据缓存,则同时存在height
的缓存数据 -> 设置currentSwiperHeight
这个高度值
- 在这个方法里边请求列表数据,原先在
效果:
至此:
- 问题 1:list 列表的高度展示错误
- 问题 2:切换 tab 时,list 的卡顿问题
这两个问题就已经全部搞定了
这也就意味着让 list 具备左右切换的能力也已经全部搞定了
六个步骤已经完成了其中的五个步骤 -> 我们需要完成第六步:「list 与 tabs 联动的能力」,也就是完成它们俩之间的联动效果
swiper
和 tabs
联动
30)List 组件 - 所谓的联动能力:
tabs
切换时,swiper
联动切换 -> 这一步已经完成了,点击某个tab
,swiper
会自动滑动到相应的list
swiper
切换时,tabs
联动切换 -> 这一步未完成
实现逻辑:
- 监听
swiper
切换的事件 ->@change
-> 通过e.detail.current
获取切换完成后此时的swiper
下标 ->onSwiperChange
onSwiperChange
:更新currentIndex
的值
效果:
目前的问题:激活的tabItem
,其底部没有那个滑块跟随着
31)List 组件 - tabs 中滑块跟随滚动
为啥不跟着滚动? -> 分析一下这个原因
我们知道滑块的滚动依赖my-tabs
组件里边的tabToIndex
这个方法来进行计算的
当activeIndex
发生切换的时候 -> 我们需要重新调用这个tabToIndex
实现逻辑:
hot.vue
->currentIndex
变化,defaultIndex
这个传给my-tabs
组件的props
更新了my-tabs
->watch
->defaultIndex
->handler
->tabToIndex()
(注意,第一次tabList
为空时,直接返回就好了)
效果:
至此,我们的第六步代码就已经完成了,整个list
组件的功能就已经全部搞定了
不过,目前还有一些小问题,比如:
- 滚动
list
到底部,希望有吸顶的效果 - 热度希望展示成
4k
这样的效果,而不是4000
我发现一个问题,
list
的数据都缓存了,swiper
的animationfinish
事件还是会被触发 -> 我突然明白这个动画指的是swiperItem
左右切换的动画,而不是loading
动画
tabs
的吸顶效果
32)完成 目前,hot
这首页的主要功能就已经完成了,剩下的都是一些边边角角的修复
💡:功能补充:让 tabs
具备吸顶的效果
- 给
my-tabs
标签包装一层view.tab-sticky
- 给
tab-sticky
这个类添加样式
💡:功能补充:控制列表滚动位置
这是啥功能?
33)控制列表滚动位置
- 当用户滚动页面之后
- 切换 tabs 时,让页面的滚动位置返回为 tabs 吸顶的位置
这个交互我不是很认可啊,我对比华为的应用市场 App 软件,和极客时间的 App,都发现每个
tabItem
所对应的list
列表的滚动位置是会保存的,也就是第一个tabItem
,你在1000px
高度处,你切换到第二个tabItem
,此时你滚动到2000px
高度处,你再切回来,这第一个tabItem
还是在1000px
高度处 -> 不过,那个打卡小程序,也没有做这样的效果!
实现逻辑:
- 定义一个
currentPageScrollTop
数据:表示当前的滚动距离 - 监听页面的滚动:
onPageScroll
-> 这是关于 uni-app 的页面生命周期130px
这个位置正好是触发吸顶的位置- 通过
uni.pageScrollTo
控制列表的滚动位置
👇:搞定这两个补充功能后,接下来处理热度显示的问题
34)List 组件 - 处理热度的显示
用过滤器
实现逻辑:
- 创建一个
filters
目录 -> 包含所有的过滤器- 创建一个
index.js
-> 暴露一个hotNumber
方法 -> 用来把大于1000
的字符串数字转化成以k
结尾的字符串数字
- 创建一个
- 注册过滤器的最佳实践 ->
main.js
-> 会把所有过滤器都给注册了 - 使用过滤器 ->
hot-list-item.vue
效果:
至此,我们这个首页的功能开发就基本完成了!
35)总结
用了 36 节,4 个多小时!
这个首页内容是我们接触 uni-app 后所完成的第一个比较复杂的页面 -> 这个页面的复杂度只要体现在tabs
和list
这两个位置
首页内容完成:
- 对
uniapp
进行了基础的了解 -> uni-app 结合了微信小程序语法和 Vue 语法的一个结合体 - 创建
imooc-blog
的项目 - 完成了
tabbar
的搭建 -> 小程序里边的搭建规则 - 了解了
.vue
的单文件组件 -> 由哪几部分组成? -> 在这种文件里边,我们实现了很多的页面以及对应的组件 - 分析了首页的模块组成
- 封装了
request
API
请求模块 -> 使用 Promise 自己封装,而不是网络上现成的库 - 实现了复杂组件
tabs
和list
组件 -> 在实现的过程中,抛出了很多问题,通过思考这些问题,然后解决这些问题,这会让你收获很多! - 完成了
tabs
和 基于swiper
的列表联动
👇:完成新的页面:慕课搜索页面