从零开始搭建React项目

5,606 阅读12分钟

从零开始搭建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. 项目创建成功后,使用yarnnpm 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抽离出来进行修改。
  1. 执行 npm run eject (此操作是不可逆的!弹出配置后,你将无法跟随官方的脚步去升级项目的 react-script 版本)
  2. 执行完成后,项目目录中会多出两个文件夹config和scripts
  3. 在config中找到webpack.config.js文件
  4. 同方法一操作即可

当前更多项目通过此模式进行开发

  • 方法三:使用 craco 覆盖webpack配置
  1. 使用yarn add @craco/craco命令安装依赖
  2. 项目根路径下创建craco.config.js文件
  3. 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

路由有常用两种路由模式可选:HashRouterBrowserRouter,BrowseRouter相对于HashRouter使用HTML5的history API,保证UI界面和URL同步的同时支持使用location.state和location.key进行页面之间传值。所以这里我们使用BrowserRouter。

  1. 当我们没有公共导航栏或者菜单栏时,一般使用一级路由即可。
// 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>
);
  1. 当需要有公共导航栏或者菜单栏时,可使用二级路由。

二级路由需要配合使用 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;

image.png

高效开发

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;

image.png

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();
}