项目中最好使用通用样式,可以创建 src/styles
目录存放,这里推荐一些分类:
styles
├── antd # 组件库样式覆盖,命名自取,这里以 ant design 为例
├── color.less # 颜色
├── index.less # 入口
├── global.less # 公共类
├── transition.less # 动画相关
└── variable.less # 变量
复制代码
6.1 预设基础样式
相信用过 normalize[92] 的同学不在少数,它可以重置 css 样式,使各浏览器效果保持一致。后面的章节会提到 tailwind.css,它内置了预设样式重置的功能,与 normalize 还是有一定的区别,有兴趣的同学可以了解一下[93]。
6.2 CSS 预处理器
虽然 vite 原生支持 less/sass/scss/stylus,但是你必须手动安装他们的预处理器依赖,例如:
npm install -D less
复制代码
如何选择预处理器?
推荐使用你是所使用的组件库的样式语言,因为 css 预处理器学会一种后,入手其他几乎没有学习成本。
6.3 开启 scoped
没有加 scoped 属性,会编译成全局样式,造成全局污染。
复制代码
6.4 深度选择器
有时我们可能想明确地制定一个针对子组件的规则。
如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符。有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/ 或 ::v-deep 操作符取而代之——两者都是 >>> 的别名,同样可以正常工作。
7.布局
页面整体布局是一个产品最外层的框架结构,往往会包含导航、页脚、侧边栏等。在页面之中,也有很多区块的布局结构。在真实项目中,页面布局通常统领整个应用的界面,有非常重要的作用,所以单独拆分出来也是非常有必要的。
在脚手架中,所有的通用布局组件都应该放在 src/layouts 中,这种封装比较简单,这里就不贴代码了,大家按照自己实际情况自行发挥,在此仅提供一下封装思路。
7.1 常规的布局
BasicLayout
基础页面布局,包含了头部导航,侧边栏等。
BlankLayout
空白的布局。
7.2 特殊的布局
RouteLayout
如果你的项目在路由切换中需要对某些二级页面进行缓存,那么推荐你创建一个 RouteLayout,通过路由 meta
中的配置,返回 router-view
或者使用 keep-alive
包裹的 router-view
。
UserLayout
用于用户登录注册等页面抽离出来。
PageLayout
基础布局,包含了面包屑等信息,内含 slot。
8.集成 Tailwind.css
Tailwind.css[94] 在我第一次看到它的时候,内心是比较反感的,但实际上手之后又觉得真香。从 vue2 项目中,我已经引入了 tailwind,整体的开发结果就是,基本很少再使用 <style>
标签去转本定义一些 class 和样式,毕竟起名字这种事,一个是涉及到规范,一个是涉及到英语。如果你选择 tailwind,CSS 预处理器的作用就会显得微乎其微,因为你无需再自定定义各种变量和 mixins。
总体来说,学习成本并不高,花上两个小时足够上手,记住不用死记硬背那些类名。
8.1 效率提升
很多人总是说样式要与 HTML 分离,现在为什么又要提倡 tailwind 这种与 HTML 紧密结合的工具?这是因为现在使用 vue 这类框架已经高度组件化,样式分离是为了方便复用和维护,但在组件化面前样式分离只能是降低开发效率。
下面介绍一下 tailwind 提供了哪些提升效率的功能:
-
提供了大量的功能类,极大的提高了可维护性。
-
响应式设计,各种设备一把梭。
-
悬停、焦点和其它状态。
-
深色模式。
-
支持配置,例如颜色方面很难做到跟你的设计师统一。
-
不用为起名字而纠结???
8.2 JIT 模式
如果你的环境支持 postcss8( vue/cli 构建的 vue2 项目是 postcss7 ),那么 JIT 模式直接带你起飞。
-
超快的构建速度。
-
支持变体,你甚至可以这么写
sm:hover:active:disabled:opacity-75
。 -
支持任意样式,例如
md:top-[-113px]
。 -
开发和生产环境结果是一致的,(我在 vue2 项目中就遇到过组件库构建结果不一致的问题)。
如果你使用 vscode 那你一定要安装 Tailwind CSS IntelliSense[95] 插件,它可以自动补全类名,显著降低学习成本。
8.3 关于打包体积
使用默认配置,未压缩是 3739.4kB ,Gzip压缩 是 293.9kB,Brotli压缩 是 73.2kB。这似乎看起来很大,这是因为 tailwind 提供了成千上万的功能类,其中绝大部分你不会使用到。
当构建生产时,你应该使用 purge 选项来 tree-shake 优化未使用的样式,并优化您的最终构建大小当使用 Tailwind 删除未使用的样式时,很难最终得到超过 10kb 的压缩 CSS。
还有一点,Atom CSS
极大的提升了样式的复用程度,从而直接降低了构建体积。
9.vuex 替代方案 pinia
由于 vuex 4
对 typescript 的支持让人感到难过,所以状态管理弃用了 vuex 而采取了 pinia[96]。
忘记在哪看到,尤大好像说 pinia[97] 可能会代替 vuex,所以请放心使用。
9.1 为什么采用 Pinia ?
-
Pinia 的 API 设计非常接近
Vuex 5
的提案[98]。(作者是 Vue 核心团队成员) -
无需像
Vuex 4
自定义复杂的类型来支持 typescript,天生具备完美的类型推断。 -
模块化设计,你引入的每一个 store 在打包时都可以自动拆分他们。
-
无嵌套结构,但你可以在任意的 store 之间交叉组合使用。
-
Pinia 与 Vue devtools 挂钩,不会影响 Vue 3 开发体验。
下面简单的介绍一下如何使用 Pinia,并对比 vuex 有哪些区别与注意事项,具体请参考官方文档[99]。
9.2 创建 Store
Pinia 已经内置在脚手架中,并且与 vue 已经做好了关联,你可以在任何位置创建一个 store:
import { defineStore } from ‘pinia’
export const useUserStore = defineStore({
id: ‘user’,
state: () =>({}),
getters: {},
actions: {}
})
复制代码
这与 Vuex 有很大不同,它是标准的 Javascript 模块导出,这种方式也让开发人员和你的 IDE 更加清楚 store 来自哪里。
Pinia 与 Vuex 的区别:
-
id 是必要的,它将所使用 store 连接到 devtools。
-
创建方式:
new Vuex.Store(...)
(vuex3),createStore(...)
(vuex4)。 -
对比于 vuex3 ,state 现在是一个函数返回对象。
-
没有 mutations,不用担心,state 的变化依然记录在 devtools 中。
9.3 State
创建好 store 之后,可以在 state 中创建一些属性了:
state: () => ({ name: ‘codexu’, age: 18 })
复制代码
将 store 中的 state 属性设置为一个函数,该函数返回一个包含不同状态值的对象,这与我们在组件中定义数据的方式非常相似。
在模板中使用 store:
现在我们想从 store 中获取到 name 的状态,我们只需要使用以下的方式即可:
{{userStore.name}}
const userStore = useUserStore()
return { userStore }
复制代码
注意这里并不需要 userStore.state.name
。
虽然上面的写法很舒适,但是你一定不要用解构的方式去提取它内部的值,这样做的话,会失去它的响应式:
const { name, email } = useUserStore()
复制代码
9.4 Getters
Pinia 中的 getter 与 Vuex 中的 getter 、组件中的计算属性具有相同的功能,传统的函数声明使用 this 代替了 state 的传参方法,但箭头函数还是要使用函数的第一个参数来获取 state ,因为箭头函数处理 this 的作用范围:
getters: {
nameLength() {
return this.name.length
},
nameLength: state => state.name.length,
nameLength: ()=> this.name.length ❌
}
复制代码
9.5 Actions
这里与 Vuex 有极大的不同,Pinia 仅提供了一种方法来定义如何更改状态的规则,放弃 mutations 只依靠 Actions,这是一项重大的改变。
Pinia 让 Actions 更加的灵活:
-
可以通过组件或其他 action 调用
-
可以从其他 store 的 action 中调用
-
直接在商店实例上调用
-
支持同步或异步
-
有任意数量的参数
-
可以包含有关如何更改状态的逻辑(也就是 vuex 的 mutations 的作用)
-
可以
$patch
方法直接更改状态属性
actions: {
async insertPost(data){
await doAjaxRequest(data);
this.name = ‘…’;
}
}
复制代码
9.6 Devtools
脚手架已内置下面的代码,这将添加 devtools 支持:
import { createPinia, PiniaPlugin } from ‘pinia’
Vue.use(PiniaPlugin)
const pinia = createPinia()
复制代码
时间旅行功能貌似已经可以使用了,这块后续会关注。
10.基于 mitt 处理组件间事件联动
如果你曾经是 Vue2.x 的开发者,那么请阅读下面引用官方文档[100]的一段话:
我们从实例中完全移除了
$on
、$off
和$once
方法。$emit
仍然包含于现有的 API 中,因为它用于触发由父组件声明式添加的事件处理函数。
在 Vue 3 中,已经不可能使用这些 API 从组件内部监听组件自己发出的事件了,该用例暂没有迁移的方法。但是该 eventHub 模式可以被替换为实现了事件触发器接口的外部库,例如
mitt
或tiny-emitter
。
10.1 为什么选择 mitt ?
-
足够小,仅有 200bytes。
-
支持全部事件的监听和批量移除。
-
无依赖,不论是什么框架都可以直接使用。
10.2 严重警告
我们已经无法在项目中使用 eventBus,仅推荐你在特殊场合下使用 mitt,它并不是开发的常态,你一定要确保知道自己在做什么?否则你的项目将难以维护!!!
10.3 如何使用 mitt ?
在使用 mitt 前建议请阅读官方文档[101]:
脚手架默认提供一个可以直接使用的对象:
import emitter from ‘@/libs/emitter’;
复制代码
当然你也可以引入已经安装好的 mitt:
import mitt from ‘mitt’
const emitter = mitt()
复制代码
mitt 提供了非常简单的 API,下面代码是官方演示:
// listen to an event
emitter.on(‘foo’, e => console.log(‘foo’, e) )
// listen to all events
emitter.on(‘*’, (type, e) => console.log(type, e) )
// fire an event
emitter.emit(‘foo’, { a: ‘b’ })
// clearing all events
emitter.all.clear()
// working with handler references:
function onFoo() {}
emitter.on(‘foo’, onFoo) // listen
emitter.off(‘foo’, onFoo) // unlisten
复制代码
11.异步请求
绝大多数项目想必逃脱不了接口的对接,如果你的项目存在大量的接口,我建议做到以下几点:
-
封装请求。
-
统一的 API 接口管理。
-
Mock 数据功能(根据需求斟酌使用)。
上述的主要目的就是在帮助我们简化代码和利于后期的更新维护。
11.1 基于 axios 的封装
相信开发过 vue2 项目的同学已经对 axios 非常熟悉的,在这里提供一些封装的思路:
-
通过
import.meta.env.VITE_APP_BASE_URL
获取环境变量,配置baseURL
,如果接口存在多个不同域名,可以通过 js 变量控制。 -
设置
timeout
请求超时、断网情况处理。 -
设置请求头,携带
token
。 -
异常拦截处理,后端通过你携带的
token
判断你是否过期,如果返回401
你可能需要跳转到登录页面,并提示需要重新登录。 -
响应拦截,通常后端返回 code、data、msg,如果是请求正常,我们可以直接返回 data 数据,如果是异常的 code,我们也可以在这里直接弹出报错提示。
-
无感刷新 token,如果你的 token 过期,可以通过后端返回的 refreshToken 调用刷新接口,获取新的 token。当然这里涉及到很多细节,例如终端请求、重新发送请求、重新请求列队。
-
中断请求,例如页面切换时,我们要中断正在发生的请求。
相关代码(仅供参考)[102]
11.2 为 axios 增加泛型的支持
到目前为止,axios 请求返回的类型是 any,这时我们对请求后的数据进行操作时,没有享受到 ts 带来的类型提示,这显然不符合我们的预期。
这时我们要做的就是重新声明 axios 模块:新建一个 shims.d.ts,然后在调用时加上泛型。
import { AxiosRequestConfig } from ‘axios’;
declare module ‘axios’ {
export interface AxiosInstance {
<T = any>(config: AxiosRequestConfig): Promise;
request<T = any>(config: AxiosRequestConfig): Promise;
get<T = any>(url: string, config?: AxiosRequestConfig): Promise;
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise;
head<T = any>(url: string, config?: AxiosRequestConfig): Promise;
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise;
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise;
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise;
}
}
复制代码
做好这一步后,你就必须在创建接口时,声明请求相应数据的类型。
11.3 封装更方便的 useRequest
设想一下,编写请求代码时,我们通常会定义这么几个变量:
-
data: 储存请求数据
-
loading: 请求加载状态
尤其是 loading,我们需要在请求前设置为 true,请求结束后设置为 false。
上面的封装方式,是对基础的功能封装,因为我们在使用 vue3,所以可以进行再一次的封装成为 hook,我们使用起来会更加方便。
例如下面这个样子:
使用 useRequest 定义一个接口:
export default getUserInfo(id) {
return useRequest({
method: ‘get’,
url: ‘/api/user’,
params: { id }
})
}
复制代码
使用此接口:
const { data, loading } = getUserInfo();
复制代码
注意这里的 data 是响应式的。
这是我想到的一种思路,目前还没有做很好的封装,相关代码仅供参考[103],你也可以借鉴一些成熟方案,比如 vueuse 中的 useFetch[104],但是他是基于 Fetch API 设计的,并不符合我的预期要求,有更好的方案请大家在下面留言。
11.4 统一的 API 接口管理
自从前端和后端分家之后,前后端接口对接就成为了常态,而对接接口的过程就离不开接口文档,比较主流就是 Swagger,但是如何在前端项目中更好的去管理跟后端对接的接口呢?
在 src 目录中 创建 api 目录,内部目录应按照后端制定的模块创建。
每个模块中创建多个 ts 文件,一个接口应对应一个 ts 文件,其中包含了以下内容:
-
请求参数的类型声明。
-
响应数据的类型声明。
-
返回定义好的请求函数(url、method、params、data 等)。
统一去定义和管理 API 接口,只要后端规范的命名和你认真的写好类型声明,对前端来说 typescript 就是最好的接口文档。
11.5 mock
vite 使用 mock 数据非常简单,你可以使用 vite-plugin-mock[105] 插件,如果你了解 mockjs,你可以快速上手。
12.路由
路由和菜单是组织起一个应用的关键骨架。
12.1 创建路由三部曲
通常一个项目需要做到这几步:
-
使用 createRouter 创建路由,这时候根据需求选择 Hash 路由或者 History 路由。
-
根据业务需求配置路由,注意这里很可能就用到前文提到过的布局组件。
-
如果有权限相关的业务,你需要创建 permission.ts 在路由钩子触发时做一些事情。
如果你的页面比较多,建议你创建 routes 目录,分模块声明路由。
参考代码[106]
12.2 使用 meta 丰富你的路由
vue-router4.x 支持 typescript,配置路由的类型是 RouteRecordRaw,这里 meta 可以让我们有更多的发挥空间,这里提供一些参考:
-
title: string; 页面标题,通常必选。
-
icon?: string; 图标,一般配合菜单使用。
-
auth?: boolean; 是否需要登录权限。
-
ignoreAuth?: boolean; 是否忽略权限。
-
roles?: RoleEnum[]; 可以访问的角色
-
keepAlive?: boolean; 是否开启页面缓存
-
hideMenu?: boolean; 有些路由我们并不想在菜单中显示,比如某些编辑页面。
-
order?: number; 菜单排序。
-
frameUrl?: string; 嵌套外链。
这里只提供一些思路,每个项目多多少少会涉及到这些问题,具体如何实现请查阅资料自行解决。
13.项目性能与细节优化
13.1 开启 gzip
开启 gzip 可以极大的压缩静态资源,对页面加载的速度起到了显著的作用。
使用 vite-plugin-compression[107] 可以 gzip 或 brotli 的方式来压缩资源,这一步需要服务器端的配合,vite 只能帮你打包出 .gz
文件。此插件使用简单,你甚至无需配置参数,引入即可。
13.2 页面载入进度条
页面路由切换时,附带一个加载进度条会显得非常友好,不至于白屏时间过长,让用户以为页面假死。
这时候我们可以用到 nprogress[108],在路由切换时开启和关闭:
import NProgress from ‘nprogress’;
router.beforeEach(async (to, from, next) => {
NProgress.start();
});
router.afterEach((to) => {
NProgress.done();
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
总结
三套“算法宝典”
算法刷题LeetCode中文版(为例)
人与人存在很大的不同,我们都拥有各自的目标,在一线城市漂泊的我偶尔也会羡慕在老家踏踏实实开开心心养老的人,但是我深刻知道自己想要的是一年比一年有进步。
最后,我想说的是,无论你现在什么年龄,位于什么城市,拥有什么背景或学历,跟你比较的人永远都是你自己,所以明年的你看看与今年的你是否有差距,不想做咸鱼的人,只能用尽全力去跳跃。祝愿,明年的你会更好!
由于篇幅有限,下篇的面试技术攻克篇只能够展示出部分的面试题,详细完整版以及答案解析,有需要的可以关注
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
[外链图片转存中…(img-vV8MIL0Q-1712283137446)]
[外链图片转存中…(img-IypQZOq2-1712283137446)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-F9ZvUWEz-1712283137446)]
总结
三套“算法宝典”
算法刷题LeetCode中文版(为例)
人与人存在很大的不同,我们都拥有各自的目标,在一线城市漂泊的我偶尔也会羡慕在老家踏踏实实开开心心养老的人,但是我深刻知道自己想要的是一年比一年有进步。
最后,我想说的是,无论你现在什么年龄,位于什么城市,拥有什么背景或学历,跟你比较的人永远都是你自己,所以明年的你看看与今年的你是否有差距,不想做咸鱼的人,只能用尽全力去跳跃。祝愿,明年的你会更好!
由于篇幅有限,下篇的面试技术攻克篇只能够展示出部分的面试题,详细完整版以及答案解析,有需要的可以关注
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算