react 项目创建

255 阅读6分钟

准备

项目地址,项目会持续进行更新,文档会和项目同步更新

项目初始化

全局安装 react 的脚手架

npm i -g create-react-app

使用脚手架创建项目,语言使用ts

create-react-app react-demo  --template typescript

使用eject命令,将所有的配置导出,可以进行项目的自定义操作,记住操作是不可逆的

 npm run eject

启动项目

npm start

项目创建完成

将项目推到远端的git 仓库

echo "# react-demo" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/XXX/react-demo.git
git push -u origin main

组件库

先安装一个antd的组件库

npm install antd --save

React Router 安装命令如下。

  npm install react-router-dom

注意如果在 web 上的话,你需要的是 react-router-dom 而不是 react-router 这个包。它们的区别是,后者包含了 react-native 中需要的一些组件,如果你只需要做网页应用的话,用前者就可以了

项目接入规范

ESlint

其中ESlint可以保证我们的代码质量,可以检查我们的代码质量并给出最终的报告。

当前的项目主要使用采用 eslint-config-airbnb 来配置,我们风格和他们保持一致,这份规范是使用度比较高的配置。

安装相关的依赖包,我们说明一下这些包的作用

  • eslint-plugin-import:此插件主要为了校验 import/export 语法,防止错误拼写文件路径以及导出名称的问题
  • eslint-plugin-react:校验 React
  • eslint-plugin-react-hooks:根据 Hooks API 校验 Hooks 的使用
npm i -D eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks

具体的.eslintrc.js配置如下

module.exports = {
  // 为我们提供运行环境,一个环境定义了一组预定义的全局变量
  env: {
    browser: true,
    es6: true,
  },
  // 一个配置文件可以被基础配置中的已启用的规则继承。
  extends: ['airbnb'],
  // 自定义全局变量
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly',
    _: true,
    $: true,
  },
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
  // ESLint 默认使用Espree作为其解析器,你可以在配置文件中指定一个不同的解析器
  // "parser": "@typescript-eslint/parser",
  // 配置解析器支持的语法
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  // ESLint 支持使用第三方插件。在使用插件之前,你必须使用 npm 安装它。
  // 在配置文件里配置插件时,可以使用 plugins 关键字来存放插件名字的列表。插件名称可以省略 eslint-plugin- 前缀。
  plugins: [
    'react',
    // "@typescript-eslint"
  ],
  // ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则。要改变一个规则设置,你必须将规则 ID 设置为下列值之一:
  // "off" 或 0 - 关闭规则
  // "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  // "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
  rules: {
    semi: 0,
    'no-unused-vars': [
      1,
      {
        vars: 'all',
        args: 'after-used',
        ignoreRestSiblings: true,
        varsIgnorePattern: '^_',
        argsIgnorePattern: '^_|^err|^ev', // _xxx, err, error, ev, event
      },
    ],
    'no-useless-escape': 2,
    quotes: [0, 'double'],// 项目默认使用双引号
    // 解决eslint 的一些报错问题
    'import/extensions': [
      'error',
      'ignorePackages',
      {
        js: 'never',
        jsx: 'never',
        ts: 'never',
        tsx: 'never',
      },
    ],
    'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', '.tsx'] }],
    'react/function-component-definition': 'off', // 规则禁用
    'object-curly-newline': 'off', // 规则禁用
  },
};

在package.json 中我们可以配置相关的命令脚本,当执行脚本的时候全局执行代码检查,判断哪些代码是存在问题的。

Prettier

我们使用ESlint 来保证代码质量,需要使用Prettier来保证代码的风格,我们也需要安装相关的包,来完成项目配置。

npm i -D prettier eslint-config-prettier eslint-plugin-prettier

我们需要在.eslintrc.js中需要添加文件的内容,兼容这部分的内容:

"extends": [
   "airbnb",
   "plugin:prettier/recommended"
]

增加 prettier 配置文件,在根目录创建 .prettierrc.js :

module.exports = {
  printWidth: 120, //一行的字符数,如果超过会进行换行,默认为80
  tabWidth: 2, //一个tab代表几个空格数,默认为2
};

提交代码

我们需要保证项目的规范确实使用,我们需要配置husky,lint-staged,保证项目在提交到git 上面的时候是正确的,我们也需要安装这包。

  • husky,一款管理 git hooks 的工具,以此来增强git功能
  • commitlint,一款 git commit 提交规范检验工具,结合commit-msg钩子,实现提交规范的校验
  • lint-staged,一个对 git暂存区 代码进行处理的工具,结合pre-commit钩子,实现对暂存区文件的校验

husky

当前使用的husky为9版本,安装和使用方法和之前稍显不同:

npm install husky -D

执行命令如下

npx husky init

添加钩子:

echo "npm test" > .husky/pre-commit

commitlint

我们需要安装,御用对提交信息的 message 规范进行校验。

  • @commitlint/config-conventional 是基于 conventional commits 规范的配置文件。
  • @commitlint/cli 是 commitlint 工具的核心。
npm install -D commitlint @commitlint/cli @commitlint/config-conventional

对于进行配置,增加 .commitlintrc.js 配置文件

module.exports = {
    extends: ['@commitlint/config-conventional']
};

然后结合husky,实现git commit的规范验证,执行命令:

echo "npx --no-install commitlint -e $HUSKY_GIT_PARAMS" > .husky/commit-msg

这个时候会在.husky/commit-msg的文件中的内容会变成如下的内容

npx --no-install commitlint -e 

lint-staged

npm install lint-staged -D

package.json添加如下的配置:

{
   "scripts": [
       "lint-staged": "lint-staged"
   ],
   "lint-staged": {
       "*.{js,ts,jsx,tsx}": [
           "eslint --fix"
       ]
   },
}

结合husky,实现对暂存区文件的校验

echo "npm run lint-staged" > .husky/pre-commit

其中当前的文件.husky/pre-commit的内容为

npm run lint-staged

接入react-router

文档参考,比较好的诠释了react-router6的特性

先进行最简单的路由实践,我们在app.tsx 文件中只做最简单的路由实践:Route 组件用于将应用的位置映射到不同的 React 组件,要在所有 Route 都不匹配时就渲染 404 页面,只需将 404 页面对应的 Route 的 path 设置为 *

import React from 'react';
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import About from './pages/About';
import Test from './pages/Test';
import './App.css';

const App = () => (
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<About />} />
      <Route path="/Test" element={<Test />} />
    </Routes>
  </BrowserRouter>
);
export default App;

最新的版本我们可以使用useRoutes的方式来定义路由,具体的写法如下:

import React from 'react';
import About from '../pages/About';
import About1 from '../pages/About1';
import Test from '../pages/Test';
import { useRoutes } from 'react-router-dom';
import routes from './router/router';
import './App.css';

const routes = [
  { path: '/about', element: <About /> },
  {
    path: '/test',
    element: <Test />,
  },
  {
    path: '/About1',
    element: <About1 />,
  },
];

const App = () => {
  const routesShow = useRoutes(routes);

  return <div>{routesShow}</div>;
};
export default App;

当我们需要点击的时候进行路由跳转的时候,代码如下:


import React from 'react';
import { Badge } from 'antd';
import { useNavigate } from 'react-router-dom';

const HomePage = () => {
  const navigate = useNavigate();
  const handleRouterGo = (key: string) => {
    navigate('/test'); // 输入path
  };
  return (
    <div className="homePageWrapper">
     <div className="desc-item" key={item.key} onClick={() => handleRouterGo(item.key)}/>
  );
};

export default HomePage;

代码片段

我在写函数组件的时候,比较喜欢使用箭头函数的,但是现在市面上的代码片段,函数的声明方式都是使用function,所以我决定自己来写一些简单的代码片段,推荐一个网站,这个网站方便我们写代码片段,由于书写代码片段很麻烦,每一行都要加双引号这些,所以推荐一个网站,将需要变为代码片段的代码转化为这种格式。 为项目写的配置文件如下:

{
  "new function component": {
    "prefix": "nfc",
    "body": [
      "import React, { useState } from 'react';",
      "import './index.scss';",
      "",
      "const page = () => {",
      "  const [state, setState] = useState(0);",
      "  return <div className=\"\"></div>;",
      "};",
      "",
      "export default page;",
      "",
    ],
    "description": "new function component",
  },
}

配置别名

在webpack的配置文件中添加配置

配置文件路径

    alias: {
        '@': path.resolve(__dirname, '../src'),
        '@BusinessComponent': path.resolve(__dirname, '../src/BusinessComponent'), // 新增加的配置
    }

但是由于我们使用的语言是TypeScript语言,导致我们还需要在tsconfig.json 中进行相关的配置,解决配置的问题,我们的配置很简单

  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@BusinessComponent/*": [
        "./src/BusinessComponent/*"
      ]
    },
  }

这样我们就解决了我们在import 的时候引用的路径过长的问题。

在线编辑

因为是组件库,所以我们需要实现组件在线编辑,而不是像antd一样需要跳转到sandbox才可以完成编辑,所以我们需要一个轻量级的编辑组件。先简单的写一个最简单的例子,不考虑样式,对于组件的封装如下:

import React from 'react';
import * as antd from 'antd';
import { LiveProvider, LiveEditor, LivePreview } from 'react-live';
import './index.scss';

const scope = { ...antd };

const Editor = (props: any) => {
  const { code } = props;
  return (
    <div className="live-card">
      <LiveProvider code={code} scope={scope} noInline>
        <div className="preview">
          <LivePreview />
        </div>
        <LiveEditor className="font-mono" />
      </LiveProvider>
    </div>
  );
};

export default Editor;

我们传入的方式如下:

import React, { useState } from 'react';
import Editor from 'src/BusinessComponent/Editor';
import './index.scss';

const jsxExample = `

const ButtonShow = () => {
  return (
    <div >
        <Button >999</Button>
    </div>
  );
};
render(<ButtonShow></ButtonShow>)
`;
const ButtonShow = () => {
  const [state, setState] = useState(0);
  return (
    <div className="">
       <Editor code={jsxExample} />
    </div>
  );
};

export default ButtonShow;

接入单元测试

Jest的官网路径 我们使用jest进行函数的单元测试,使用断言等方式对组件进行自动化测试,并生成测试报告。我使用的脚手架已经提供了jest大家若是没有jest,可以自己安装一下,安装命令如下:

   npm install jest -D

我们可以在util中进行一些简单的公共方法的测试,验证公共方法的正确性,后续在修改公共方法的时候,可以跑一下测试用例,保证边界值的正确性。

// math.test.ts
import { add } from './math';
describe('测试add', function () {
  test('测试1+1', function () {
    expect(add(1, 1)).toBe(2);
  });
  test('测试2+2', function () {
    expect(add(2, 2)).toBe(4);
  });
  test('测试2+9', function () {
    expect(add(2, 2)).toBe(4);
  });
});

公共方法的写法:

// math.test.ts
const add = (a: number, b: number) => {
  return a + b;
};
export { add };

在package.json 配置跑的命令如下:

"scripts": {
  "test": "node scripts/test.js",
  "coverage": "node scripts/test.js --coverage", // 查看测试报告,查看测试用例覆盖率等指标
}
类型说明
line coverage行覆盖率
function coverage函数覆盖率
branch coverage分支覆盖率
statement coverage语句覆盖率

我们需要配合UI进行单元测试,所以我们还需要一些其他的库进行配合单元测试,之前使用的单元测试的包为enzyme,现在我们一般使用@testing-library/react进行测试,假设我们对公共的业务组件进行测试,具体的流程如下:

我们具体的Header组件的代码如下:

import React from 'react';
import { useNavigate } from 'react-router-dom';
import imgURL from '../../assets/header.png';
import gitUrl from '../../assets/git.jpeg';
import { tabConfig } from './config';
import './index.scss';

const Header = () => {
  const navigate = useNavigate();

  const handleClick = (url: string) => {
    navigate(url);
  };

  const gotoGitPage = () => {
    window.open('https://github.com/KimQueen/react-project');
  };
  return (
    <div className="warpContent-header">
      <div className="left">
        <img src={imgURL} alt="图标" className="imgStyle" />
        <div className="title">Component</div>
      </div>
      <div className="content" />
      <div className="right">
        {tabConfig.map((item) => (
          <div key={item.name} onClick={() => handleClick(item.url)} className="tabItem">
            {item.name}
          </div>
        ))}
        <div onClick={() => gotoGitPage()}>
          <img src={gitUrl} className="gitImg" alt="git" />
        </div>
      </div>
    </div>
  );
};

export default Header;

我们需要对于这个组件进行快照抓取,我们具体的header.test.js如下:


import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import Header from '../Header';

const mockUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockUsedNavigate,
}));

describe('Header', () => {
  test('renders Header component, give snapshots', () => {
    const { asFragment } = render(<Header />);
    expect(asFragment()).toMatchSnapshot();
  });
});

写好后我们再跑test的命令得到的快照如下:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Header renders Header component 1`] = `
<DocumentFragment>
  <div
    class="warpContent-header"
  >
    <div
      class="left"
    >
      <img
        alt="图标"
        class="imgStyle"
        src="header.png"
      />
      <div
        class="title"
      >
        Component
      </div>
    </div>
    <div
      class="content"
    />
    <div
      class="right"
    >
      <div
        class="tabItem"
      >
        首页
      </div>
      <div
        class="tabItem"
      >
        关于
      </div>
      <div
        class="tabItem"
      >
        联系我们
      </div>
      <div>
        <img
          alt="git"
          class="gitImg"
          src="git.jpeg"
        />
      </div>
    </div>
  </div>
</DocumentFragment>
`;

react-testing-library官方网址相关文档可以再这里进行查看