从零开始搭建React项目
创建项目
1. 使用npm install -g create-react-app
命令全局安装react脚手架。
2. 通过脚手架npx create-react-app my-app
创建react项目,也可使用npx create-react-app my-app --template typescript
创建基于TypeScript的react项目。
- 在使用旧的 create-react-app 包创建项目时,控制台会有以下提示:
A template was not provided. This is likely because you're using anoutdated version of create-react-app.
Please note that global installs of create-react-app are no longer supported.
- 使用
npm uninstall -g create-react-app
卸载老版本,再重新安装脚手架即可。
3. 项目创建成功后,使用yarn
或npm i
安装依赖即可运行。
提一嘴,关于npm和yarn的知识,也可以深入了解一下哦,里面的学问也是很深的,这里推荐一篇文章《字节的一个小问题 npm 和 yarn不一样吗?》
精简项目
1.删除多余文件
新建项目含有一些初始的文件,我们可以删除这些用不到的文件,删除结果前后对比如下结构:
├─ node_modules>
├─ public
| ├─ favicon.ico
| ├─ index.html
- | ├─ logo192.png
- | ├─ logo512.png
- | ├─ mainfest.json
- | └─ robots.txt
├─ src
- | ├─ App.css
| ├─ App.tsx
- | ├─ App.test.tsx
- | ├─ index.css
| ├─ index.tsx
- | ├─ logo.svg
- | ├─ reportWebVitals.ts
| ├─ react-app-env.d.ts
- | └─ setupTests.ts
├─ package-lock.json
├─ package.json
├─ README.md
├─ tsconfig.json
└─ yarn.lock
这里拓展一下一些文件的作用哦:
-
manifest.json:Web 应用程序清单,在 JSON 文本文件中提供有关应用程序的信息(例如名称、作者、图标和描述)。清单的目的是将 Web 应用程序安装到设备的主屏幕上,为用户提供更快的访问和更丰富的体验。
-
robots.txt:告诉搜索引擎抓取工具(蜘蛛)禁止或允许抓取网站的哪些内容。主流搜索引擎(包括 Google、Bing 和 Yahoo)都能够识别并尊重 Robots.txt的要求。
-
App.test.tsx:可以通过
@testing-library/react
等测试库,jTest自动化测试。 -
reportWebVitals.ts:通过
web-vitals
获取当前项目网页的性能指标:1.LCP (Largest Contentful Paint):最大内容渲染时间;2.FID (First Input Delay):首次输入延迟;3.CLS (Cumulative Layout Shift) :累计布局偏移;4.FCP(First Contentful Paint)首次内容绘制;
// 将create-react-app项目中的index.ts文件最后一行改为如下代码,即可采取到性能指标数据
reportWebVitals(console.log);
-
setupTests.ts:用于编写此项目测试代码,可直接删除。
-
react-app-env.d.ts:类型声明文件,告诉编译器在编译过程中要引入的额外的文件。如果引入某些文件未能成功找到时,在这里添加对应类型可能得到解决
// 三斜线引用,告诉编译器在编译过程中要引入的额外的文件
/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />
// 引入图片地址
declare module '*.png' {
const src: string;
export default src;
}
2.修改代码
因为删除了本地文件,此时项目会因找不到依赖报错,所以修改一下文件即可:
文件src/App.tsx
function App() {
return (
<div className="App">
<h1>Hello,World!</h1>
</div>
);
}
export default App;
文件src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
文件public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
根据安装的react版本不同,以上文件的内容会有所出入,不过主要目的就是删除不需要的本地文件引入即可。
必要的配置
1.配置TypeScript
最新的 React/TypeScript
会自动生成 tsconfig.json
,并且默认带有一些最基本的配置。这里大家可以查阅《TypeScript 中文手册》,配置适合自己的tsconfig。
{
"compilerOptions": {
"baseUrl": "./", // 路径配置
"paths": {
"@": ["src"],
"@/*": ["src/*"]
},
"target": "es5", // 指定 ECMAScript 版本
"lib": [
"dom",
"dom.iterable",
"esnext"
], // 要包含在编译中的依赖库文件列表
"allowJs": true, // 允许编译 JavaScript 文件
"skipLibCheck": true, // 跳过所有声明文件的类型检查
"esModuleInterop": true, // 禁用命名空间引用 (import * as fs from "fs") 启用 CJS/AMD/UMD 风格引用 (import fs from "fs")
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
"strict": true, // 启用所有严格类型检查选项
"forceConsistentCasingInFileNames": true, // 禁止对同一个文件的不一致的引用。
"noFallthroughCasesInSwitch": true, // 报告switch语句的fallthrough错误。(即,不允许switch的case语句贯穿)
"module": "esnext", // 指定模块代码生成
"moduleResolution": "node", // 使用 Node.js 风格解析模块
"resolveJsonModule": true, // 允许使用 .json 扩展名导入的模块
"isolatedModules": true, // 将每个文件作为单独的模块
"noEmit": false, // 不输出(意思是不编译代码,只执行类型检查)
"jsx": "react-jsx",
"noUnusedLocals": true, // 报告未使用的本地变量的错误
"noUnusedParameters": false, // 报告未使用参数的错误
"experimentalDecorators": true, // 启用对ES装饰器的实验性支持
},
"include": [
"src" // TypeScript文件应该进行类型检查
],
"exclude": ["node_modules", "build"] // 不进行类型检查的文件
}
2.统一代码风格prettier
Prettier
侧重代码格式化检查,使用yarn add -D prettier
命令安装prettier,声明.prettierignore
和.prettierrc.js
两个文件,前者表示忽略不需要去检查的目录,后者定义prettier的规则。具体的规则可以参考《Prettier文档》
- 文件 .prettierignore:
# Ignore artifacts:
node_modules
dist
.prettierignore
- 文件 .prettierrc.js:
module.exports = {
printWidth: 120, // 指定编译器换行的行长
tabWidth: 2, // 指定每个缩进空格数
semi: true, // 在语句的末尾输入分号
singleQuote: true, // 使用单引号而不是双引号
trailingComma: 'none', // 在多行逗号分隔的句法结构中尽可能输入尾随逗号
bracketSpacing: true, // 在对象字面量中的括号之间输入空格
jsxBracketSameLine: true, // 将多行 JSX 元素的 > 放在最后一行的末尾,而不是单独放在下一行
arrowParens: 'always', // 在唯一的箭头函数参数周围包含括号
useTabs: false, // 使用制表符而不是空格缩进行
ignorePath: '.prettierignore',
};
每个人的书写规范使得配置都有所不同,按照自己团队的规范来编写就好了。
3.样式文件代码质量检查Stylelint
首先介绍这里安装的四个插件及其用处
-
tylelint-config-standard:官网提供的 css 标准
-
stylelint-config-recess-order:用来指定样式排序,比如声明的块内(插件包)属性的顺序,例如:先写定位,再写盒模型,再写内容区样式,最后写 CSS3 相关属性。
-
stylelint-prettier:基于
prettier
代码风格的stylelint
规则 -
stylelint-config-prettier: 禁用所有与格式相关的 Stylelint 规则,解决 prettier 与 stylelint 规则冲突,确保将其放在
extends
队列最后,这样它将覆盖其他配置(可以使用自己喜欢的可共享配置,而不会在使用pettier 时妨碍其风格选择)
使用命令yarn add -D stylelint stylelint-config-standard stylelint-config-rational-order stylelint-prettier stylelint-config-prettier
安装如上依赖,项目根路径创建.stylelintrc.js
文件进行配置。具体的规则可以参考《Stylelint文档》
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-rational-order', 'stylelint-prettier/recommended'],
rules: {
'block-no-empty': true, // 禁止出现空块
'declaration-empty-line-before': 'never', // 要求或禁止在声明语句之前有空行。
'declaration-block-no-duplicate-properties': true, // 在声明的块中中禁止出现重复的属性
'shorthand-property-no-redundant-values': true, // 禁止在简写属性中使用冗余值。
'color-hex-length': 'short', // 指定十六进制颜色是否使用缩写
'color-named': 'never', // 要求 (可能的情况下) 或 禁止使用命名的颜色
'comment-no-empty': true, // 禁止空注释
'font-family-name-quotes': 'always-unless-keyword', // 指定字体名称是否需要使用引号引起来 | 期待每一个不是关键字的字体名都使用引号引起来
'font-weight-notation': 'numeric', // 要求使用数字或命名的 (可能的情况下) font-weight 值
'no-descending-specificity': null // 禁止低优先级的选择器出现在高优先级的选择器之后
}
};
4.决代码质量的利器eslint
ESLint
是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误——就是一个代码检查工具。
介绍下常用的插件及其用处:
-
@typescript-eslint/parser:代替eslint默认的解析器 Espree 对 TypeScript 的进行解析
-
eslint-plugin-react:检测和规范React代码的书写的插件
-
@typescript-eslint/eslint-plugin:包含了各类定义好的检测Typescript代码的规范
-
eslint-plugin-import:ES2015 +(ES6 +)导入/导出语法的检查
-
eslint-import-resolver-webpack:使 eslint 识别 webpack 配置,或者使用eslint-import-resolver-alias
-
babel-eslint:使 eslint 支持有效的 babel 代码
-
eslint-plugin-prettier:基于
prettier
代码风格的eslint
规则 -
eslint-config-prettier: 禁用所有与格式相关的 eslint 规则,解决 prettier 与 eslint 规则冲突,确保将其放在
extends
队列最后,这样它将覆盖其他配置。
老规矩使用yarn add -D eslint eslint-plugin-react @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import babel-eslint eslint-plugin-prettier eslint-config-prettier
命令安装eslint和需要的插件。
然后声明一个.eslintrc.js
文件用于配置我们的eslint规则:
module.exports = {
parser: '@typescript-eslint/parser', // 定义ESLint的解析器
extends: ['plugin:prettier/recommended'], //定义文件继承的子规范
plugins: ['@typescript-eslint', 'eslint-plugin-react'], //定义了该eslint文件所依赖的插件
env: {
//指定代码的运行环境
browser: true,
node: true
},
settings: {
//自动发现React的版本,从而进行规范react代码
react: {
pragma: 'React',
version: 'detect'
}
},
parserOptions: {
//指定ESLint可以解析JSX语法
parser: 'babel-eslint',
ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
rules: {
'prettier/prettier': ['error', { endOfLine: 'auto' }],
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }], // 允许使用短路、三目
'func-names': ['error', 'as-needed'], // 需要时添加函数名称
'no-param-reassign': ['error', { props: false }], // 函数形参可修改
'react/jsx-uses-react': 'off', // React 17及以后引入了新的 JSX 编译方式,无须在组件中显式地 import React,可关闭
'react/react-in-jsx-scope': 'off',
'no-plusplus': 'off',
'no-shadow': 'off',
}
};
5.配置@路径别名简化路径处理
这里提供三种方法配置项目根路径供参考
- 方法一:直接修改node-modules包中的
webpack.config.js
文件
alias: {
'@': path.resolve('src'),
......
}
// 或
const { appSrc } = require('./paths');
alias: {
'@': appSrc,
......
}
- 方法二:我们通过
create-react-app
脚手架搭建的项目其中的webpack配置文件都是被封装起来的,项目中我们需要使用@src
配置根路径,从而方便使用绝对路径的话,就需要去把webpack抽离出来进行修改。
- 执行
npm run eject
(此操作是不可逆的!弹出配置后,你将无法跟随官方的脚步去升级项目的react-script
版本) - 执行完成后,项目目录中会多出两个文件夹config和scripts
- 在config中找到
webpack.config.js
文件 - 同方法一操作即可
当前更多项目通过此模式进行开发
- 方法三:使用
craco
覆盖webpack配置
- 使用
yarn add @craco/craco
命令安装依赖 - 项目根路径下创建craco.config.js文件
- craco.config.js写入配置,并修改package.json的scripts配置
// craco.config.js
const path = require('path')
module.exports = {
// webpack 配置
webpack: {
// 配置别名
alias: {
// 约定:使用 @ 表示 src 文件所在路径
"@": path.resolve(__dirname, "src"),
}
}
}
// package.json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
6.使用React-Router V6配置路由
- 安装React-Router V6路由
npm: npm install react-router-dom@6
yarn: yarn add react-router-dom@6
路由有常用两种路由模式可选:HashRouter
和 BrowserRouter
,BrowseRouter相对于HashRouter使用HTML5的history API,保证UI界面和URL同步的同时支持使用location.state和location.key进行页面之间传值。所以这里我们使用BrowserRouter。
- 当我们没有公共导航栏或者菜单栏时,一般使用一级路由即可。
// index.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from '@/App';
import Profile from '@/pages/Profile';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
{/* 可使用<Link to={'/'}></Link>标签跳转 */}
<Route element={<App />} path="/"></Route>
{/* 也可navigate = useNavigate()后navigate('/profile')跳转 */}
<Route element={<Profile />} path="/profile"></Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
);
- 当需要有公共导航栏或者菜单栏时,可使用二级路由。
二级路由需要配合使用 Outlet
组件,此组件是一个占位符,告诉 React Router嵌套的内容应该放到哪里。
// index.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from '@/App';
import Profile from '@/pages/Profile';
import Home from '@/pages/Home';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route element={<App />} path="/">
// 使用index属性设置默认展示内容
<Route index element={<Home />}></Route>
<Route element={<Profile />} path="/profile"></Route>
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
);
// App.tsx
import { Outlet } from 'react-router-dom';
import CommonHeader from "@/pages/CommonHeader.js";
function App() {
return (
<div className="App">
<CommonHeader />
<Outlet />
</div>
);
}
export default App;
7.支持less
为什么使用CSS Modules:
CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。产生局部作用域的唯一方法,就是使用一个独一无二的class
的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。
-
安装相应的依赖
yarn add less less-loader -D
-
增加less文件的正则校验
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
+ const lessRegex = /\.less$/;
+ const lessModuleRegex = /\.module\.less$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
-
修改对应的webpack配置,搜索sassRegex和sassModuleRegex,复制sass的配置将
sass
修改为less
后添加在后面(支持通过两种方式引入,一种是lessRegex,一种是lessModuleRegex的方式) -
别忘了在
react-app-env.d.ts
中声明less类型
declare module '*.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
此时我们在项目可以通过两种形式引入样式文件
-
import '@/.../styles.less'
,该方法引入不具备less module能力,可能存在污染样式的风险。 -
import styles from '@/.../styles.less'
,具备less module能力。
如下图:
import '@/pages/Home/styles.less';
import styles from '@/pages/Home/styles.module.less';
function Home() {
return (
<div className="Home">
<h1 className={styles.title}>Home</h1>
<h1 className="title">Home</h1>
</div>
);
}
export default Home;
高效开发
1.合理运动svg
在项目中我们常常会使用到一些图标,相对于<img src="" />
标签引入和iconfont等,svg通过代码编写具有以下优点:
1. 具有分辨率无关性和无限可伸缩性。
2. SVG是完全可编辑和可脚本编写的。
3. 体积更小,Web性能更强。
使用过程中,只需要用记事本打开name.svg复制其中的svg代码即可
例如以下例子:
// svgComponents.tsx
export const AddSvg = ({ width = '24px', height = '24px', ...props }: React.SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill={props.color}
{...props}>
<path d="M874.325333 758.229333h0.277334v-1.578666l-0.277334 1.578666zM927.850667 512c0-229.333333-186.581333-415.850667-415.850667-415.850667C282.666667 96.149333 96.149333 282.688 96.149333 512c0 229.290667 186.538667 415.829333 415.850667 415.829333 229.269333 0 415.850667-186.538667 415.850667-415.829333M512 868.181333c-196.373333 0-356.16-159.829333-356.16-356.181333 0-196.373333 159.786667-356.16 356.16-356.16 196.352 0 356.16 159.786667 356.16 356.16 0 196.352-159.808 356.181333-356.16 356.181333m170.389333-225.962666l-128.597333-128.96 128.469333-127.018667a29.717333 29.717333 0 1 0-41.792-42.24l-128.576 127.189333-126.677333-127.061333a29.717333 29.717333 0 0 0-42.069333 41.962667l126.506666 126.890666-127.509333 126.122667a29.653333 29.653333 0 0 0 20.842667 50.794667c7.552 0 15.146667-2.88 20.906666-8.554667l127.701334-126.314667 128.704 129.173334a29.717333 29.717333 0 0 0 42.090666-41.984"></path>
</svg>
);
通过参数控制,我们可以做到控制图标的大小、颜色等。
import { AddSvg } from '@/components/common/svgComponents';
function Home() {
return (
<div>
<AddSvg />
<AddSvg width="48px" height="48px" color="#f40" />
</div>
);
}
export default Home;
2.善用less语法
- less常量
对于每一个产品来说,都有一套自己的UI规范,相应的针对字体、边框、背景色等样式都应有一套统一的代码去维护。
创建variable.less文件用于存放常用less常量:
// 背景颜色
@bgColor-f0: #fff;
@bgColor-f2: #f2f2f2;
// 字体颜色
@color-19: #191919;
// 字体
@objectSans: 'Object Sans';
@objectSansBold: 'Object Sans Bold';
当我们需要使用时,仅需要使用变量名即可:
@import url('@/utils/styles/variable.less');
.title {
color: @color-19;
font-family: @objectSans;
}
- 混入
相对于前者储存常用的less常量,现在就是封装常用的less函数,避免过多的代码冗余。
创建mixin.less文件用于存放常用less函数:
@import url('./variable.less');
.flex(@x:center;@y:center) {
display: flex;
justify-content: @x;
align-items: @y;
}
.font(@size: 28px,@wight: 'normal',@family: @objectSans) {
font-size: @size;
font-weight: @wight;
font-family: @family;
}
当我们需要使用时,仅需要调用函数根据场景传参即可:
reference 在less中,@import声明会根据引入的文件的后缀进行相应的解析。当使用该关键字后:导入外部文件,导入的样式不会添加到编译输出,除非该样式被引用。
@import (reference) url('@/utils/styles/mixin.less');
.title {
color: @color-19;
.font();
}