背景说明
随着业务的增长和开发团队的成员快速增加,其中很多新人来自于五湖四海各大门派,在编码的风格和习惯中也出现各异。通常在相互 code review 时发现很多代码上的问题,久而久之代码出现了代码难以维护的问题,甚至还会出现低级错误。
格式化代码规范
第三方库版本
环境准备前端代码开发规范工程化
- node >= v20.0.0
- vite >= 5.2.0
- vscode 插件
- ESLint
- Stylelint
- Prettier - Code formatter
初始化Remix 项目
- 创建一个新的 Hydrogen 店面,可以使用官方提供的脚手架
npm create @shopify/hydrogen@latest -- --quickstart
- 可以看到项目已经默认集成了Remix、Vite、Eslint等等
- 根目录下创建.vscode文件夹
- extensions.json - 用于存放推荐vscode扩展插件
- settings.json - 用于设置vscode的基本配置(项目配置会覆盖本地全局配置)
ESLint
ESLint 能够帮助开发者发现代码中的潜在错误和不良实践,确保代码符合团队或项目的编码规范,从而提高代码质量和可维护性。
这是使用的版本是eslint v8的版本,和v9的版本有一定的区别
- 配置写入(保存的时候会自动修复)
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
},
}
- 提供的脚手架已经有安装了相关的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 规则能够正确处理你的别名配置。
npm install eslint-import-resolver-alias eslint-plugin-import -D
- 在
.eslintrc.cjs
文件中配置相关配置
module.export = {
// ...
extends: [
// ...
"plugin:import/recommended",
],
settings: {
'import/resolver': {
// eslint-import-resolver-typescript 可以解决别名报错的问题
typescript: {
alwaysTryTypes: true,
},
// eslint-import-resolver-alias 可以解决绝对路径的问题
alias: {
map: [
['', './public'], // <-- this line
],
extensions: ['.js', '.jsx', '.ts', '.tsx', '.svg'],
},
},
},
rules: {
//...
"import/order": [
"error",
{
// 对导入模块进行分组,分组排序规则如下
groups: [
"builtin", // 内置模块
"external", // 外部模块
"parent", //父节点依赖
"sibling", //兄弟依赖
"internal", //内部引用
"index", // index文件
"type", //类型文件
"unknown",
],
//通过路径自定义分组
pathGroups: [
{
pattern: "@/**", // 把@开头的应用放在external分组后面
group: "external",
position: "after",
},
],
// 是否开启独特组,用于区分自定义规则分组和其他规则分组
distinctGroup: true,
// 每个分组之间换行
"newlines-between": "always",
// 相同分组排列规则 按字母升序排序
alphabetize: { order: "asc" },
},
],
},
};
- 在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"
- 安装prettier相关依赖
Prettier 是一个代码格式化工具,旨在自动化地格式化代码,使其符合一致的风格标准。它支持多种语言,包括 JavaScript、TypeScript、HTML 和 CSS 等。
eslint-config-prettier 是一个 ESLint 配置,它用于禁用与 Prettier 冲突的 ESLint 规则,确保 ESLint 和 Prettier 可以和平共处。
eslint-plugin-prettier 是一个 ESLint 插件,它将 Prettier 的格式化规则作为 ESLint 规则运行,确保代码在保存时自动格式化。
npm install prettier eslint-config-prettier eslint-plugin-prettier -D
- 添加
.prettierrc.cjs
文件
module.exports = {
// 一行最多多少个字符
printWidth: 90,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: false,
// 在语句末尾是否需要分号
semi: true,
// 是否使用单引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
//在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
//然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
//对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'crlf',
};
- 修改
.eslintrc.cjs
文件,注入prettier
相关插件和配置 - 添加
.prettierignore
文件
/dist/
/node_modules/
/public/
- 配置好可以看到
_index.tsx
文件里的内容标红了- 使用配置好的脚本
npm run lint:fix
进行修复
- 使用配置好的脚本
- .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
}
- Tip:
.prettierrc.cjs
修改了相关格式化配置规则,ESLint代码不会立即去检查报红,Ctrl+Shift+P 打开命令面板,输入Restart Extension Host
重启扩展宿主,等待加载一会儿,就能同步更新了。也可以暴力直接重启打开vscode软件。 - 疑问
- 为什么有了eslint还使用prettier?
Stylelint
- 安装css预处理器 sass
npm i sass
- 安装相关依赖(这是使用Scss样式预处理器为例)
npm install stylelint stylelint-scss stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-scss stylelint-order -D
- 根目录分别新增
.stylelintignore
、.stylelintrc.cjs
两个文件
# .stylelintignore
# 旧的不需打包的样式库
*.min.css
# 其他类型文件
*.js
*.jpg
*.png
*.eot
*.ttf
*.woff
*.json
# 测试和打包目录
/test/
/dist/
/node_modules/
/lib/
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-standard-scss',
'stylelint-config-recommended-scss',
],
plugins: ['stylelint-scss', 'stylelint-order'],
rules: {
'no-irregular-whitespace': true,
// 指定样式的排序
'order/properties-order': [
[
'content',
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'vertical-align',
'flex',
'flex-grow',
'flex-shrink',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-wrap',
'grid',
'grid-area',
'grid-template',
'grid-template-areas',
'grid-template-rows',
'grid-template-columns',
'grid-row',
'grid-row-start',
'grid-row-end',
'grid-column',
'grid-column-start',
'grid-column-end',
'grid-auto-rows',
'grid-auto-columns',
'grid-auto-flow',
'grid-gap',
'grid-row-gap',
'grid-column-gap',
'gap',
'row-gap',
'column-gap',
'align-content',
'align-items',
'align-self',
'justify-content',
'justify-items',
'justify-self',
'order',
'float',
'clear',
'object-fit',
'overflow',
'overflow-x',
'overflow-y',
'overflow-scrolling',
'clip',
//
'box-sizing',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'border',
'border-spacing',
'border-collapse',
'border-width',
'border-style',
'border-color',
'border-top',
'border-top-width',
'border-top-style',
'border-top-color',
'border-right',
'border-right-width',
'border-right-style',
'border-right-color',
'border-bottom',
'border-bottom-width',
'border-bottom-style',
'border-bottom-color',
'border-left',
'border-left-width',
'border-left-style',
'border-left-color',
'border-radius',
'border-top-left-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'border-bottom-left-radius',
'border-image',
'border-image-source',
'border-image-slice',
'border-image-width',
'border-image-outset',
'border-image-repeat',
'border-top-image',
'border-right-image',
'border-bottom-image',
'border-left-image',
'border-corner-image',
'border-top-left-image',
'border-top-right-image',
'border-bottom-right-image',
'border-bottom-left-image',
//
'background',
'background-color',
'background-image',
'background-attachment',
'background-position',
'background-position-x',
'background-position-y',
'background-clip',
'background-origin',
'background-size',
'background-repeat',
'color',
'box-decoration-break',
'box-shadow',
'outline',
'outline-width',
'outline-style',
'outline-color',
'outline-offset',
'table-layout',
'caption-side',
'empty-cells',
'list-style',
'list-style-position',
'list-style-type',
'list-style-image',
//
'font',
'font-weight',
'font-style',
'font-variant',
'font-size-adjust',
'font-stretch',
'font-size',
'font-family',
'src',
'line-height',
'letter-spacing',
'quotes',
'counter-increment',
'counter-reset',
'-ms-writing-mode',
'text-align',
'text-align-last',
'text-decoration',
'text-emphasis',
'text-emphasis-position',
'text-emphasis-style',
'text-emphasis-color',
'text-indent',
'text-justify',
'text-outline',
'text-transform',
'text-wrap',
'text-overflow',
'text-overflow-ellipsis',
'text-overflow-mode',
'text-shadow',
'white-space',
'word-spacing',
'word-wrap',
'word-break',
'overflow-wrap',
'tab-size',
'hyphens',
'interpolation-mode',
//
'opacity',
'visibility',
'filter',
'resize',
'cursor',
'pointer-events',
'user-select',
//
'unicode-bidi',
'direction',
'columns',
'column-span',
'column-width',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-width',
'column-rule-style',
'column-rule-color',
'break-before',
'break-inside',
'break-after',
'page-break-before',
'page-break-inside',
'page-break-after',
'orphans',
'widows',
'zoom',
'max-zoom',
'min-zoom',
'user-zoom',
'orientation',
'fill',
'stroke',
//
'transition',
'transition-delay',
'transition-timing-function',
'transition-duration',
'transition-property',
'transform',
'transform-origin',
'animation',
'animation-name',
'animation-duration',
'animation-play-state',
'animation-timing-function',
'animation-delay',
'animation-iteration-count',
'animation-direction',
'animation-fill-mode',
],
{
unspecified: 'bottom',
severity: 'error',
},
],
},
}
- 打开命令面板重启一下扩展宿主,可以创建一个css文件,输入相关样式,可以看到文件报红
- 添加脚本命令行
// ...
"scripts": {
// ...
"stylelint": "stylelint ./**/*.{css,scss}",
"stylelint:fix": "stylelint ./**/*.{css,scss} --fix",
},
// ...
- 使用
npm run stylelint:fix
进行样式格式化修复 - .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"stylelint.validate": ["css", "scss", "sass"],
"stylelint.enable": true,
"css.validate": false,
"scss.validate": false,
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
Husky
- 安装依赖
npm install husky@8.0.3 -D
- 添加脚本命令
// ...
"scripts": {
// ...
"prepare": "husky install"
},
// ...
- 执行
npm run prepare
命令,根目录会多一个.husky
的目录 - 在终端输入命令
就可以当我们在使用 git commit 代码提交的时候会先触发以下命令进行代码格式化修复
npm husky add .husky/pre-commit "npm run lint:fix && npm run stylelint:fix"
- 输入完后在
.husky
文件下会出现pre-commit
文件
Commitlint
- 安装Commitlint相关依赖
npm install @commitlint/config-conventional @commitlint/cli commitizen@4.2.4 cz-customizable -D
- 为什么使用指定版本
- 在根目录分别新增
.commitlintrc.cjs
、.cz-config.cjs
两个文件
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
// type枚举
2,
'always',
[
':sparkles:',
':bug:',
':construction:',
':memo:',
':lipstick:',
':zap:',
':hammer:',
':white_check_mark:',
':rewind:',
':package:',
':rocket:',
':construction_worker:',
],
],
// 'type-empty': [2, 'never'], // 提交类型不能为空
// 'type-case': [0, 'always', 'lower-case'], // 提交类型的大小写(0表示不检查)
// 'scope-empty': [0], // 提交范围(scope)是否为空(0表示不检查)
// 'scope-case': [0], // 提交范围的大小写(0表示不检查)
// 'subject-empty': [2, 'never'], // 提交说明(subject)不能为空
// 'subject-case': [0], // 提交说明的大小写(0表示不检查)
// 'subject-full-stop': [0, 'never', '.'], // 提交说明的结尾不能有句号
// 'header-max-length': [2, 'always', 72], // 提交信息的头部最长72个字符
// 'body-leading-blank': [0], // 提交信息的主体开头不强制为空行
// 'footer-leading-blank': [0, 'always'], // 提交信息的脚注开头需要有空行
},
parserPreset: {
parserOpts: {
headerPattern: /^(:\w*:)(?:\((.*?)\))?\s((?:.*(?=\())|.*)(?:\(#(\d*)\))?/,
headerCorrespondence: ['type', 'scope', 'subject', 'ticket'],
},
},
}
module.exports = {
types: [
{
value: ':sparkles: feat',
name: '✨ feat: 新功能',
},
{
value: ':bug: fix',
name: '🐛 fix: 修复bug',
},
{
value: ':construction: WIP',
name: '🚧 WIP: 工作进行中',
},
{
value: ':memo: docs',
name: '📝 docs: 文档变更',
},
{
value: ':lipstick: style',
name: '💄 style: 代码风格变更',
},
{
value: ':zap: perf',
name: '⚡ perf: 性能优化',
},
{
value: ':hammer: refactor',
name: '🔨 refactor: 重构',
},
{
value: ':white_check_mark: test',
name: '✅ test: 测试',
},
{
value: ':rewind: revert',
name: '⏪️ revert: 代码回退',
},
{
value: ':package: build',
name: '📦 build: 打包构建',
},
{
value: ':rocket: chore',
name: '🚀 chore: 构建/工程依赖/工具',
},
{
value: ':construction_worker: ci',
name: '👷 CI 配置变更',
},
],
scopes: [],
scopeOverrides: {},
// override the messages, defaults are as follows
messages: {
type: '请选择提交类型(必填):',
scope: '请输入文件修改范围(可选):',
// used if allowCustomScopes is true
customScope: '请输入文件修改范围(可选):',
subject: '请简要描述提交(必填):\n',
body: '请输入详细描述,使用 "|" 实现换行输入(可选):\n',
breaking: '列出所有BREAKING CHANGES(可选):\n',
footer: '请输入要关联的 YouTrack Issue ID,例如: #5, #30 (可选):\n',
confirmCommit: '确定提交此说明吗?',
},
// 跳过空的 scope
skipEmptyScopes: false,
skipQuestions: ['breaking', 'body'],
// 设置为 true,在 scope 选择的时候,会有 empty 和 custom 可以选择
// 顾名思义,选择 empty 表示 scope 缺省,如果选择 custom,则可以自己输入信息
allowCustomScopes: true,
// 只有我们 type 选择了 feat 或者是 fix,才会询问我们 breaking message.
allowBreakingChanges: ['feat', 'fix'],
}
package.json
"scripts": {
// ...
"commit": "git-cz"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": ".cz-config.cjs"
}
}
- 添加commit-msg钩子
会验证你输入的commit信息是否符合规范
npm husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
- 输入完后在
.husky
文件下会出现commit-msg
文件
lint-staged
- 安装lint-staged相关依赖
npm install lint-staged -D
- 在根目录新增文件
.lintstagedrc
{
"*.{js,jsx,ts,tsx}": [
"npm run lint:fix"
],
"*.{css,scss}": [
"npm run stylelint:fix",
"git add"
]
}
- 或者
package.json
下新增
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"npm run lint:fix"
],
"*.{css,scss}": [
"npm run stylelint:fix",
"git add"
]
}
- 修改.husky/pre-commit文件
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
准备完毕
- 这时候可以使用
npm commit
进行代码提交
Axios请求的二次封装
- 安装依赖
npm i axios
- 在
app
文件夹中新建http
文件夹,然后在http
文件夹下新建request.js
文件用于存放axios的二次封装
import axios from 'axios';
// BASE_URL
const BASE_URL = 'https://strapi.wininfluencer.com/api';
// 请求头 - Authorization
const AUTHORIZATION =
'Bearer 514be30c52acc904c0474108388b9f8b4c00c66f543d011ad3b9f8d2c0a98d2fe79faf517779c10876234c36f44ee374ffdefbe1c1e114278ba183f8a1d8b27f304bee80108d7934db81cd647419b1a8d14f400a706f6f233303745485bc5e403bf21a66d373c8db80db5f2bc48311f4f9c4d36b49dd37732916f75e84ae3c43';
const service = axios.create({
timeout: 60 * 1000,
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
Authorization: AUTHORIZATION,
},
});
// axios实例拦截请求
service.interceptors.request.use(
(config) => {
// 这里可以对config请求体数据进行处理
return config;
},
(error) => {
// 排除错误
return Promise.reject(error);
},
);
// axios实例拦截响应
service.interceptors.response.use(
(response) => {
return response.data;
},
// 请求失败
(error) => {
return Promise.reject(error);
},
);
// 此处相当于二次响应拦截
// 为响应数据进行定制化处理
const requestInstance = (config) => {
return new Promise((resolve, reject) => {
service
.request(config)
.then(({data}) => {
// 成功直接返回数据
if (data.result === 'success') {
resolve(data);
} else {
resolve(data);
}
})
.catch((error) => {
reject(error);
});
});
};
// GET请求
export function get(url, parms, config = {}) {
return requestInstance({url, method: 'GET', params: parms, ...config});
}
// POST请求
export function post(url, data, config = {}) {
return requestInstance({url, method: 'POST', data, ...config});
}
// PUT请求
export function put(url, data, config = {}) {
return requestInstance({url, method: 'PUT', data, ...config});
}
// DELETE请求
export function del(url, data, config = {}) {
return requestInstance({url, method: 'DELETE', data, ...config});
}
- 在
http
文件夹下新建homepage.js
文件用于存放首页的一些请求
这里最好进行模块化 account collections blogs 分别创建不同的文件以更好的区分
import {get, post, put, del} from '~/http/request';
/**
* 定义接口示例
* 遵循RESFUL API规范
*/
const services = {
// GET请求
getXxxXxx(params) {
return get('/xxxx/xxxx', params, {});
},
// POST请求
postXxxXxx(data) {
return post('/xxxx/xxxx', data, {});
},
// PUT请求
putXxxXxx(data) {
return put('/xxxx/xxxx', data, { });
},
// DELETE请求
delXxxXxx(data) {
return del('/xxxx/xxxx', data, {});
},
};
export default services;
- 使用:引用刚刚创建的
homepage.js
文件
import {defer, redirect} from '@shopify/remix-oxygen';
import httpHomepage from '~/http/homepage';
export async function loader(args) {
// 举例
const dataResult = await httpHomepage.getXxxXxx({key: 'value'})
return defer({dataResult})
}
export default function Homepage() {
const data = useLoaderData();
// 打印结果
console.log('data====>', data.dataResult);
return (
<div className="home">
{/* ...... */}
</div>
);
}
Fetch 请求二次封装
- 在
app
文件夹中新建http
文件夹,然后在http
文件夹下新建request.js
文件用于存放axios的二次封装
// BASE_URL
const BASE_URL = 'https://strapi.wininfluencer.com/api';
// 请求头 - Authorization
const AUTHORIZATION =
'Bearer 514be30c52acc904c0474108388b9f8b4c00c66f543d011ad3b9f8d2c0a98d2fe79faf517779c10876234c36f44ee374ffdefbe1c1e114278ba183f8a1d8b27f304bee80108d7934db81cd647419b1a8d14f400a706f6f233303745485bc5e403bf21a66d373c8db80db5f2bc48311f4f9c4d36b49dd37732916f75e84ae3c43';
// 封装的Fetch请求函数
export const customFetch = async ({ url, params, method, data, config }) => {
const options = {
method,
body: data,
headers: {
'Content-Type': 'application/json',
Authorization: AUTHORIZATION
},
...config
};
// 对GET请求的参数进行拼接处理
if (method === 'GET' && params) {
const urlSearchParams = new URLSearchParams(params);
const queryString = urlSearchParams.toString(); // 输出 "a=xxx&b=yyy"
// 兼容http开头的请求
if (!(url.includes('http://') || url.includes('https://'))) {
url = url + '?' + queryString;
}
}
// 如果是POST请求,将数据转换成JSON字符串
if (data) {
options.body = JSON.stringify(data);
}
// 最终请求的url
const fetchUrl = BASE_URL + url;
return fetch(fetchUrl, options)
.then((response) => {
if (!response.ok) {
throw new Error(`${fetchUrl} HTTP error! Status: ${response.status}`);
}
return response.json();
})
.catch((error) => {
console.error('Fetch error:', error);
throw error;
});
};
// GET请求
export function get(url, params, config = {}) {
return customFetch({ url, method: 'GET', params, config });
}
// POST请求
export function post(url, data, config = {}) {
return customFetch({ url, method: 'POST', data, config });
}
// PUT请求
export function put(url, data, config = {}) {
return customFetch({ url, method: 'PUT', data, config });
}
// DELETE请求
export function del(url, data, config = {}) {
return customFetch({ url, method: 'DELETE', data, config });
}
- 在
http
文件夹下新建homepage.js
文件用于存放首页的一些请求
这里最好进行模块化 account collections blogs 分别创建不同的文件以更好的区分
import {get, post, put, del} from './request';
/**
* 定义接口示例
* 遵循RESFUL API规范
*/
const services = {
// GET请求
getXxxXxx(params) {
return get('/xxxx/xxxx', params, {});
},
// POST请求
postXxxXxx(data) {
return post('/xxxx/xxxx', data, {});
},
// PUT请求
putXxxXxx(data) {
return put('/xxxx/xxxx', data, { });
},
// DELETE请求
delXxxXxx(data) {
return del('/xxxx/xxxx', data, {});
},
};
export default services;
- 使用:引用刚刚创建的
homepage.js
文件
import {defer, redirect} from '@shopify/remix-oxygen';
import httpHomepage from '~/http/homepage';
export async function loader(args) {
// 举例
const dataResult = await httpHomepage.getXxxXxx({key: 'value'})
return defer({dataResult})
}
export default function Homepage() {
const data = useLoaderData();
// 打印结果
console.log('data====>', data.dataResult);
return (
<div className="home">
{/* ...... */}
</div>
);
}
README文档说明撰写规范
一、 项目说明
- 项目名: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说明
六、 其他说明
七、其他参考文档/链接
目录统一规范
公共组件封装编写规范
参考资料