当前位置: 首页 > 科技观察

一个简洁、强大、可扩展的前端项目架构是什么样的?

时间:2023-03-14 19:52:52 科技观察

大家好,我是Kason。React技术栈的优势之一就是社区繁荣,你在业务中需要实现的功能基本都能找到对应的开源库。但繁荣也有不利的一面——实现同样的功能,选择太多,该选哪个?本文将介绍一个12.7k的开源项目——BulletproofReact[1]。该项目对构建“干净、强大、可扩展的前端项目架构”的各个方面给出了建议。什么是防弹反应?BulletproofReact不同于我们常见的脚手架(比如CRA),它的作用是“根据模板创建一个新项目”。前者包括一个完整的React全栈论坛项目:用户登录页作者以该项目为例,展示了与“项目架构”相关的13个方面,例如:如何组织文件目录。推荐的工程配置是什么。编写业务组件时如何规范。如何进行状态管理。API层如何设计。etc.......由于篇幅有限,本文介绍了其中的一些。你同意这些观点吗?文件目录如何组织项目推荐如下目录格式:src|+--assets#静态资源|+--components#公共组件|+--config#全局配置|+--features#特性|+--hooks#公共钩子|+--lib#二次导出的第三方库|+--providers#应用中的所有provider|+--routes#路由配置|+--stores#全局statestores|+--test#测试工具,mockServer|+--types#全局类型文件|+--utils#通用工具功能其中features目录和components目录的区别在于components存放的是全局公共组件,而features存放的是"业务相关的功能”。比如我要开发“评论”模块。“评论”是一个特征,所有和它相关的内容都存在于features/comments目录下。“评论”模块中需要输入框,输入框的通用组件来自components目录。所有“特性相关”的内容都会汇聚到features目录下,包括:src/features/xxx-feature|+--api#特性相关的请求|+--assets#特性相关的静态资源|+--components#componentsrelatedtofeatures|+--hooks#hooksrelatedtofeatures|+--routes#routesrelatedtofeatures|+--stores#statestoresrelatedtofeatures|+--types#relatedtofeatures相关类型声明|+--utils#Feature相关的实用函数|+--index.ts#入口特征导出的所有内容只能通过统一入口调用,例如:import{CommentBar}from"@/features/comments"改为:import{CommentBar}from"@/features/comments/components/CommentBar这可以通过配置ESLint来实现:{rules:{'no-restricted-imports':['error',{patterns:['@/features/*/*'],},],//...其他配置}}与在全局目录中以“平面形式”存储“功能相关内容”相比(例如,存储功能钩子存放在全局hooks目录下),将features目录作为“相关代码的集合”,可以有效防止项目规模增大后代码组织混乱。怎么做?并不是状态管理项目中的所有状态都需要存储在“中心化存储”中,需要根据状态类型区别对待。组件状态对于组件的局部状态,如果只有组件本身及其后代需要这部分状态,可以用useState或useReducer保存。应用状态的状态与应用交互的状态有关,比如“打开弹窗”、“通知”、“改变深色模式”等,应该遵循“放状态”的原则尽可能靠近使用它的组件”,并且不要将所有状态都定义为“全局状态”。以BulletproofReact中的示例工程为例,首先定义“通知相关状态”://bulletproof-react/src/stores/notifications.tsexportconstuseNotificationStore=create((set)=>({notifications:[],addNotification:(notification)=>set((state)=>({notifications:[...state.notifications,{id:nanoid(),...notification}],})),dismissNotification:(id)=>set((state)=>({notifications:state.notifications.filter((notification)=>notification.id!==id),})),}));然后使用“通知相关状态”引用useNotificationStore,例如://bulletproof-react/src/components/Notifications/Notifications.tsximport{useNotificationStore}from'@/stores/notifications';import{Notification}from'./Notification';exportconstNotifications=()=>{const{notifications,dismissNotification}=useNotificationStore();return(

{notifications.map((notification)=>())}
);};这里使用的状态管理工具是zustand,还有很多其他的选择:context+hooksredux+reduxtoolkitmobxconstatejotairecoilxstate这些方案各有特色,但都是为了处理“应用状态”服务端缓存状态为服务端请求缓存在前端的数据,虽然可以使用上述处理“应用状态”的工具解决,但是相对于“应用状态”,“服务端缓存状态”还涉及到诸如此类的问题作为“缓存失效”和“序列化数据”。因此,最好使用专门的工具,比如:react-query-REST+GraphQLswr-REST+GraphQLapolloclient-GraphQLurql-GraphQlformstate表单数据需要区分“受控”和“不受控”,form本身还有很多逻辑需要处理(比如“表单验证”),所以建议使用专门的库来处理这部分状态,比如:ReactHookFormFormikReactFinalFormURLstateURLstateincludes:urlparams(/app/${dynamicParam})queryparams(/app?dynamicParam=1)的状态通常由路由库处理,比如react-router-dom。小结本文摘录了BulletproofReact中推荐的一些解决方案。有什么观点是你认同的?欢迎在评论区交流项目架构的最佳实践。参考文献[1]BulletproofReact:https://github.com/alan2207/bulletproof-react。