react及其生态简介
这篇文章的主要目的是介绍react社区中各种插件/包,并为创建一个react应用提供相应的建议,由于整篇文章是综述性质的,因此不会过多涉及每个插件/包的高级用法,我们在这里只展示初级用法,如果你想要更进一步,你可以阅读本文的推荐阅读或直接去官网看文档
本文主要会涉及到以下内容
- redux / mobx
- react-query
- clean code
- eslint / husky / jest
- a clear project structure
- css预处理器 less / scss / tailwind
初始化代码环境
由于react只是一个JavaScript library,因此我们需要别的包配合以构建一个完整的react应用,框架在这一过程中起的是减轻开发者初始化代码环境负担的作用
目前比较主流的react框架有如下几种
- create-react-app
- next.js
- vite
每种框架都有自己的优点× ,在这里我们用create-react-app初始化代码环境
初始化的一些指令在此就不赘述了,在初始化完之后,我们首先来修改src文件夹下的文件结构
我认为一个清晰的文件结构对于开发是有很大帮助的
scr文件夹下可以分成以下几个目录
- assets,存放项目中用到的所有静态资源文件
- components,存放可以被复用的组件
- hooks,存放自定义的hook函数
- routes,应该是有关路由的文件夹,内部应该会和protect routes等有关(但我还没学×)
- store,使用redux / mobx等全局状态管理工具时需要的store
- test,存放单元测试文件的地方
- types,自定义的可以复用的各种type
- utils,自定义的纯函数
- pages,页面文件夹
- config,一些全局常量,如请求的server名
对于pages文件夹,里面的每一个page都应该由以下部分组成
- index.tsx
- components
- api,存放各种封装好的请求的文件夹
- types
- style,存放样式文件(但用tailwind就不用这个目录了×
为什么pages里面有types却没有hooks / utils呢
从我个人的开发经验来看,自定义的hook / util的复用频率较高且数量较少,为了文件目录的简洁,所以放在了全局目录下
在建立好文件目录之后,我们需要做的是配置各种开发插件,这些插件对于规范代码质量有着很重要的作用,包括不局限于:
- husky
- eslint
- jest
eslint
首先我们来初始化eslint,执行以下命令:
npm install eslint --save-dev./node_modules/.bin/eslint --init- 在package.json的scripts中添加:
{
"lint": "eslint --ext .js,.jsx,.ts,.tsx src/",
"lint-fix": "eslint --ext .js,.jsx,.ts,.tsx src/ --fix"
}
之后你就可以通过npm run lint来检查你的代码格式,使用npm run lint -- --fix来自动修复格式问题
如果你需要自定义eslint检查的内容,你可以编辑.eslintrc.js文件,大部分规则定义在rules字段中,下面是一个例子
{
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "double"]
}
}
"semi"和"quotes"是规则名,"error"代表将规则视为一个错误,可选的别的字段还有"off","warn",分别代表将这个规则关掉或视为warning
husky
下一步是使用husky配置规范的git commit流程
执行以下命令安装husky:
npx husky-initnpm installnpx huksy installnpm set-script prepare "husky install"- 执行完上述命令后可以看到项目根目录下出现
.husky/pre-commit文件,由于我们之前配置了eslint,修改.husky/pre-commit中的npm run test为npm run lint,这样,当你执行git时,husky会自动执行npm run lint
之后创建用于检查commit-msg的文件,并在husky上添加对应检查规则
npm install @commitlint/cli @commitlint/config-conventionalecho "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.jsnpx husky add .husky/commit-msg "npm run commitlint"
为commitlint.config.js添加以下内容
/* eslint-disable no-undef */
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [ // type枚举
2, "always",
[
"build", // 编译相关的修改,例如发布版本、对项目构建或者依赖的改动
"feat", // 新功能
"fix", // 修补bug
"docs", // 文档修改
"style", // 代码格式修改, 注意不是 css 修改
"refactor", // 重构
"perf", // 优化相关,比如提升性能、体验
"test", // 测试用例修改
"revert", // 代码回滚
"ci", // 持续集成修改
"config", // 配置修改
"chore", // 其他改动
],
],
"type-empty": [2, "never"], // never: type不能为空; always: type必须为空
"type-case": [0, "always", "lower-case"], // type必须小写,upper-case大写,camel-case小驼峰,kebab-case短横线,pascal-case大驼峰,等等
"scope-empty": [0],
"scope-case": [0],
"subject-empty": [2, "never"], // subject不能为空
"subject-case": [0],
"subject-full-stop": [0, "never", "."], // subject以.为结束标记
"header-max-length": [2, "always", 72], // header最长72
"body-leading-blank": [0], // body换行
"footer-leading-blank": [0, "always"], // footer以空行开头
}
};
之后每次执行git commit时,husky都会执行npm run commitlint检查你的提交规范,一次成功的提交示例如下:
jest
下一步是配置jest,单元测试对于维护开发的安全性&稳定性有着很大的作用
Jest是Facebook开源的一套JavaScript测试框架, 它集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具。
执行以下步骤以初始化jest
npm install --save-dev jest- 在package.json中修改
scripts:{"test": "jest",} - 在.eslintrc.js中添加
"env":{"jest/globals": true}
至此,你可以运行npm test来运行你的所有jest.js文件
下面提供一个样例:
在test目录下新建一个example文件夹,在example文件夹中新建sum.js与sum.jest.js
// sum.js
function sum(a,b) {
return a+b;
}
module.exports = sum;
// sum.jest.js
const sum = require("./sum");
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
运行npm test后你可以看到
css* 预处理器*
在完成各种插件的配置后,另一个重要的问题是选用什么样的css预处理器(现在一般很少有直接写css的了吧×)
目前主流的css预处理器有:
- less
- scss
- tailwind
由于我完全不了解scss,在这里仅介绍一下less和tailwind
less
less: Less (Leaner Style Sheets 的缩写) 是一门向后兼容的 CSS 扩展语言。这里呈现的是 Less 的官方文档(中文版),包含了 Less 语言以及利用 JavaScript 开发的用于将 Less 样式转换成 CSS 样式的 Less.js 工具。
less增加了嵌套,混合,变量等一系列特性,使用less可以大大简化你的样式文件的代码量
tailwind
tailwind是一个功能优先的css框架,通过tailwind,你可以在html中书写样式代码,同时,tailwind规定了一系列预先设置好的className,使用tailwind可以使你不用在css文件和jsx文件之间不停切换,这样就可以大大节省开发时间以及代码量,但是由于tailwind内部是集成了一系列预先设置好的样式,当项目需要复杂的css动画效果时,tailwind可能不会是一个很好的选择
对于create-react-app创建的应用来说,由于tailwind官网的引入教程有一些typo,我在这里列一下正确的引入步骤,建议读者同时参考在这里列的步骤以及官网引入教程
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^8 autoprefixer@^9npm install @craco/craco --legacy-peer-depsnpx tailwindcss-cli@latest init
craco.config.js文件中需要将postcss改为postcssOptions
例子:
在tailwind中,你只需要下面三行代码即可创建一个垂直居中的黑色矩形
<div className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
<div className="w-10 h-10 rounded-lg bg-black flex justify-center items-center"/>
</div>
react-query
在前端应用中,网络请求是一个很重要的部分,react库本身并没有提供一个很好的请求方式,因此选择一个优秀的网络请求工具对于后续代码质量&简化有着很重要的作用
在传统的react应用中,一种普遍的发起请求的方式是通过useEffect获取数据(这大概是由于请求是异步的,我们需要在请求完成后重新setState并更新组件,同时还要避免无限重渲染),但是这样就会带来很多问题,举例而言,你需要怎么处理请求异常?如果需要展示loading效果你又应该怎么写请求?你需要怎么处理请求结果的缓存?
react-query被提出用于解决这一系列问题,通过使用react-query,你可以以简洁的代码完成上述的一系列功能,当然,它的功能不仅限于此,更多的用处你可以查阅官网文档
这里是react-query的一个sample:
const { isLoading, error, data } = useQuery(["repoData"], () =>
fetch("https://api.github.com/repos/tannerlinsley/react-query").then(
(res) => res.json(),
),
);
if (isLoading) return "Loading...";
if (error) return "An error has occurred: " + error.message;
状态管理
状态管理也是构建前端应用中很重要的一部分,我们都知道react应用会形成一个组件树,参数只能在这个组件树上从上到下流动(当然也有一些办法可以完成子组件到父组件的通信),如果没有一个全局的状态管理器的话,假设我们需要从树的一个子组件传递参数到它的兄弟节点,我们得遵循:子组件->父组件->子组件的顺序传递参数,这种写法无疑是非常冗长的
redux / mobx被提出用于解决这个问题
redux
redux的设计模式类似于发布-订阅模式,所有全局数据&逻辑被保存在一个对象中,用户使用action修改对象内的状态,当对象内状态发生改变时,redux会告知订阅者状态发生了改变
redux主要有以下几个核心概念:
-
action:包含type属性和payload属性,描述动作相关信息
-
reducer:定义如何响应不同的action
-
store:管理action以及reducer,提供以下功能
- 监听action的分发(dispatch(action))
- 支持订阅store的变更(subscribe(listener))
- 由于redux对store状态的变更都是通过action触发,对于异步任务,我们需要引入redux-thunk等插件来保证不将业务或者数据相关的任务混入react组件中
另外我们还可以关注一下redux-toolkit,这是redux官方推出的为了简化redux使用的一个库,在redux-toolkit中,我们可以简单的通过useSelector()获取store中的数据,通过useDispatch(action)来修改store中的数据
mobx
mobx则是利用es6的proxy来追踪属性,通过隐式订阅,自动追踪被监听的对象变化
相对于redux,我个人认为mobx最大的优点是实现了数据局部更新
就使用上来说,你可以通过mobx的useObserver()或<Observer/>来局部监听数据变化,
而对应到redux-toolkit上来说,每次useSelector的数据的变化都会引起当前组件的刷新
clean code
好的编程远不止是掌握语言的机制。最重要的是,需要记住程序员创建程序是为其他人将来阅读的。好的程序反映了问题陈述及其中的重要概念,它带有简洁的自我描述。示例说明了此描述,并将其与需要解决的问题联系起来。这些示例还确保未来的读者知道代码的工作原理和方式。
这一部分是对于代码格式的一些建议,包括但不仅限于变量命名,函数命名,注释格式等
变量
- 使用meaningful的变量名
bad: const yyyymmdstr = moment().format("YYYY/MM/DD");
good: const currentDate = moment().format("YYYY/MM/DD");
- 不要使用magic number
bad:
// What the heck is 86400000 for?
setTimeout(blastOff, 86400000);
good:
// Declare them as capitalized named constants.
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
setTimeout(blastOff, MILLISECONDS_PER_DAY);
- 使用易于理解的变量名(在forEach等中)
bad:
[1,2,3,4].forEach(i => {
//...
})
good:
[1,2,3,4].forEach(item => {
// replace item with other meaningful name related to origin array
})
- 如果你的class/object名表明了一些事情,不要在变量名中重复
bad:
const Car = {
carColor:"blue"
}
good:
const Car = {
color:"blue"
}
- 在函数中使用default parameters
bad:
function tutorialExample(param) {
const tmpParam = param || "default param";
}
good:
function tutorialExample(param = "default param") {
//...
}
函数
- 限制函数的参数在两个及以下,当参数数量过多时,将他们写在一个对象里
- 一个函数只能做一件事情,在函数中尽可能避免if等条件语句
- 函数的名字应该说明这个函数的用处
- 尽可能减少重复的代码,提高函数的可复用性
- 尽可能避免Side Effects,一个函数应当尽可能只做到接受一个值并返回一个值
Bad:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
Good:
function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};
参考
如果你在配置本文涉及到的插件时遇到问题,可以参考https://github.com/biu9/sample-react-app这个仓库的配置
子页面目录
暫時無法在飛書文件外顯示此內容