本系列文章将以实际Vue项目开发的视角完成对整个项目从底层文件结构,库使用,到顶层数据管理进行系统性的描述。同时将在此过程中完成对一个初步的前端项目的基础开发设计和实现。
目标
本系列以拉通整个前端开发流程为目标,在整个项目前期搭建、配置构建、第三方库选择、常用库使用、界面开发、文件结构规范等角度进行统一规范化的整理。本系列文章将不断迭代以丰富内容。
内容规划
本系列拟拆分为九篇文章:
- 第一篇:序章
- 第二篇:开发伊始
- 第三篇:基础架构开发
- 第四篇:业务开发
- 第五篇:调与试
- 第六篇:数据与持久化
- 第七篇:抽象与具体
- 第八篇:框架与组件
- 第九篇:设计与艺术
每篇文章之间有少部分重合,从项目开发的实际角度进行一步步递进,对Vue前端项目在我日常开发的过程进行整理和简述。
本篇文章将对整个系列文章内所使用的框架、技术、主要依赖、开发规范、开发流程、文件结构进行总体性的描述。
前端框架Vue3 CompositionAPI + TypescriptReact(tsx)
本系列中采用Vue3作为前端框架,在采用浏览器内置Proxy特性后,Vue整体API风格从原本的OptionAPI风格偏向了Composition 风格API,同时CompositionAPI向下兼容OptionAPI,但从我实际开发的角度来说,CompositionAPI所具有的优势是OptionAPI无法比拟的。(这里默认使用SinglePageComponent开发,这是大型项目的基础)
使用OptionAPI进行开发拥有以下弊端:
- OptionAPI 对Typescript(JavaScript超集,提供强类型静态检查,不具备运行时检查能力)支持很差
- OptionAPI组件在单组件逻辑趋向复杂时,组件复杂度由于需要将同一功能特性下的不同类型(数据、计算属性、监听器等)的代码放置在不同key中,同一特性业务代码被拆分到不同位置,导致阅读某一特性代码时需要不断滚动屏幕至对应类型代码存放区域。随着程序复杂度的提升,使用OptionAPI构建的组件其代码可读性的倒数呈指数级增长。因此OptionAPI在没有良好规范和抽象化约束的条件下难以在满足足够功能时做到较高的组件可读性。
因此本系列采用Vue提供的另一种CompositionAPI进行组件构建,该API从我开发的角度上来说,用了CompositionAPI就不会再想写OptionAPI,同时CompositionAPI对于Typescript的支持相对好一些,可以提供必要的CodeIntellisense智能提示,同时推到各个数据的类型并在编程时实时静态校验变量类型等。这里将直接采用Vue CompositionAPI + Typescript的形式进行开发。
=======跳过先写一些实际的开发细节的草稿,后续补充========
前端数据请求+缓存
关键字: Axios、useQuery、分页、缓存、请求、RESTfulAPI
项目后端API风格采用RESTful(REpresentation-State-Transfer-Engine ful),即“URL即资源”,该风格将所有API都描述为对某种资源的操作,操作形式以HttpMethod的方式进行描述,当然由于这只是一种风格,并不是必须严格按照此风格设计所有API,但在采用该风格进行API构建时,从Human Readability人类可读性来说比胡乱设计要好很多。因此大部分API应参考此风格进行设计约束。(有兴趣可以了解以下GraphQL,Facebook设计的用于替代RESTful的下一代API风格规范)。
后端API风格确定后,我们需要确定在前端时我们采用的请求库是什么,浏览器实际本身具有异步Ajax请求的能力,但使用前端请求库可以帮助我们减少很多在拦截、校验方面的代码编写。这里我们使用Vue官方推荐的请求库Axios作为我们进行Http请求发送及管理的第三方库。
权限校验 Authentication
Axios采用中间件Middleware(具体可以看《Head First Java》中关于中间件设计模式的描述)的设计模式来完成插件化,我们通过API发送的请求都将通过中间件链条完成对于请求Request、相应Response数据的拦截、处理、校验等操作。
这里我们注入一个全局Interceptor拦截器(实际为前述链条上的一个中间件)来完成对于请求的访问令牌AccessToken存在性校验及Token刷新(这里采用JWT Json Web Token作为认证规范)。拦截器逻辑为自动从Cookie内拿到访问令牌值,而后将令牌值以Bearer Authentication的验证模式写入请求头(RequestHttpHeader)中Authorization字段内,而后发送请求。下面为该请求拦截器的代码实现:
axiosInstance.interceptors.request.use( async (config) => { if (config.url != "/auth/refresh") { const value = authStore.access_token; config.headers = { Authorization: `Bearer ${value}`, Accept: "application/json", "Content-Type": "application/json", }; } return config; }, (error) => { Promise.reject(error); } );
同时由于当Token失效时请求将返回无权限访问状态码403,因此在相应Response阶段也需要一个相应拦截器将响应状态码值为403的响应拦截而后刷新令牌后重新发起请求的逻辑。该部分代码逻辑如下:
axiosInstance.interceptors.response.use( (response) => { return response; }, async (error) => { const originalRequest: any = error.config; const status = error.response.status; if ((status == 401 || status == 403) && !originalRequest._retry) { if (error.config.url == "/auth/refresh") { router.push({ name: "Login" }); authStore.eraseToken(); return Promise.reject(error); } originalRequest._retry = true; const data = await apiTokenRefresh(); authStore.setAccessToken(data.access_token); authStore.setRefreshToken(data.refresh_token); return axiosInstance(originalRequest); } return Promise.reject(error); } );
上述两部分代码完成了我们创建的Axios实例中关于权限校验和令牌刷新的功能。下面开始设计实际的请求:
请求方法 HttpMethod
数据请求为上层代码实际调用并使用Axios实例的地方,Axios实例按照Http方法(HttpMethod)名提供了一组用于不同请求方法发出的API,因为此处使用RESTful风格进行API约束,因此此处仅采用最常用的四种方法GET、POST、DELETE、PUT。
GET请求
GET http://api.cat.store/cat?page=0&pageSize=1&name=Miao
GET请求语义为根据URL获取服务器上某资源的信息、数据。该操作将不改变服务器上该资源状态(这里也是RESTful中StateTransfer的一个表现,Representation则指向的是资源性的描述API),因此在查询数据时可使用GET作为请求方法。
POST请求
POST http://api.cat.store/cat <!--body--> { "name":"Cat", "age":3, "category":"British shorthair" }
POST请求语义为在服务器上创建一个该资源,一般来说请求创建的资源的信息将存放在请求体(Request Body)内,这里将请求体内载荷类型都定为JSON(JavaScript Object Notation)字符串。
DELETE请求
DELETE http://api.cat.store/cat/2
DELETE请求语义顾名思义,将某一条服务器上的资源记录删除。RESTful风格一般使用`/{资源名}/{资源ID}`的路由来直接表示某一条服务器资源数据。
PUT请求
POST http://api.cat.store/cat/2 <!--body--> { "age":4, }
PUT请求语义为修改服务器上已有对象,因此需要通过对象ID定位到对应的资源,而后在请求体内根据API设计完成对变更属性的描述。上面这一段代码就将ID为2的猫咪资源的年龄age属性变更为4.
上述这几种简单的规范仅满足RESTfulAPI风格的最低级规范,因此如果希望学习完整的RESTful风格规范及其最高等级的API的具体设计方法参考《RESTfulWebAPIs》书籍。
数据请求 Data-Fetching
数据请求的实际代码实现相对比较简单,几乎一看就懂:
//用户修改API,参数为用户ID及用户实体 export async function apiModifyUser(userid: number, user: IUser) { // PUT请求,请求返回结果类型为IUserModifyResponse const resp = await axiosInstance.put<IUserModifyResponse>( //请求URL `/users/${userid}`, //请求体RequestBody,这里直接传入一个对象,在发送请求时Axios将自动序列化该对象为JSON字符串 user ); // 返回是否修改成功,该API若响应对象某data属性大于零则说明修改成功 return resp.data.data > 0; }
这里就跳了一个比较有代表性的API进行简单描述,最快提升该库熟练度的方法是自己写一遍,然后调一遍请求。
数据缓存 Data-Cache
==未完待续==