手摸手,打造属于自己的 React 组件库01 — 基础篇

3,539 阅读6分钟

第一部分 - 基础篇:从 0 到 1,还原一个后台项目

原文链接

组件库现在由我们团队在维护,欢迎围观,Start ~

引言

2020年,作为一名普通前端打字员,我平时工作的主要目标还是使用 React 构建应用程序。去年一直想建立一个属于自己的 NPM 库,用于沉淀自己的业务组件、Hooks,但是由于很多概念都不是很了解的缘故,又感觉这个目标有点遥不可及。

但是其实只要明白一些原理和细节之后,构建自己的 NPM 库其实是一件很简单的事情。不仅可以拓宽视野,还可以将自己在多个项目中反复使用的某些相同的组件编译到一个组件库中,推广给其他小伙伴使用,贡献开源,岂不美哉。

教程部分

本篇文章,是这个系列的第一篇:基于 React 和 Antd,从 0 到 1,还原一个后台项目

技术栈

让我们看看在 2020 年,一个普通的后台项目,需要的技术栈有哪些:

  • React:最流行的 UI 层 JavaScript 库
  • Antd:西湖区最流行的 UI 设计语言
  • Create React App:官方 Cli 脚手架
  • React-Router:处理路由
  • TypeScript:9012 年成长最快的前端编程语言,提供了类型系统对 ES6 的支持
  • eslint & prettier & husky:整合代码规范,统一编码风格。
  • less:参考 Antd 文档,配合 react-app-rewired 等库让我们的构建工具支持 less。

项目准备

具体实现,可以参考这个:Demo 项目

预览地址:jokingzhang.github.io/dantd-demo/

使用 React + TypeScript 开始项目

此处首先确定下,我们已经安装了 npm 模块,没有安装的童鞋可以去 Node 官网 下载一个安装包安装一下,然后你就有 npm 了😑。

执行下面的命令,初始化项目:

sudo npm i -g npx
npx create-react-app my-app --template typescript
cd my-app
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
npm start

这个时候,应该可以看到一个经典的初始化页面了~

安装 Antd

npm install antd --save

安装 React Router

npm install --save react-router-dom @types/react-router-dom

使用 eslint & prettier & husky 整合代码规范,统一编码风格

安装依赖
npm install -D eslint eslint-config-prettier eslint-config-react-app eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks lint-staged prettier husky

依赖安装好之后,就可以为项目增加一些配置文件了

tsconfig.json

tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项。这是当前项目所用到的配置文件:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es6",
      "dom"
    ],
    "downlevelIteration": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "sourceMap": true,
    "baseUrl": "./src",
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node"
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "src"
  ]
}
.eslintrc

.eslintrc 文件中,允许你指定你想要支持的 JavaScript 语言选项。这是当前项目所用到的配置文件:

{
    "extends": "react-app",
    "plugins": ["prettier"],
    "rules": {
      "prettier/prettier": "error",
      "no-unused-vars": "off",
      "@typescript-eslint/no-unused-vars": ["error", {
        "vars": "all",
        "args": "after-used",
        "ignoreRestSiblings": false
      }]
    }
}
.eslintignore

你可以通过在项目根目录创建一个 .eslintignore 文件告诉 ESLint 去忽略特定的文件和目录。.eslintignore 文件是一个纯文本文件,其中的每一行都是一个 glob 模式表明哪些路径应该忽略检测。

node_modules/*
.prettierrc.js

prettierrc 的配置文件,打造适合你们团队的代码配置

module.exports = {
  singleQuote: true,
  trailingComma: 'all',
  printWidth: 100,
  proseWrap: 'never',
  overrides: [
    {
      files: '.prettierrc',
      options: {
        parser: 'json',
      },
    },
  ],
};
.vscode/settings.json

如果你是使用 Visual Studio Code 来开发项目的话,这个配置文件就可以方便的统一团队的编辑器配置,解决项目中,缩进是「2个空格」还是「4个空格」的问题。

{
  "editor.tabSize": 2,
  "eslint.autoFixOnSave": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    { "language": "typescript", "autoFix": true },
    { "language": "typescriptreact", "autoFix": true }
  ],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}
.env

当我们搜索 create-react-app 是如何快速解决 alias 的问题时,Dan 给出的答案是添加 .env 这个文件:

NODE_PATH=src

到这里,就整理完了所有需要的配置文件

使用 Less

更加详细的解析,请参考 Antd 的文档

安装下面依赖:

npm i -D react-app-rewired customize-cra babel-plugin-import less less-loader
修改 package.json
/* package.json */
"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
}
config-overrides.js

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。

const { override, fixBabelImports, addLessLoader } = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: true,
  }),
  addLessLoader({
    javascriptEnabled: true,
  }),
);

到此,我们就可以在项目中,愉快的使用 less 了~

项目结构

此时,使用编辑器打开项目,我们可以看到如下的 src 目录结构:

├── src
|  ├── App.css
|  ├── App.test.tsx
|  ├── App.tsx
|  ├── index.css
|  ├── index.tsx
|  ├── logo.svg
|  ├── react-app-env.d.ts
|  ├── serviceWorker.ts
|  └── setupTests.ts

这是脚手架为我们准备的初始目录结构。接下来,我们需要在这个基础上,进行一些改造。以一个最为简单的 EmptyLine 组件为例子,看看我们需要增加哪些文件。

处理路由

首先,增加两个文件夹 , pages 用来存放路由相关的组件,以及components 用来存放通用组件。
此处不做赘述,感兴趣的小伙伴可以看下 demo 的实现

此时,代码结构如下:

├── src
|  ├── App.less
|  ├── App.test.tsx
|  ├── App.tsx
|  ├── components
|  |  ├── empty-line
|  |  |  ├── EmptyLine.tsx
|  |  |  ├── demo.tsx
|  |  |  ├── index.tsx
|  |  |  └── style
|  |  └── index.tsx
|  ├── config.tsx
|  ├── index.css
|  ├── index.tsx
|  ├── logo.svg
|  ├── pages
|  |  ├── Component
|  |  |  ├── index.tsx
|  |  |  └── style.less
|  |  ├── Home
|  |  |  ├── index.tsx
|  |  |  └── style.less
|  |  └── NotFound
|  |     └── index.tsx
|  ├── react-app-env.d.ts
|  ├── serviceWorker.ts
|  └── setupTests.ts

pages 下面的目录结构比较容易理解,只存放了组件,以及样式文件。

components 下的目录结构,主要参考了 Antd 的源码实现。这样写的有如下好处:

  • 分离了样式组件,结构清晰
  • 代码结构更加清晰,便于之后组件的导出
  • 由于我们使用 babel-plugin-import 来进行按需加载 Antd,当我们想使用 ES module 的方式引入组件的时候,它会对我们 es 目录下的文件有所要求:

下面摘自 babel-plugin-import 的文档:

  • { "libraryName": "antd" }

    import { Button } from 'antd'; ReactDOM.render(xxxx);

      ↓ ↓ ↓ ↓ ↓ ↓
    

    var _button = require('antd/lib/button'); ReactDOM.render(<_button>xxxx</_button>);

  • { "libraryName": "antd", style: "css" }

    import { Button } from 'antd'; ReactDOM.render(xxxx);

      ↓ ↓ ↓ ↓ ↓ ↓
    

    var _button = require('antd/lib/button'); require('antd/lib/button/style/css'); ReactDOM.render(<_button>xxxx</_button>);

  • { "libraryName": "antd", style: true }

    import { Button } from 'antd'; ReactDOM.render(xxxx);

      ↓ ↓ ↓ ↓ ↓ ↓
    

    var _button = require('antd/lib/button'); require('antd/lib/button/style'); ReactDOM.render(<_button>xxxx</_button>);

所以,不论之后时使用 commonjs 的方式打包,还是使用 es 的方式打包,组件的目录结构最好和 Antd 的组件目录结构保持一致,否则 babel-plugin-import 这个插件会找不到对应的文件所在。

增加第一个组件

现在,我们可以开始写我们的第一个组件了,<EmptyLine />:产生一个空行,并且可以根据 props 改变高度。

src/index.tsx
export { default as EmptyLine } from './empty-line';
src/empty-line/index.tsx
import './style/index.less';
import EmptyLine from './EmptyLine';

export default EmptyLine;
src/empty-line/EmptyLine.tsx
import React from 'react';
import './style/index.less';

export interface IEmptyLineProps {
  height?: number;
}

const EmptyLine = ({ height = 20 }: IEmptyLineProps) => {
  return <div className="empty-line" style={{ height }} />;
};

export default EmptyLine;
src/empty-line/style/index.less
.empty-line {
    width: 100%;
    height: 20px;
}
src/empty-line/demo.tsx
import React from 'react';
import EmptyLine from './EmptyLine';

const demo = () => {
  return (
    <div>
      <h3>组件名称:空行(EmptyLine)</h3>
      <p>自定义组件,默认高度 20 ,宽度 100%</p>
      <p>第一行文字</p>
      <EmptyLine />
      <p>第二行文字</p>
    </div>
  );
};

export default demo;

到这里,组件就完成了,这是:预览地址

image

结束语

我们已经从头开始创建了 React 组件库!在接下来的章节中,还会和大家讨论测试,和打包的话题,敬请期待吧~