背景说明

随着业务的增长和开发团队的成员快速增加,其中很多新人来自于五湖四海各大门派,在编码的风格和习惯中也出现各异。通常在相互 code review 时发现很多代码上的问题,久而久之代码出现了代码难以维护的问题,甚至还会出现低级错误。

格式化代码规范

第三方库版本

环境准备前端代码开发规范工程化

  • node >= v20.0.0
  • vite >= 5.2.0
  • vscode 插件
    • ESLint
    • Stylelint
    • Prettier - Code formatter

初始化Remix 项目

  • 创建一个新的 Hydrogen 店面,可以使用官方提供的脚手架
  1. npm create @shopify/hydrogen@latest -- --quickstart

Shopify Hydrogen前端代码开发规范工程化 - 图1

  • 可以看到项目已经默认集成了Remix、Vite、Eslint等等
  • 根目录下创建.vscode文件夹
    • extensions.json - 用于存放推荐vscode扩展插件
    • settings.json - 用于设置vscode的基本配置(项目配置会覆盖本地全局配置)

ESLint

ESLint 能够帮助开发者发现代码中的潜在错误和不良实践,确保代码符合团队或项目的编码规范,从而提高代码质量和可维护性。

这是使用的版本是eslint v8的版本,和v9的版本有一定的区别

  • 配置写入(保存的时候会自动修复)
  1. {
  2. "editor.codeActionsOnSave": {
  3. "source.fixAll": "explicit",
  4. "source.fixAll.eslint": "explicit",
  5. },
  6. }
  • 提供的脚手架已经有安装了相关的ESlint的插件,例如:eslint-plugin-hydrogen、@remix-run/eslint-config等等
  • 安装额外ESlint相关的插件(排序插件)

eslint-import-resolver-alias: 允许你配置模块别名,帮助 ESLint 正确解析这些别名。这样在使用 import 语法时,ESLint 可以正确识别别名对应的模块路径,避免因无法解析别名而导致的错误提示。

eslint-plugin-import: 它通常与 eslint-plugin-import 一起使用,后者用于增强 ESLint 对模块导入的检查和规范化。通过 eslint-import-resolver-alias,你可以确保 ESLint 的 import 规则能够正确处理你的别名配置。

  1. npm install eslint-import-resolver-alias eslint-plugin-import -D
  • .eslintrc.cjs文件中配置相关配置
  1. module.export = {
  2. // ...
  3. extends: [
  4. // ...
  5. "plugin:import/recommended",
  6. ],
  7. settings: {
  8. 'import/resolver': {
  9. // eslint-import-resolver-typescript 可以解决别名报错的问题
  10. typescript: {
  11. alwaysTryTypes: true,
  12. },
  13. // eslint-import-resolver-alias 可以解决绝对路径的问题
  14. alias: {
  15. map: [
  16. ['', './public'], // <-- this line
  17. ],
  18. extensions: ['.js', '.jsx', '.ts', '.tsx', '.svg'],
  19. },
  20. },
  21. },
  22. rules: {
  23. //...
  24. "import/order": [
  25. "error",
  26. {
  27. // 对导入模块进行分组,分组排序规则如下
  28. groups: [
  29. "builtin", // 内置模块
  30. "external", // 外部模块
  31. "parent", //父节点依赖
  32. "sibling", //兄弟依赖
  33. "internal", //内部引用
  34. "index", // index文件
  35. "type", //类型文件
  36. "unknown",
  37. ],
  38. //通过路径自定义分组
  39. pathGroups: [
  40. {
  41. pattern: "@/**", // 把@开头的应用放在external分组后面
  42. group: "external",
  43. position: "after",
  44. },
  45. ],
  46. // 是否开启独特组,用于区分自定义规则分组和其他规则分组
  47. distinctGroup: true,
  48. // 每个分组之间换行
  49. "newlines-between": "always",
  50. // 相同分组排列规则 按字母升序排序
  51. alphabetize: { order: "asc" },
  52. },
  53. ],
  54. },
  55. };
  • 在package.json添加脚本"lint:fix": "eslint . --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore --fix"
  • 这时候可以使用脚本npm run lint:fix进行初步格式化

Prettier

  • 删除掉package.json中的"prettier": "@shopify/prettier-config"

Shopify Hydrogen前端代码开发规范工程化 - 图2

  • 安装prettier相关依赖

Prettier 是一个代码格式化工具,旨在自动化地格式化代码,使其符合一致的风格标准。它支持多种语言,包括 JavaScript、TypeScript、HTML 和 CSS 等。

eslint-config-prettier 是一个 ESLint 配置,它用于禁用与 Prettier 冲突的 ESLint 规则,确保 ESLint 和 Prettier 可以和平共处。

eslint-plugin-prettier 是一个 ESLint 插件,它将 Prettier 的格式化规则作为 ESLint 规则运行,确保代码在保存时自动格式化。

  1. npm install prettier eslint-config-prettier eslint-plugin-prettier -D
  • 添加.prettierrc.cjs文件
  1. module.exports = {
  2. // 一行最多多少个字符
  3. printWidth: 90,
  4. // 指定每个缩进级别的空格数
  5. tabWidth: 2,
  6. // 使用制表符而不是空格缩进行
  7. useTabs: false,
  8. // 在语句末尾是否需要分号
  9. semi: true,
  10. // 是否使用单引号
  11. singleQuote: true,
  12. // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
  13. quoteProps: 'as-needed',
  14. // 在JSX中使用单引号而不是双引号
  15. jsxSingleQuote: false,
  16. // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
  17. trailingComma: 'es5',
  18. // 在对象文字中的括号之间打印空格
  19. bracketSpacing: true,
  20. // jsx 标签的反尖括号需要换行
  21. jsxBracketSameLine: false,
  22. // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
  23. arrowParens: 'always',
  24. // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
  25. rangeStart: 0,
  26. rangeEnd: Infinity,
  27. // 指定要使用的解析器,不需要写文件开头的 @prettier
  28. requirePragma: false,
  29. // 不需要自动在文件开头插入 @prettier
  30. insertPragma: false,
  31. // 使用默认的折行标准 always\never\preserve
  32. proseWrap: 'preserve',
  33. // 指定HTML文件的全局空格敏感度 css\strict\ignore
  34. htmlWhitespaceSensitivity: 'css',
  35. // Vue文件脚本和样式标签缩进
  36. vueIndentScriptAndStyle: false,
  37. //在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
  38. //然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
  39. //对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
  40. // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
  41. endOfLine: 'crlf',
  42. };
  • 修改.eslintrc.cjs文件,注入prettier相关插件和配置
  • 添加.prettierignore文件
  1. /dist/
  2. /node_modules/
  3. /public/
  • 配置好可以看到_index.tsx文件里的内容标红了
      1. 使用配置好的脚本 npm run lint:fix进行修复
      1. .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
  1. {
  2. "editor.codeActionsOnSave": {
  3. "source.fixAll": "explicit",
  4. "source.fixAll.eslint": "explicit",
  5. "source.fixAll.stylelint": "explicit"
  6. },
  7. "editor.formatOnSave": true,
  8. "editor.defaultFormatter": "esbenp.prettier-vscode",
  9. }
  • Tip:.prettierrc.cjs修改了相关格式化配置规则,ESLint代码不会立即去检查报红,Ctrl+Shift+P 打开命令面板,输入Restart Extension Host重启扩展宿主,等待加载一会儿,就能同步更新了。也可以暴力直接重启打开vscode软件。
  • 疑问
    • 为什么有了eslint还使用prettier?

Stylelint

  • 安装css预处理器 sass
  1. npm i sass
  • 安装相关依赖(这是使用Scss样式预处理器为例)
  1. npm install stylelint stylelint-scss stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-scss stylelint-order -D
  • 根目录分别新增 .stylelintignore.stylelintrc.cjs两个文件
  1. # .stylelintignore
  2. # 旧的不需打包的样式库
  3. *.min.css
  4. # 其他类型文件
  5. *.js
  6. *.jpg
  7. *.png
  8. *.eot
  9. *.ttf
  10. *.woff
  11. *.json
  12. # 测试和打包目录
  13. /test/
  14. /dist/
  15. /node_modules/
  16. /lib/
  1. module.exports = {
  2. extends: [
  3. 'stylelint-config-standard',
  4. 'stylelint-config-standard-scss',
  5. 'stylelint-config-recommended-scss',
  6. ],
  7. plugins: ['stylelint-scss', 'stylelint-order'],
  8. rules: {
  9. 'no-irregular-whitespace': true,
  10. // 指定样式的排序
  11. 'order/properties-order': [
  12. [
  13. 'content',
  14. 'position',
  15. 'top',
  16. 'right',
  17. 'bottom',
  18. 'left',
  19. 'z-index',
  20. 'display',
  21. 'vertical-align',
  22. 'flex',
  23. 'flex-grow',
  24. 'flex-shrink',
  25. 'flex-basis',
  26. 'flex-direction',
  27. 'flex-flow',
  28. 'flex-wrap',
  29. 'grid',
  30. 'grid-area',
  31. 'grid-template',
  32. 'grid-template-areas',
  33. 'grid-template-rows',
  34. 'grid-template-columns',
  35. 'grid-row',
  36. 'grid-row-start',
  37. 'grid-row-end',
  38. 'grid-column',
  39. 'grid-column-start',
  40. 'grid-column-end',
  41. 'grid-auto-rows',
  42. 'grid-auto-columns',
  43. 'grid-auto-flow',
  44. 'grid-gap',
  45. 'grid-row-gap',
  46. 'grid-column-gap',
  47. 'gap',
  48. 'row-gap',
  49. 'column-gap',
  50. 'align-content',
  51. 'align-items',
  52. 'align-self',
  53. 'justify-content',
  54. 'justify-items',
  55. 'justify-self',
  56. 'order',
  57. 'float',
  58. 'clear',
  59. 'object-fit',
  60. 'overflow',
  61. 'overflow-x',
  62. 'overflow-y',
  63. 'overflow-scrolling',
  64. 'clip',
  65. //
  66. 'box-sizing',
  67. 'width',
  68. 'min-width',
  69. 'max-width',
  70. 'height',
  71. 'min-height',
  72. 'max-height',
  73. 'margin',
  74. 'margin-top',
  75. 'margin-right',
  76. 'margin-bottom',
  77. 'margin-left',
  78. 'padding',
  79. 'padding-top',
  80. 'padding-right',
  81. 'padding-bottom',
  82. 'padding-left',
  83. 'border',
  84. 'border-spacing',
  85. 'border-collapse',
  86. 'border-width',
  87. 'border-style',
  88. 'border-color',
  89. 'border-top',
  90. 'border-top-width',
  91. 'border-top-style',
  92. 'border-top-color',
  93. 'border-right',
  94. 'border-right-width',
  95. 'border-right-style',
  96. 'border-right-color',
  97. 'border-bottom',
  98. 'border-bottom-width',
  99. 'border-bottom-style',
  100. 'border-bottom-color',
  101. 'border-left',
  102. 'border-left-width',
  103. 'border-left-style',
  104. 'border-left-color',
  105. 'border-radius',
  106. 'border-top-left-radius',
  107. 'border-top-right-radius',
  108. 'border-bottom-right-radius',
  109. 'border-bottom-left-radius',
  110. 'border-image',
  111. 'border-image-source',
  112. 'border-image-slice',
  113. 'border-image-width',
  114. 'border-image-outset',
  115. 'border-image-repeat',
  116. 'border-top-image',
  117. 'border-right-image',
  118. 'border-bottom-image',
  119. 'border-left-image',
  120. 'border-corner-image',
  121. 'border-top-left-image',
  122. 'border-top-right-image',
  123. 'border-bottom-right-image',
  124. 'border-bottom-left-image',
  125. //
  126. 'background',
  127. 'background-color',
  128. 'background-image',
  129. 'background-attachment',
  130. 'background-position',
  131. 'background-position-x',
  132. 'background-position-y',
  133. 'background-clip',
  134. 'background-origin',
  135. 'background-size',
  136. 'background-repeat',
  137. 'color',
  138. 'box-decoration-break',
  139. 'box-shadow',
  140. 'outline',
  141. 'outline-width',
  142. 'outline-style',
  143. 'outline-color',
  144. 'outline-offset',
  145. 'table-layout',
  146. 'caption-side',
  147. 'empty-cells',
  148. 'list-style',
  149. 'list-style-position',
  150. 'list-style-type',
  151. 'list-style-image',
  152. //
  153. 'font',
  154. 'font-weight',
  155. 'font-style',
  156. 'font-variant',
  157. 'font-size-adjust',
  158. 'font-stretch',
  159. 'font-size',
  160. 'font-family',
  161. 'src',
  162. 'line-height',
  163. 'letter-spacing',
  164. 'quotes',
  165. 'counter-increment',
  166. 'counter-reset',
  167. '-ms-writing-mode',
  168. 'text-align',
  169. 'text-align-last',
  170. 'text-decoration',
  171. 'text-emphasis',
  172. 'text-emphasis-position',
  173. 'text-emphasis-style',
  174. 'text-emphasis-color',
  175. 'text-indent',
  176. 'text-justify',
  177. 'text-outline',
  178. 'text-transform',
  179. 'text-wrap',
  180. 'text-overflow',
  181. 'text-overflow-ellipsis',
  182. 'text-overflow-mode',
  183. 'text-shadow',
  184. 'white-space',
  185. 'word-spacing',
  186. 'word-wrap',
  187. 'word-break',
  188. 'overflow-wrap',
  189. 'tab-size',
  190. 'hyphens',
  191. 'interpolation-mode',
  192. //
  193. 'opacity',
  194. 'visibility',
  195. 'filter',
  196. 'resize',
  197. 'cursor',
  198. 'pointer-events',
  199. 'user-select',
  200. //
  201. 'unicode-bidi',
  202. 'direction',
  203. 'columns',
  204. 'column-span',
  205. 'column-width',
  206. 'column-count',
  207. 'column-fill',
  208. 'column-gap',
  209. 'column-rule',
  210. 'column-rule-width',
  211. 'column-rule-style',
  212. 'column-rule-color',
  213. 'break-before',
  214. 'break-inside',
  215. 'break-after',
  216. 'page-break-before',
  217. 'page-break-inside',
  218. 'page-break-after',
  219. 'orphans',
  220. 'widows',
  221. 'zoom',
  222. 'max-zoom',
  223. 'min-zoom',
  224. 'user-zoom',
  225. 'orientation',
  226. 'fill',
  227. 'stroke',
  228. //
  229. 'transition',
  230. 'transition-delay',
  231. 'transition-timing-function',
  232. 'transition-duration',
  233. 'transition-property',
  234. 'transform',
  235. 'transform-origin',
  236. 'animation',
  237. 'animation-name',
  238. 'animation-duration',
  239. 'animation-play-state',
  240. 'animation-timing-function',
  241. 'animation-delay',
  242. 'animation-iteration-count',
  243. 'animation-direction',
  244. 'animation-fill-mode',
  245. ],
  246. {
  247. unspecified: 'bottom',
  248. severity: 'error',
  249. },
  250. ],
  251. },
  252. }
  • 打开命令面板重启一下扩展宿主,可以创建一个css文件,输入相关样式,可以看到文件报红
  • 添加脚本命令行
  1. // ...
  2. "scripts": {
  3. // ...
  4. "stylelint": "stylelint ./**/*.{css,scss}",
  5. "stylelint:fix": "stylelint ./**/*.{css,scss} --fix",
  6. },
  7. // ...
  • 使用npm run stylelint:fix进行样式格式化修复
  • .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
  1. {
  2. "editor.codeActionsOnSave": {
  3. "source.fixAll": "explicit",
  4. "source.fixAll.eslint": "explicit",
  5. "source.fixAll.stylelint": "explicit"
  6. },
  7. "editor.formatOnSave": true,
  8. "editor.defaultFormatter": "esbenp.prettier-vscode",
  9. "stylelint.validate": ["css", "scss", "sass"],
  10. "stylelint.enable": true,
  11. "css.validate": false,
  12. "scss.validate": false,
  13. "[scss]": {
  14. "editor.defaultFormatter": "esbenp.prettier-vscode"
  15. },
  16. "[css]": {
  17. "editor.defaultFormatter": "esbenp.prettier-vscode"
  18. }
  19. }

Husky

  • 安装依赖
  1. npm install husky@8.0.3 -D
  • 添加脚本命令
  1. // ...
  2. "scripts": {
  3. // ...
  4. "prepare": "husky install"
  5. },
  6. // ...
  • 执行npm run prepare命令,根目录会多一个.husky的目录
  • 在终端输入命令

就可以当我们在使用 git commit 代码提交的时候会先触发以下命令进行代码格式化修复

  1. npm husky add .husky/pre-commit "npm run lint:fix && npm run stylelint:fix"
  • 输入完后在.husky文件下会出现pre-commit文件

Commitlint

  • 安装Commitlint相关依赖
  1. npm install @commitlint/config-conventional @commitlint/cli commitizen@4.2.4 cz-customizable -D
  1. module.exports = {
  2. extends: ['@commitlint/config-conventional'],
  3. rules: {
  4. 'type-enum': [
  5. // type枚举
  6. 2,
  7. 'always',
  8. [
  9. ':sparkles:',
  10. ':bug:',
  11. ':construction:',
  12. ':memo:',
  13. ':lipstick:',
  14. ':zap:',
  15. ':hammer:',
  16. ':white_check_mark:',
  17. ':rewind:',
  18. ':package:',
  19. ':rocket:',
  20. ':construction_worker:',
  21. ],
  22. ],
  23. // 'type-empty': [2, 'never'], // 提交类型不能为空
  24. // 'type-case': [0, 'always', 'lower-case'], // 提交类型的大小写(0表示不检查)
  25. // 'scope-empty': [0], // 提交范围(scope)是否为空(0表示不检查)
  26. // 'scope-case': [0], // 提交范围的大小写(0表示不检查)
  27. // 'subject-empty': [2, 'never'], // 提交说明(subject)不能为空
  28. // 'subject-case': [0], // 提交说明的大小写(0表示不检查)
  29. // 'subject-full-stop': [0, 'never', '.'], // 提交说明的结尾不能有句号
  30. // 'header-max-length': [2, 'always', 72], // 提交信息的头部最长72个字符
  31. // 'body-leading-blank': [0], // 提交信息的主体开头不强制为空行
  32. // 'footer-leading-blank': [0, 'always'], // 提交信息的脚注开头需要有空行
  33. },
  34. parserPreset: {
  35. parserOpts: {
  36. headerPattern: /^(:\w*:)(?:\((.*?)\))?\s((?:.*(?=\())|.*)(?:\(#(\d*)\))?/,
  37. headerCorrespondence: ['type', 'scope', 'subject', 'ticket'],
  38. },
  39. },
  40. }
  1. module.exports = {
  2. types: [
  3. {
  4. value: ':sparkles: feat',
  5. name: '✨ feat: 新功能',
  6. },
  7. {
  8. value: ':bug: fix',
  9. name: '🐛 fix: 修复bug',
  10. },
  11. {
  12. value: ':construction: WIP',
  13. name: '🚧 WIP: 工作进行中',
  14. },
  15. {
  16. value: ':memo: docs',
  17. name: '📝 docs: 文档变更',
  18. },
  19. {
  20. value: ':lipstick: style',
  21. name: '💄 style: 代码风格变更',
  22. },
  23. {
  24. value: ':zap: perf',
  25. name: '⚡ perf: 性能优化',
  26. },
  27. {
  28. value: ':hammer: refactor',
  29. name: '🔨 refactor: 重构',
  30. },
  31. {
  32. value: ':white_check_mark: test',
  33. name: '✅ test: 测试',
  34. },
  35. {
  36. value: ':rewind: revert',
  37. name: '⏪️ revert: 代码回退',
  38. },
  39. {
  40. value: ':package: build',
  41. name: '📦 build: 打包构建',
  42. },
  43. {
  44. value: ':rocket: chore',
  45. name: '🚀 chore: 构建/工程依赖/工具',
  46. },
  47. {
  48. value: ':construction_worker: ci',
  49. name: '👷 CI 配置变更',
  50. },
  51. ],
  52. scopes: [],
  53. scopeOverrides: {},
  54. // override the messages, defaults are as follows
  55. messages: {
  56. type: '请选择提交类型(必填):',
  57. scope: '请输入文件修改范围(可选):',
  58. // used if allowCustomScopes is true
  59. customScope: '请输入文件修改范围(可选):',
  60. subject: '请简要描述提交(必填):\n',
  61. body: '请输入详细描述,使用 "|" 实现换行输入(可选):\n',
  62. breaking: '列出所有BREAKING CHANGES(可选):\n',
  63. footer: '请输入要关联的 YouTrack Issue ID,例如: #5, #30 (可选):\n',
  64. confirmCommit: '确定提交此说明吗?',
  65. },
  66. // 跳过空的 scope
  67. skipEmptyScopes: false,
  68. skipQuestions: ['breaking', 'body'],
  69. // 设置为 true,在 scope 选择的时候,会有 empty 和 custom 可以选择
  70. // 顾名思义,选择 empty 表示 scope 缺省,如果选择 custom,则可以自己输入信息
  71. allowCustomScopes: true,
  72. // 只有我们 type 选择了 feat 或者是 fix,才会询问我们 breaking message.
  73. allowBreakingChanges: ['feat', 'fix'],
  74. }
  • package.json
  1. "scripts": {
  2. // ...
  3. "commit": "git-cz"
  4. },
  5. "config": {
  6. "commitizen": {
  7. "path": "./node_modules/cz-customizable"
  8. },
  9. "cz-customizable": {
  10. "config": ".cz-config.cjs"
  11. }
  12. }
  • 添加commit-msg钩子

会验证你输入的commit信息是否符合规范

  1. npm husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
  • 输入完后在.husky文件下会出现commit-msg文件

lint-staged

  • 安装lint-staged相关依赖
  1. npm install lint-staged -D
  • 在根目录新增文件.lintstagedrc
  1. {
  2. "*.{js,jsx,ts,tsx}": [
  3. "npm run lint:fix"
  4. ],
  5. "*.{css,scss}": [
  6. "npm run stylelint:fix",
  7. "git add"
  8. ]
  9. }
  • 或者package.json下新增
  1. "lint-staged": {
  2. "*.{js,jsx,ts,tsx}": [
  3. "npm run lint:fix"
  4. ],
  5. "*.{css,scss}": [
  6. "npm run stylelint:fix",
  7. "git add"
  8. ]
  9. }
  • 修改.husky/pre-commit文件
  1. #!/usr/bin/env sh
  2. . "$(dirname -- "$0")/_/husky.sh"
  3. npx lint-staged

准备完毕

  • 这时候可以使用npm commit进行代码提交

Axios请求的二次封装

  • 安装依赖
  1. npm i axios
  • app文件夹中新建http文件夹,然后在http文件夹下新建request.js文件用于存放axios的二次封装
  1. import axios from 'axios';
  2. // BASE_URL
  3. const BASE_URL = 'https://strapi.wininfluencer.com/api';
  4. // 请求头 - Authorization
  5. const AUTHORIZATION =
  6. 'Bearer 514be30c52acc904c0474108388b9f8b4c00c66f543d011ad3b9f8d2c0a98d2fe79faf517779c10876234c36f44ee374ffdefbe1c1e114278ba183f8a1d8b27f304bee80108d7934db81cd647419b1a8d14f400a706f6f233303745485bc5e403bf21a66d373c8db80db5f2bc48311f4f9c4d36b49dd37732916f75e84ae3c43';
  7. const service = axios.create({
  8. timeout: 60 * 1000,
  9. baseURL: BASE_URL,
  10. headers: {
  11. 'Content-Type': 'application/json',
  12. Authorization: AUTHORIZATION,
  13. },
  14. });
  15. // axios实例拦截请求
  16. service.interceptors.request.use(
  17. (config) => {
  18. // 这里可以对config请求体数据进行处理
  19. return config;
  20. },
  21. (error) => {
  22. // 排除错误
  23. return Promise.reject(error);
  24. },
  25. );
  26. // axios实例拦截响应
  27. service.interceptors.response.use(
  28. (response) => {
  29. return response.data;
  30. },
  31. // 请求失败
  32. (error) => {
  33. return Promise.reject(error);
  34. },
  35. );
  36. // 此处相当于二次响应拦截
  37. // 为响应数据进行定制化处理
  38. const requestInstance = (config) => {
  39. return new Promise((resolve, reject) => {
  40. service
  41. .request(config)
  42. .then(({data}) => {
  43. // 成功直接返回数据
  44. if (data.result === 'success') {
  45. resolve(data);
  46. } else {
  47. resolve(data);
  48. }
  49. })
  50. .catch((error) => {
  51. reject(error);
  52. });
  53. });
  54. };
  55. // GET请求
  56. export function get(url, parms, config = {}) {
  57. return requestInstance({url, method: 'GET', params: parms, ...config});
  58. }
  59. // POST请求
  60. export function post(url, data, config = {}) {
  61. return requestInstance({url, method: 'POST', data, ...config});
  62. }
  63. // PUT请求
  64. export function put(url, data, config = {}) {
  65. return requestInstance({url, method: 'PUT', data, ...config});
  66. }
  67. // DELETE请求
  68. export function del(url, data, config = {}) {
  69. return requestInstance({url, method: 'DELETE', data, ...config});
  70. }
  • http文件夹下新建homepage.js文件用于存放首页的一些请求

这里最好进行模块化 account collections blogs 分别创建不同的文件以更好的区分

  1. import {get, post, put, del} from '~/http/request';
  2. /**
  3. * 定义接口示例
  4. * 遵循RESFUL API规范
  5. */
  6. const services = {
  7. // GET请求
  8. getXxxXxx(params) {
  9. return get('/xxxx/xxxx', params, {});
  10. },
  11. // POST请求
  12. postXxxXxx(data) {
  13. return post('/xxxx/xxxx', data, {});
  14. },
  15. // PUT请求
  16. putXxxXxx(data) {
  17. return put('/xxxx/xxxx', data, { });
  18. },
  19. // DELETE请求
  20. delXxxXxx(data) {
  21. return del('/xxxx/xxxx', data, {});
  22. },
  23. };
  24. export default services;
  • 使用:引用刚刚创建的homepage.js文件
  1. import {defer, redirect} from '@shopify/remix-oxygen';
  2. import httpHomepage from '~/http/homepage';
  3. export async function loader(args) {
  4. // 举例
  5. const dataResult = await httpHomepage.getXxxXxx({key: 'value'})
  6. return defer({dataResult})
  7. }
  8. export default function Homepage() {
  9. const data = useLoaderData();
  10. // 打印结果
  11. console.log('data====>', data.dataResult);
  12. return (
  13. <div className="home">
  14. {/* ...... */}
  15. </div>
  16. );
  17. }

Fetch 请求二次封装

  • app文件夹中新建http文件夹,然后在http文件夹下新建request.js文件用于存放axios的二次封装
  1. // BASE_URL
  2. const BASE_URL = 'https://strapi.wininfluencer.com/api';
  3. // 请求头 - Authorization
  4. const AUTHORIZATION =
  5. 'Bearer 514be30c52acc904c0474108388b9f8b4c00c66f543d011ad3b9f8d2c0a98d2fe79faf517779c10876234c36f44ee374ffdefbe1c1e114278ba183f8a1d8b27f304bee80108d7934db81cd647419b1a8d14f400a706f6f233303745485bc5e403bf21a66d373c8db80db5f2bc48311f4f9c4d36b49dd37732916f75e84ae3c43';
  6. // 封装的Fetch请求函数
  7. export const customFetch = async ({ url, params, method, data, config }) => {
  8. const options = {
  9. method,
  10. body: data,
  11. headers: {
  12. 'Content-Type': 'application/json',
  13. Authorization: AUTHORIZATION
  14. },
  15. ...config
  16. };
  17. // 对GET请求的参数进行拼接处理
  18. if (method === 'GET' && params) {
  19. const urlSearchParams = new URLSearchParams(params);
  20. const queryString = urlSearchParams.toString(); // 输出 "a=xxx&b=yyy"
  21. // 兼容http开头的请求
  22. if (!(url.includes('http://') || url.includes('https://'))) {
  23. url = url + '?' + queryString;
  24. }
  25. }
  26. // 如果是POST请求,将数据转换成JSON字符串
  27. if (data) {
  28. options.body = JSON.stringify(data);
  29. }
  30. // 最终请求的url
  31. const fetchUrl = BASE_URL + url;
  32. return fetch(fetchUrl, options)
  33. .then((response) => {
  34. if (!response.ok) {
  35. throw new Error(`${fetchUrl} HTTP error! Status: ${response.status}`);
  36. }
  37. return response.json();
  38. })
  39. .catch((error) => {
  40. console.error('Fetch error:', error);
  41. throw error;
  42. });
  43. };
  44. // GET请求
  45. export function get(url, params, config = {}) {
  46. return customFetch({ url, method: 'GET', params, config });
  47. }
  48. // POST请求
  49. export function post(url, data, config = {}) {
  50. return customFetch({ url, method: 'POST', data, config });
  51. }
  52. // PUT请求
  53. export function put(url, data, config = {}) {
  54. return customFetch({ url, method: 'PUT', data, config });
  55. }
  56. // DELETE请求
  57. export function del(url, data, config = {}) {
  58. return customFetch({ url, method: 'DELETE', data, config });
  59. }
  • http文件夹下新建homepage.js文件用于存放首页的一些请求

这里最好进行模块化 account collections blogs 分别创建不同的文件以更好的区分

  1. import {get, post, put, del} from './request';
  2. /**
  3. * 定义接口示例
  4. * 遵循RESFUL API规范
  5. */
  6. const services = {
  7. // GET请求
  8. getXxxXxx(params) {
  9. return get('/xxxx/xxxx', params, {});
  10. },
  11. // POST请求
  12. postXxxXxx(data) {
  13. return post('/xxxx/xxxx', data, {});
  14. },
  15. // PUT请求
  16. putXxxXxx(data) {
  17. return put('/xxxx/xxxx', data, { });
  18. },
  19. // DELETE请求
  20. delXxxXxx(data) {
  21. return del('/xxxx/xxxx', data, {});
  22. },
  23. };
  24. export default services;
  • 使用:引用刚刚创建的homepage.js文件
  1. import {defer, redirect} from '@shopify/remix-oxygen';
  2. import httpHomepage from '~/http/homepage';
  3. export async function loader(args) {
  4. // 举例
  5. const dataResult = await httpHomepage.getXxxXxx({key: 'value'})
  6. return defer({dataResult})
  7. }
  8. export default function Homepage() {
  9. const data = useLoaderData();
  10. // 打印结果
  11. console.log('data====>', data.dataResult);
  12. return (
  13. <div className="home">
  14. {/* ...... */}
  15. </div>
  16. );
  17. }

README文档说明撰写规范

Shopify Hydrogen前端代码开发规范工程化 - 图3

一、 项目说明

  • 项目名:XXXX
  • 简要说明:xxxxxxxxxxxxxxxx

二、关联文档

  • PRD:
  • 原型图:
  • 设计图:
  • 设计文档:
  • 接口文档:
  • 需求文档:
  • gitee地址:
  • shopify后台:
……

三、使用技术

3.1 主要技术栈

  • Remix、React、TypeScript

3.2 使用到的第三方库

第三方库 版本 用途
axios 1.6.8 常用于发送 HTTP 请求和处理响应数据
xxx x.x.x xxxxxxx

四、开发步骤

4.1 环境准备

4.2 项目启动

五、 部署构建流程

5.1 分支说明

5.2 CI/CD说明

六、 其他说明

七、其他参考文档/链接

目录统一规范

Shopify Hydrogen前端代码开发规范工程化 - 图4

公共组件封装编写规范

Shopify Hydrogen前端代码开发规范工程化 - 图5

参考资料