我的Vue之旅 06 超详细、仿 itch.io 主页设计(Mobile)
我的主题 HapiGames 是仿 itch.io 的 indie game hosting marketplace。
效果图
代码仓库
alicepolice/Vue at 06 (github.com)
风格指南
当你掌握一门语言的时候,在写项目之前不妨先看看风格指南吧,前人早为你铺好了路。下面是我自己编写项目代码时没有规范到位的几个点。
Prop 定义
Prop 定义应该尽量详细,至少需要指定其类型。Props | Vue.js (vuejs.org)
Vue的选项式API为我们提供了Prop校验,你可以向 props
选项提供一个带有 props 校验选项的对象,当 prop 的校验失败后,Vue 会抛出一个控制台警告 (开发模式)。(如果用ts的话更好)
注意 prop 的校验是在组件实例被创建之前,所以实例的属性 (比如 data
、computed
等) 将在 default
或 validator
函数中不可用。
v-for和v-if同时在一个标签时,将v-if提取到计算属性
因为 v-for 优先级比 v-if 高,所以每次渲染时必定会遍历数组所有元素。避免 v-if 和 v-for 用在一起
将v-if提取到计算属性后的好处
- 过滤后的列表只会在对应数组发生相关变化时才被重新运算,过滤更高效。
- 使用
v-for="item in afterComputed"
之后,在渲染的时候遍历元素少了,渲染更高效。 - 解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。
紧密耦合的组件名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。紧密耦合的组件名
如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。
不建议为了紧密耦合搞目录区分,因为会出现文件名名字相同、IDE侧边栏浏览组件花费时间多的问题。
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
自闭合组件
在单文件组件、字符串模板和 JSX 中没有内容的组件应该是自闭合的——但在 DOM 模板里永远不要这样做。 自闭合组件
Prop 名大小写
在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。 Prop 名大小写
props: {
greetingText: String
}
简单的计算属性
应该把复杂计算属性分割为尽可能多的更简单的 property。 简单的计算属性
好处是易于测试、易于阅读、更好的“拥抱变化”。
单文件组件的顶级元素的顺序
单文件组件应该总是让
隐性的父子组件通信
应该优先通过 prop 和事件进行父子组件之间的通信,而不是
this.$parent
或变更 prop。 隐性的父子组件通信
数据流应该是单向的,不要反向修改 props。
方便调试
为了方便调试,我们在 index.css 下新增一个样式组合,通过添加test类样式类看到块元素的边框。
.test{
@apply border border-gray-900
}
目录结构
├───assets
│ ├───avater
│ │ 用户头像
│ ├───blog
│ │ 博文封面图
│ ├───diffuse
│ │ 模糊背景
│ ├───game
│ │ 游戏封面图
│ ├───logo
│ │ 网站logo
│ ├───slideshow
│ │ 轮播图样图
│ └───svg
│ 很多矢量图
├───components
│ ├───common
│ │ BottomBar.vue
│ │ CommentArea.vue
│ │ SideBar.vue
│ │ SideBarHref.vue
│ │ SlideShow.vue
│ │ TopBar.vue
│ │
│ └───HomeView
│ GameBlog.vue
│ GameInfo.vue
│ GameList.vue
│ HomeFAQ.vue
│ HomeFooter.vue
│ PlatformNavigation.vue
│ TopNavigation.vue
│
├───router
│ index.ts
└───views
AboutView.vue
CommentTestView.vue
HomeLoginView.vue
HomeView.vue
LoginView.vue
RegisterView.vue
网站顶部组件 TopBar.vue
在 src/components/common 下新建 TopBar.vue,并移入之前写的 BottomBar.vue。
先从网站顶部开始,该组件在每个页面都会显示,并在滚动过程中固定定位。
编写代码,实现顶部栏。
Log in
侧边导航栏组件 SideBar.vue
注释底部导航栏
我的Vue之旅、05 导航栏、登录、注册 (Mobile) - 小能日记 - 博客园 (cnblogs.com)
在前一期内容中,我们创建的导航栏是底部导航栏。
现在我们推倒重来,实现一下侧边导航栏。
侧边栏导航也叫抽屉式导航是隐藏在界面侧边的位置,一般是通过点击界面左上角的icon弹出,主要承载的内容是除了核心功能意外的主要功能。侧边栏还分全侧边和半侧边。
当我们在App.vue中注释掉现有的底部导航栏,此时会出现错误item.routerName => item对象的类型为 "unknown"。
[TS]使用高级类型PropType注释props类型
IDE报错并不影响当前Vue实例,因为BottomBar组件并未挂载。但为了去除报错,使用高级类型注释来修改BottomBar.vue。
运行时 props
选项仅支持使用构造函数来作为一个 prop 的类型,没有办法指定多层级对象或函数签名之类的复杂类型。在这里可以使用 PropType 注释复杂的props类型,报错解决。
SideBarHref.vue
在 src/components/common 下新建 SideBarHref.vue
侧边导航栏有相似之处,不妨将这一块提取成独立的组件,然后复用三次。
添加样式 hover:text-rose-500 hover:underline
,在移动端按下时会改变颜色。
SideBar.vue
遮蔽层
在 src/components/common 下新建 SideBar.vue 以下代码片段均为分段表示,不是完整代码。
先写model层(遮蔽层),一般指侧边栏滚出后背景变黑的部分。
我们使用自定义类名实现过渡动画。类名也是TailWind.css的类样式,给定200毫秒时间,过渡透明度状态。
div嵌套了两层,把opacity-50写到里面的div层能解决opacity-50在外面div层的时候出现背景全黑问题。
fixed 用于固定遮蔽层。z-30用于设置优先级,先显示在前面。v-show由App.vue传入,顶部组件通知App.vue事件对应的方法修改,进而引发当前transition的过渡。
html - Vue Transition with Tailwind - Stack Overflow
侧边栏
侧边栏的动画效果跟遮蔽层一个原理,只不过修改成为了移动而不是改变透明度。
overflow-auto 可以让侧边栏在内容溢出时具备滚动条。
搜索框
focus:outline-none focus:ring focus:border-blue-200
当当前光标指向该input标签时更改样式,让四角发光变蓝。该段代码也可以提取成基本组件。
SideBarHref
三次复用之前定义的SideBarHref组件,并传入了props
download app

让图标和下载超链接完全数据化,增加网页动态变化能力。
数据驱动
除非结构要改,现在完全可以靠data里的对象数据驱动当前侧边栏的所有内容。
data() {
return {
search: "",
popularTags: {
title: "POPULAR TAGS",
items: [
{ text: "Horror games", href: "" },
{ text: "Multiplayer", href: "" },
{ text: "Visual novels", href: "" },
{ text: "HTML5 games", href: "" },
{ text: "Simulation", href: "" },
{ text: "macOS games", href: "" },
{ text: "Roguelike", href: "" },
{ text: "Linux games", href: "" },
{ text: "Browse all tags", href: "" },
],
},
browse: {
title: "BROWSE",
....
联动顶部组件与侧边导航栏组件
我们的想法是按下顶部组件左边的 list icon,弹出导航栏,再按一次关闭导航栏。

很容易想到父子通信的解决方案,这也是Vue单向数据流的最佳实现。

[TS]defineComponent 作用
App.vue 里的 export default defineComponent({
是什么?
搭配 TypeScript 使用 Vue | Vue.js (vuejs.org)
defineComponent 是TypeScript独有的,可以根据选项式API的props、data自动推导各个字段的类型,当在生命周期函数、Methods函数、模板表达式中使用这些字段时可以进行类型检查。(不显式引入编译器默认自动引入)
移动端主页
HomeView.vue
我们将一个主页拆分为各个组件,并完全依托数据驱动,图片仅用来本地测试。
HomeFAQ.vue

HapiGames is a simple way to find and share indie games online for
free.
TopNavigation

overflow-x-auto flex flex布局,并在溢出时开启横轴滚动条
whitespace-nowrap 类可以防止换行,让所有元素保持在一行上。
html - Div with horizontal scrolling only - Stack Overflow
GameInfo.vue

嵌入YOUTUBE视频可参考 youtubeembedcode.com
用于生成两张轮播图,每四秒切换一次,具体方法如下。注意 currentImg: function (): string[] {
可以给计算属性添加类型检查。
methods: {
startSlide: function (): void {
this.timer = setInterval(this.next, 4000);
},
next: function (): void {
this.currentIndex += 1;
},
},
computed: {
currentImg: function (): string[] {
let index = Math.abs(this.currentIndex) % this.gameInfo.images.length;
let index2 = (index + 1) % this.gameInfo.images.length;
return [this.gameInfo.images[index], this.gameInfo.images[index2]];
},
},
考虑 img 标签的 :src 只能接收 string ,我们假设所有 require 方法获取的图片均为 string 类型。定义prop类型
import { PropType } from "vue";
interface GameInfo {
youtube: string;
title: string;
desc: string;
price: number;
platforms: string[];
images: string[];
}
完整代码如下
{{ gameInfo.title }}
{{ gameInfo.desc }}.
${{ gameInfo.price }}
Get the game
GameBlog.vue

From the blog
{{ value.title }}
{{ value.text }}
TopNavigation.vue

Platform & Sale
GameList.vue

规定了比较复杂的传入prop类型,考虑到tags可能为空,在原来的模板外层div做v-if判断,否则会ts报错value.tags可能为undefined。
interface Game {
title: string;
text: string;
img: string;
price: number;
web?: boolean;
tags?: string[];
}
interface GameList {
title: string;
button: {
title: string;
href: string;
};
games: Game[];
}
完整代码
{{ gameList.title }}
{{ gameList.button.title }}
HomeFooter.vue
Don't see anything you like?
View all Games
View something random
几个问题
这里列举我在开发过程遇到的一些问题,也许能帮助到你。
ERROR Error: The project seems to require yarn but it's not installed.
明明 yarn serve 成功了,并显示如下内容,但连接网页还是转圈圈。尝试重启电脑后重新 yarn serve
App running at:
- Local: http://localhost:8080/
得到如下报错
ERROR Error: The project seems to require yarn but it's not installed.
解决方法:删除当前目录下的 yarn.lock 文件,命令行输入 npm install -g yarn
Type assertion expressions can only be used in TypeScript files.Vetur(8016)
解决方法:修改
网站资源
Bootstrap Icons · Official open source SVG icon library for Bootstrap (getbootstrap.com)
Working with props declaration in Vue 3 + Typescript - DEV Community 👩💻👨💻
单组件的编写 | Vue3 入门指南与实战案例 (chengpeiquan.com)
vue3中的组件定义中defineComponent作用? - #2 由 cuidong - 中文 - Vue Forum (vuejs.org)
使用 CSS 实现垂直居中的8种方法_wincheshe的博客-CSDN博客_css垂直居中
前端 - vue如何动态加载本地图片_个人文章 - SegmentFault 思否