阅读 640

「超详细React项目搭建教程三」集成 CSS/Less/Sass/Antd

在上一篇文章中,我们使用 TypeScript/ESLint/Webpack 搭建了一个 React 应用。这篇文章中,我们继续在上一篇文章的基础上加入 CSS/Less/Sass/Antd

在代码中引入 CSS

创建一个src/pages/CssDemo/index.tsx组件并添加一个 CSS 类名

import React from "react";

const Index: React.FC = () => {
  return (
    <div className="container">
      <div>CSSDemo</div>
    </div>
  );
};

export default Index;
复制代码

然后我们在组件的同级目录创建一个 index.css文件并加入以下内容

.container > div {
  font-size: 20px;
  color: blue;

  width: 200px;
  height: 200px;
  border: 1px solid gray;
}
复制代码

通过npm start 启动应用

当然!样式肯定是不会生效的。因为组件根本不知道container是在哪里被定义的。 so!我们需要在组件中导入样式文件模块

import "./index.css";
复制代码

Webpack 会提示错误:"Module parse failed: Unexpected token".错误 ❎ 的原因是我们使用了一个 Webpack 无法解析的文件模块

Webpack无法解析CSS

为开发环境配置 CSS

我们需要让 Webpack 知道如何解析 CSS 文件,因此我们需要在webpack.dev.js中加入两个解析器

const config= {
  ...
  module: {
    rules: [
      ...,
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  ...
};
复制代码

这样配置后,当 Webpack 再遇到.css文件时,它将使用css-loaderstyle-loader进行处理(use 数组中的加载器从后向前执行)。

  • css-loader 在 import 语句(在我们的示例中为app.css)中读取引用的 CSS 文件并解析成 JavaScript 代码。
  • style-loader 将 JavaScript 代码中的 CSS 以 style 标签的形式插入到 html 文件中。

为了使新的 Webpack 配置正常工作,我们需要安装css-loaderstyle-loader

yarn add css-loader style-loader --dev
复制代码

接下来重新启动应用并观察界面,当然!样式生效了!

no-cssModules-demo-right

为生产环境配置 CSS

在生产环境中,需要把 CSS 样式抽离成单独的文件(避免浏览器缓存). 我们可以使用mini-css-extract-plugin代替style-loader来做到这一点。

webpack 官方文档有说:

从 webpack v4 开始,应该使用mini-css-extract-plugin 替换extract-text-webpack-plugin

接下来,我们安装mini-css-extract-plugin及其 TypeScript 类型库

yarn add mini-css-extract-plugin @types/mini-css-extract-plugin --dev
复制代码

然后,我们需要把此 loader 加入到生产环境配置文件中,在webpack.prod.js中修改如下内容

...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const config= {
  ...,
  module: {
    rules: [
      ...,
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  ...,
  plugins: [
    ...,
    new MiniCssExtractPlugin({
      filename: "[name].[contenthash].css",
    }),
  ],
  ...
};
复制代码

mini-css-extract-plugin会将 CSS 样式从 JS 代码中抽离出来并创建.css 文件

如果我们的应用使用了 code split

  • 那么可以使用[name]标志来让 Webpack 命名新的 CSS 文件。
  • 当内容更改时,使用[contenthash]标志更改已打包的新的文件名称,这也可以避免浏览器缓存。

来,我们打一个包看看

yarn run build
复制代码

我们可以看到在 build目录下已经生成了 CSS 文件

build/file.css

然后我们查看 HTML 文件,发现 CSS 文件也被自动引入了

html自动引入/file.css

为多个组件添加样式

接下来我们在cssDemo/index.tsx组件中添加两个子组件,并且导入两个样式文件

import "./heading.css";
import "./content.css";

const App = () => (
  <>
    <Heading />
    <Content />
  </>
);
const Heading = () => <h1 className="heading">My React and TypeScript App</h1>;

const Content = () => <div className="content">With CSS!</div>;
复制代码

两个 CSS 文件内容如下所示

/* heading.css */
.heading {
  color: red;
}

/* content.css */
.content {
  color: green;
}
复制代码

然后通过yanr start启动应用查看界面效果,我们发现样式文件被转换了,变成了两个 style 标签引入到了 HTML 文件中

开发环境下的样式效果

接下来执行yarn run build打包项目,并查看打包后的文件,我们看到 CSS 生成了单独的文件

生产环境下的样式文件效果

是不是很有趣!🙂

使用 CSS modules

到这里,有经验的同学应该会发现一个问题: 开发人员必须仔细命名 CSS 类,才能避免样式名称冲突。 例如,如果我们将两个 CSS 类都称为“text”,那么标题和内容的颜色是什么呢?

样式冲突

第二个 CSS 类的优先级高于第一个 CSS 类,导致两段文本均为绿色。

如何解决呢?

CSS modules 可以通过将 CSS 名称限定到特定组件中来解决样式类名冲突问题

接下来我们CV如下代码实现CSS modules

import heading from "./heading.module.css";
import content from "./content.module.css";
...
const Heading = () => (
  <h1 className={heading.heading}>My React and TypeScript App</h1>
);
const Content = () => <div className={content.content}>With CSS!</div>;
复制代码

import 语句跟原来的略有不同。 我们通过引用.module.css后缀的文件,并从中导入为一个变量

这个变量是一个对象,包含了对应样式文件的所有 CSS 类名称, 然后在组件中引用对应的类名变量

我们还需要将content.css重命名为content.module.css,将heading.css重命名为heading.module.css

但是!TypeScript 编译出现错误 ❎“无法找到模块'.module.css'或对应的类型声明”错误,因为 TS 无法解析CSS modules

TS无法解析CSS modules

为了解决这个错误,我们需要创建一个src/typings.d.ts类型声明文件并加入以下内容

declare module "*.module.css";
复制代码

之后重启应用,再次查看界面效果,可以发现样式按预期的正常显示了

TS正常解析CSS modules

我们看到 CSS 类名称被赋予了一个看起来很随机的名称。 因为这样可以确保不同组件中的样式名称不会冲突。

如果我们还记得在组件中引用类名称的方式,那看起就有些奇怪了:

className={fileName.cssClassName}
复制代码

为什么代码中导入的样式类名与生成后的类名不一样?因为上面的代码其实在通知 Webpack 为类名称赋予全局唯一的名称。

接下来,我们再看看npm run build后的样式文件,我们发现生产环境的样式文件类名也是全局唯一的

生产环境的样式类名

CSS modules 的引用方式优化

每次都需要通过 *.module.css的方式实现 CSS modules 不免有些麻烦。其实,我们可以通过修改 Webpack 配置简化 CSS modules 的写法

修改webpack.dev.js 的配置, CSS modules 官方文档

...
      {
        test: /\.css$/i,
        use: ["style-loader", {
          loader: "css-loader",
          options: {
            modules: true,
          },
        },
        ],
      },
...
复制代码

修改webpack.pro.js 的配置

...
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, {
          loader: "css-loader",
          options: {
            modules: true,
          },
        },],
      },
...
复制代码

修改typings.d.ts 的配置

declare module "*.css";
复制代码

我们还需要将content.module.css重命名为content.css,将heading.module.css重命名为heading.css

修改 App 组件中导入的样式名称

import contentStyle from "./content.css";
import headingStyle from "./heading.css";
复制代码

最后重新启动应用查看界面效果,可以发现与之前的效果一致

写法更简洁了是吧?😯

在项目中配置 Less

真实工作中,我们一般需要一些 CSS 预编译器以提高我们的工作效率,先说如何配置 Less webpack 参考文档

修改webpack.dev.js 配置

      {
        test: /\.less$/i,
        use: [
          {
            loader: "style-loader",
          },
          {
            loader: "css-loader",
            options: {
              modules: true,
            },
          },
          {
            loader: "less-loader",
          },
        ],
      },
复制代码

修改webpack.pro.js 配置

      {
        test: /\.less$/i,
        use: [MiniCssExtractPlugin.loader, {
          loader: "css-loader",
          options: {
            modules: true,
            importLoaders: 1,
          },
        },
        {
          loader: "less-loader",
        },
        ],
      },
复制代码

为了使新的 Webpack 配置正常工作,我们需要安装less-loaderless

yarn add less less-loader --dev
复制代码

注意:还需要在typings.d.ts 中加入以下内容,否则 Typescript 无法识别 Less 类型

declare module "*.less";
复制代码

为组件添加 Less

接下来我们在 src/lessDemo/index.tsx 组件中导入 less 文件

import React from "react";

import style from "./index.less";

const Index: React.FC = () => {
  return (
    <div className={style.container}>
      <div>LessDemo</div>
    </div>
  );
};

export default Index;
复制代码

Less 文件内容如下所示

@color: purple;

.container {
  div {
    font-size: 20px;
    color: @color;
    width: 200px;
    height: 200px;
    border: 1px solid gray;
  }
}
复制代码

然后通过yanr start启动应用查看界面效果,我们发现样式文件被转换了,变成了两个 style 标签引入到了 HTML 文件中

开发环境下的Less样式效果

接下来执行yarn run build打包项目,并查看打包后的文件,我们看到 Less 和其他的 CSS 一起 生成了单独的文件

生产环境下的样式文件效果

是不是很有趣!🙂

在项目中配置 Sass

接下来我们看看如何配置 Sass,其实与 Less 的设置方式是类似的 . [webpack 参考文档][14]

修改webpack.dev.js 配置

      {
        test: /\.s[ac]ss$/i,
        use: [
          //从包含CSS的JS代码中 创建 `style` 节点
          {
            loader: "style-loader",
          },
          // 将 CSS 转换为 CommonJS 格式的JS代码
          {
            loader: "css-loader",
            options: {
              modules: true,
            },
          },
          // 将 Sass 转换为 CSS
          {
            loader: "sass-loader",
          },
        ],
      },
复制代码

修改webpack.pro.js 为如下配置

      {
        test: /\.s[ac]ss$/i,
        use: [MiniCssExtractPlugin.loader, {
          loader: "css-loader",
          options: {
            modules: true,
            importLoaders: 1,
          },
        },
        {
          loader: "sass-loader",
        },
        ],
      },
复制代码

为了使新的 Webpack 配置正常工作,我们需要安装sass-loadersass

yarn add sass sass-loader --dev
复制代码

注意:还需要在typings.d.ts 中加入以下内容,否则 Typescript 无法识别 sass/scss 类型

declare module "*.sass";
declare module "*.scss";
复制代码

为组件添加 Sass

接下来我们为 src/sassDemo/index.tsx 组件入 Sass 样式文件

import React from "react";

import style from "./index.scss";

const Index: React.FC = () => {
  return (
    <div className={style.container}>
      <div>SASSDemo</div>
    </div>
  );
};

export default Index;
复制代码

Ssss 文件内容如下所示

$color: red;

.container {
  div {
    font-size: 20px;
    color: $color;
    width: 200px;
    height: 200px;
    border: 1px solid gray;
  }
}
复制代码

然后通过yanr start启动应用查看界面效果,跟预期一样...

为项目添加 UI 库-Antd

在真实开发中,我们一般使用 UI 库来提高我们的界面开发效率。国内最火的 React UI 库非 Antd莫属。接下来我们开始配置吧

首先安装 Antd 依赖

yarn add antd
复制代码

修改 sr/pages/antdDemo/index.tsx组件-引入一个 Button 组件

import React from "react";
import { Button } from "antd";

const Index: React.FC = () => {
  return (
    <div>
      <Button type="primary">Antd 按钮</Button>
    </div>
  );
};

export default Index;
复制代码

通过yarn start启动项目并观察界面效果,我们发现按钮被成功引用了,但是却没有显示正确的样式

antd没有显示正确的样式

接下我们给 Antd 加上样式

import React from "react";
import { Button } from "antd";

import "./index.less";

const Index: React.FC = () => {
  return (
    <div>
      <Button type="primary">Antd 按钮</Button>
    </div>
  );
};

export default Index;
复制代码

index.less的内容如下

@import "~antd/dist/antd.less";
复制代码

这里可以发现,Antd 的样式是基于 Less 实现的

保存代码后却发现 Webpack 编译报错"Module build failed"

antd-style-error

我们根据报错中的 issue 地址去找解决方案,有方案说Less加上javascriptEnabled即可

来我们加一下看看

      {
        test: /\.less$/i,
        use: ["style-loader", {
          loader: "css-loader",
          options: {
            modules: true,
          },
        },
          {
            loader: "less-loader",
            options: {
              lessOptions: {
                javascriptEnabled: true
              },
            },
          },
        ],
      },
复制代码

设置完成后重启项目并观察界面效果,但是我们发现 Antd 还是没有样式,那么我们继续查找 issue

真实原因:

因为我们的 webpack 项目配置了 css-modules ,它会将 Ant 的样式模块化前缀及哈希化处理,导致样式不匹配.

因此建议 CSS 模块化配置将 node_modules 目录文件 exclude 掉,不要让它们走 CSS Modules

换句话说,就是让 antd 的 less 不通过 css-module-loader,只让项目自己的 less 文件通过 css-module-loader

正确的 Webpack 配置如下

const config = {
   ........
  module: {
    rules: [
       ........
      {
        test: /\.less$/i,
        use: ["style-loader", {
          loader: "css-loader",
          options: {
            modules: true,
          },
        },
          {
            loader: "less-loader",
          },
        ],
        exclude: path.resolve(__dirname, "../src/app.less"),
      },
      {
        test: /\.less$/i,
        use: ["style-loader", {
          loader: "css-loader",
        },
          {
            loader: "less-loader",
            options: {
              lessOptions: {
                javascriptEnabled: true
              },
            },
          },
        ],
        include: path.resolve(__dirname, "../src/app.less"),
      },
    ]
  },
  ........

复制代码

修改src/app.less的内容

@import "~antd/dist/antd.less";

/* 全局样式,没有CSS modules

作用:
1. 覆盖Antd样式
2. 定义全局公共样式
*/

.myText {
  color: red;
}
复制代码

修改src/index.tsx内容

import React from "react";
import ReactDOM from "react-dom";

import './app.less'
import AntdDemo from "./pages/antdDemo";
........

const App = () => (
  <>
  ....
    <AntdDemo />
  ....
  </>
);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

复制代码

重新启动项目,发现样式正常显示了,😆


难道这就结束了吗?🙅‍♂️ 不,离搭建一个完整的 React 应用有些距离。那么在下一篇文章中,我会告诉你如何在项目中集成图片/字体资源

文章分类
前端
文章标签