• 前端架构思考,Vue or React?领域设计、文件结构、数据管理、主题替换


    从 Vue 和 React 看问题

    Vue 的优势
    1. 内置的 vite 构建工具,减少构建时间,提高开发效率,在大工程上特别明显

    2. 结构、样式、功能分开的设计,再通过 setup 做 crud 的分隔,整个页面维度的逻辑会特别清晰

    3. 在路由和数据管理上有官方的解决方案,可以完全没有选择的压力🍐

    4. 框架本身做了很多的性能优化,如下👇

      1. 静态提升,包括静态节点,静态属性

      2. 预字符串化,20 个静态节点以上

      3. 缓存事件处理函数

      4. block tree

      5. patch flag

    React 的优势
    1. 整体采用函数式编程思想以及数据不可变的渲染方式,减少魔法,没有 vue 指令的花里胡哨,相对更加好理解

    2. 用 jsx 的语法,减少了 html 本身来带的一些困扰,更加灵活性,可操控性强,更容易出更个性化的项目

    弱水三千,只取一瓢

    其实写到这里,相信大家已经明白我的价值倾向了。在没有企业包袱的角度来看,大厂都是 react 为先😯, 我更加推荐使用 vue,原因如下👇

    1. 大神没那么多,就大部分场景 95%,vue 都可以很好的覆盖

    2. 天生的结构、样式、逻辑相分离,各自的职责界限已经很明显了

    3. 因为魔法,所以开发更简单。vue 将很多业务常见的场景(嵌套路由、受保护的页面、导航守卫、路由切换动画、滚动条复位)都在 vuex 和 router 中实现了,开箱即用

    Why

    主要是为了避免出现以下这些问题👇

    1. 一个文件千八百行,太长了,需要不断的上下滑动,还看不懂🤔

    2. 界限不明确,就会导致混乱,dom 里面写逻辑,逻辑里透出 dom

    3. 都是页面的维度,没有领域的概念,缺少一个整体的认知

    最佳实践:每个页面不超过 7 个属性和方法,不强求

    How

    领域设计

    用面向对象的思维来思考整个项目。可以从一个产品中找到它所涵盖的一些抽象概念,并对每个概念进行赋能。例子🌰:类似协议、商品、计划、达人等维度,然后再对计划进行细分为通用计划、招募计划(类型维度)等,达人模块有添加、履约详情和搜索(功能维度)等模块,落在目录结构上如下👇

    1. └── page
    2.     ├── protocol 
    3.     ├── product
    4.     ├── plan 
    5.         ├── general 
    6.         └── recruit 
    7.     └── talent 
    8.         ├── add 
    9.         ├── fulfillment 
    10.         └── search

    计划为栗子🌰

    类型维度

    1. └── plan
    2.     ├── general // 通用
    3.         ├── create // 创建
    4.         ├── modify // 修改
    5.         ├── detail // 详情
    6.         └── info // 信息
    7.     ├── recruit // 招募
    8.         └── create // 创建
    9.             ├── components // 业务组件
    10.             └── views // 视图
    11.                 ├── Main // 业务组件
    12.                 ├── Video // 短视频
    13.                 ├── Live // 直播
    14.                 ├── index.module.scss // 页面样式
    15.                 └── index.tsx // 引入 main,提供上下文
    16.     ├── oriented // 定向
    17.     ├── free_visit // 自由
    18.         └── detail // 详情
    19.              ├── components // 业务组件
    20.              └── views // 视图
    21.                 ├── Loading // 加载态
    22.                 ├── Error // 错误态
    23.                 ├── Retry // 重试
    24.                 ├── Normal // 正常
    25.                 ├── index.module.scss // 页面样式
    26.                 └── index.tsx // 引入 main,提供上下文
    27.     └── coop_visit // 合作

    在划分类型后,再对每个计划做能力层级的划分,可以是 create、modify、detail、info 等模块,适用于每个计划有较大的差异性,可复用的模块不太多的情况

    在更复杂化的场景中,例如 recruit_plan 的 create 有 live 和 video 2种模式,差异化不大,可以在同一个页面中组装。可以用 main 承担 controller 层的功能,做模式的划分。同样例如页面的加载,错误,重试,正常等各个状态也同样可以在 main 做统一的处理

    功能维度

    1. └── plan
    2.     ├── create // 创建
    3.         ├── components // 业务组件
    4.         ├── models // 数据处理,逻辑层
    5.         ├── utils // 工具函数
    6.         ├── hooks // 自定义钩子🪝
    7.         ├── constants // 常量、enum
    8.         ├── typings // 类型 interfacetype
    9.         └── views // 对 components 做组装 
    10.             ├── Main // controller,做模式的适配和分发
    11.             ├── CreateGeneral // 具体的业务页面
    12.             ├── CreateRecruit // 具体的业务页面
    13.             ├── CreateOriented // 具体的业务页面
    14.             ├── CreateFreeVisit // 具体的业务页面
    15.             ├── CreateCoopVisit // 具体的业务页面
    16.             ├── index.module.scss // 页面样式
    17.             └── index.tsx // 引入 main,提供上下文
    18.     ├── modify // 修改
    19.     ├── detail // 详情
    20.     ├── info // 信息
    21.     └── list // 列表

    在功能划分后,再对每个计划进行赋能,可以是通用计划、招募计划、定向计划、自由探店、合作探店等模块,适用于功能大体类似的应用,可复用的组件、工具函数比较多的场景

    当然,也可以每种计划类型都是单独的一个文件夹,只是全部聚合在 detail 这个域中而已

    整体的一个原则是,跟着页面维度来走,页面文件夹📁映射路由,每个页面有自己的数据、权限等等其他的业务逻辑

    /plan/general/create ---> 找到的就是 plan 域下, general 类型的 create 能力

    顺便提一嘴,命名规范相关的

    命名文件名变量名常量名css名组件名/文件夹
    camelCase
    PascaCase
    snake_case
    kebab-case

    整体系统的框架体系

    1. ├── src 
    2.     ├── assets 
    3.     ├── biz_components 
    4.     ├── components 
    5.     ├── core 
    6.         ├── apis 
    7.         ├── constants 
    8.         ├── hooks 
    9.         ├── typings 
    10.         └── styles
    11.     ├── layout 
    12.     ├── pages
    13.     ├── app.module.scss 
    14.     ├── app.tsx 
    15.     ├── index.html 
    16.     ├── index.tsx 
    17.     └──router.ts
    18. ├── .gitignore 
    19. ├── build.sh 
    20. ├── jest.config.js 
    21. ├── pack.json 
    22. ├── README.md 
    23. └── tsconfig.json

    所有域的划分都是基于页面维度 pages 进行的,领域分层的方式也正如上面👆所谈到的那 2 种方式。在这种结构中,对几种 components 做下解读

    和 pages 同级的 components,这 2 种类型,都是领域的原子能力,他们的数据来源绝对的纯粹,就是从 props 中取

    1. biz_components: 复用比较多的业务组件

    2. components: 纯粹的组件,不含一丝一毫的业务逻辑

    每个页面下的 components,到了这个类别下,已经是圈定了指定的页面,所以除了 props,还可以有 model,甚至是页面级的 model 数据,至于数据的处理方案,请向下细读

    数据管理

    整体使用的是 context 的一个方案,包裹在最外层,在里层去消费数据

    用到了一个三方库 unstated-next[1]

    用法很简单,demo

    1. // page.ts
    2. import { createContainer } from 'unstated-next';
    3. const useContainer = () => {
    4.     ...
    5.     
    6.     return {
    7.         state: {
    8.             ...
    9.         }
    10.         ...
    11.     }
    12. }
    13. export const xxxModel = createContainer(useContainer);
    14. // 在页面的 index.tsx 中
    15. export default () => {
    16.     <xxxModel.Provider>
    17.         <Main />
    18.     </xxModel.Provider>
    19. }
    20. // 在 views 或者 components 中消费
    21. export default () => {
    22.     const {} = xxxModel.useContainer();
    23.     
    24. }

    这一套的思维逻辑和实现,是基于一定的业务场景,不一定全部适合哈。总的来说,通过这种代码的组织方式,让 ui 层和逻辑层出现了比较分明的界限,明确了各自的职责,让维护的成本更加低了。至于逻辑层的抽方法、抽 hook;ui 层的减少 dom 元素、语意化、seo 等其他的优化,需要在一定的场景下进行讨论,这里就不涉及了哈。

    相比较 redux 来说,unstated-next 的 size 更小,使用起来更简单

    相比较 context 来说,它本身就还是 hook,封装在自定义 hook,或者其他地方,都不是一种很好的实现 ui 和逻辑分离的方式

    提供下 localStorage 的最佳用法,拒绝花里胡哨,只为解决问题

    1. // 从 localStorage 中获取数据
    2. export const getLocalStorage = (keystring=> JSON.parse(localStorage.getItem(key) || '{}')?.data;
    3. // 把值存在 localStorage,格式 { dataany }
    4. export const setLocalStorage = (keystringdataany=> {
    5.   localStorage.setItem(key, JSON.stringify({ data }));
    6. };
    7. 想想 data 的妙用,哈哈哈😄

    推荐 2 个处理数据 filter 的库,无样式侵入只接管状态

    1. rc-field-form[2] // 推荐

    2. formily

    rc 还可以用来做反馈组件,很好用的,antd 的表单也是基于此封装的哈

    单元测试覆盖

    单测的写法,使用 jest + testing-library + mm 来进行 mock 以及断言

    最好可以在 CI/CD 上配置增量的代码覆盖率是要求在多少,每个 mr 都不能拉低单测覆盖率(待学习)

    需要注意的一些点

    1. describe 的描述可以统一下

    2. it 和 test 也可以统一下😒

    3. 通过 snapshot 来进行 ui 的校对

    4. 在每个 test 中,用户的行为操作是基于人的视角,而不是机器的视角

      await userEvent.click(btn as Elment) ✅ // @testing-library/user-event

      screen.querySelector('.btn').click ❌

    强迫症患者最后的挣扎😄,毕竟代码是给人看的,机器顺便运行运行

    1. test('render', () => {
    2.     const { asFragment } = render(<XXX />);
    3.     expect(asFragment()).toMatchSnapshot();
    4. })

    推荐一些学习单测的网站

    jest: zh-hans.reactjs.org/docs/testin…[3]

    testing-library:

    1. rualc.com/frontend/te…[4]

    2. rualc.com/frontend/te…[5]

    3. testing-library.com/docs/[6]

    4. juejin.cn/post/690705…[7]

    5. blog.mimacom.com/react-testi…[8]

    6. github.com/testing-lib…[9]

    7. testing-library.com/docs/ecosys…[10]

    8. www.w3cschool.cn/doc\_jest/je…[11]

    9. juejin.cn/post/709218…[12]

    mm: www.npmjs.com/package/mm[13]

    如果在组件维度去写单测需要去 mock 和页面一样多的数据时,我们应该考虑单测的覆盖维度就是页面级别的

    个人喜好:test 跟着 components 或者 views,这种方式比放在最外层会好很多!

    浅谈其他

    主题替换

    设计产品总是会有很多其他的 idea,特别在视觉上,所以视觉改版是 FE 很痛苦的一件事。纯粹的手动替换,傻傻的。所以我们在开发时,如果可以有一个主题包如果可以的组件库相结合是最好的,类似 antd 和 elmentui 一样,在需要更换主题的时候,升级包版本就欧了

    其他方式

    1. 利用媒体查询,在 media_type 里去做xxx

    2. 利用 css next 的变量模式

    总的来说,基于 css 变量,推荐一篇文章 关于前端主题切换的思考和现代前端样式的解决方案落地[14]

    Icon管理

    常见的几种方式

    1. 雪碧图 // 没条件的情况下

    2. iconfont

    3. png、svg // 最好只用一种,不强求

    4. 生成一个 icon 包,所有的小图标做统一的管理 // 有条件的话,成本比较大,有管理的成本💰

    生成二维码

    推荐使用库 qrcode.react[15]

    在 svg 图片格式下,当成组件来用。通过 backgroud 的 z-index:0 和 info 的 z-index:1 来处理背景图的问题,简单好用

    • 参考资料

    [1]

    https://www.npmjs.com/package/unstated-next: https://link.juejin.cn/?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Funstated-next

    [2]

    https://www.npmjs.com/package/rc-field-form: https://link.juejin.cn/?target=https%3A%2F%2F

    www.npmjs.com%2Fpackage%2Frc-field-form

    [3]

    https://zh-hans.reactjs.org/docs/testing-recipes.html: https://link.juejin.cn/?target=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Ftesting-recipes.html

    [4]

    https://rualc.com/frontend/testing-library/#he-react-yi-qi: https://link.juejin.cn/?target=https%3A%2F%2Frualc.com%2Ffrontend%2Ftesting-library%2F%23he-react-yi-qi

    [5]

    https://rualc.com/frontend/testing-library/#package: https://link.juejin.cn/?target=https%3A%2F%2Frualc.com%2Ffrontend%2Ftesting-library%2F%23package

    [6]

    https://testing-library.com/docs/: https://link.juejin.cn/?target=https%3A%2F%2Ftesting-library.com%2Fdocs%2F

    [7]

    https://juejin.cn/post/6907052045262389255#heading-20: https://juejin.cn/post/6907052045262389255#heading-20

    [8]

    https://blog.mimacom.com/react-testing-library-fireevent-vs-userevent/: https://link.juejin.cn/?target=https%3A%2F%2Fblog.mimacom.com%2Freact-testing-library-fireevent-vs-userevent%2F

    [9]

    https://github.com/testing-library/user-event: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftesting-library%2Fuser-event

    [10]

    https://testing-library.com/docs/ecosystem-user-event/: https://link.juejin.cn/?target=https%3A%2F%2Ftesting-library.com%2Fdocs%2Fecosystem-user-event%2F

    [11]

    https://www.w3cschool.cn/doc_jest/jest-expect.html#tomatchregexporstring: https://link.juejin.cn/?target=https%3A%2F%2Fwww.w3cschool.cn%2Fdoc_jest%2Fjest-expect.html%23tomatchregexporstring

    [12]

    https://juejin.cn/post/7092188990471667749: https://juejin.cn/post/7092188990471667749

    [13]

    https://www.npmjs.com/package/mm: https://link.juejin.cn/?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fmm

    [14]

    https://mp.weixin.qq.com/s/0xTZcE3MPezRl3LILR8a_w: https://link.juejin.cn/?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2F0xTZcE3MPezRl3LILR8a_w

    [15]

    https://www.npmjs.com/package/qrcode.react: https://link.juejin.cn/?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fqrcode.react

  • 相关阅读:
    翻译:Fully Convolutional Networksfor Semantic Segmentation
    PTP Precision Time Protocol精确时间协议 IEEE1588解决方案(含PTP和PPS)
    JAVA栈、堆、方法区
    jmeter理论
    设计模式之工厂模式
    802.11-2020协议学习__专题__TxTime-Calculation__HR/DSSS
    定时备份mysql数据库
    【CV】Reg2Net:一种用于计算机视觉任务的多尺度骨干架构
    MQTT协议
    GStreamer安装——Mac OS X
  • 原文地址:https://blog.csdn.net/2301_79226238/article/details/133864777