知海

React 与 webpack 教程

TypeScript-dev指南与教程

React 与 webpack 教程

本指南将教你如何将 TypeScriptReactwebpack 结合使用,完成一个从零开始的项目搭建。

如果你正在启动一个全新的项目,可以先阅读 React 快速上手指南

以下教程默认你已经安装了 Node.jsnpm

初始化项目结构

首先,创建一个新的项目目录。本教程将其命名为 proj,你可以根据需要改为其他名称。

text 复制代码
mkdir proj
cd proj

我们将按以下结构组织项目:

text 复制代码
proj/
├─ dist/
└─ src/
   └─ components/
  • src 目录用于存放 TypeScript 源码文件
  • src/components 目录用于存放自定义的 React 组件
  • dist 目录将由 webpack 自动生成,存放最终打包后的文件

创建基础目录结构:

text 复制代码
mkdir src
cd src
mkdir components
cd ..

初始化 npm 工程

将当前目录初始化为一个 npm 包:

text 复制代码
npm init -y

该命令将使用默认配置生成一个 package.json 文件。

安装依赖

安装 webpack

确保已全局安装 webpack,然后将其作为开发依赖安装到本地:

text 复制代码
npm install --save-dev webpack webpack-cli

webpack 是一个模块打包工具,它可以将你的所有代码及其依赖打包成一个(或多个).js 文件。

安装 React 及其类型声明

安装 React 和 React-DOM 作为项目依赖,同时安装它们的 TypeScript 类型声明文件:

text 复制代码
npm install --save react react-dom
npm install --save-dev @types/react @types/react-dom

@types/ 前缀表示我们额外获取 React 和 React-DOM 的声明文件。一般情况下,TypeScript 会从包自身的类型声明中查找类型;如果包未包含声明文件,TypeScript 则会去查找 @types/ 下的对应声明包。

安装 TypeScript 及 webpack 加载器

安装 TypeScript、ts-loadersource-map-loader 作为开发依赖:

text 复制代码
npm install --save-dev typescript ts-loader source-map-loader

这些工具的作用如下:

  • ts-loader:让 webpack 使用 TypeScript 的标准配置文件 tsconfig.json 来编译 TypeScript 代码。
  • source-map-loader:读取 TypeScript 生成的 sourcemap 文件,帮助 webpack 生成属于自己的 sourcemaps。这样,你在调试最终打包文件时,可以直接调试 TypeScript 源代码。

注意ts-loader 不是唯一的 TypeScript 加载器,你也可以选择使用 awesome-typescript-loader。两者之间的区别可查看这里

以上将 TypeScript 安装为开发依赖。虽然你也可以通过 npm link typescript 链接到全局的 TypeScript 副本,但这不是常规做法。

添加 TypeScript 配置文件

我们需要创建一个 tsconfig.json 文件,用于指定 TypeScript 编译器需要处理的文件列表以及编译选项。

在项目根目录下新建 tsconfig.json 文件,并添加以下内容:

javascript 复制代码
{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es6",
        "jsx": "react"
    }
}

各选项的简要说明:

  • outDir:编译输出的目录。
  • sourceMap:生成 .map 文件,便于调试。
  • noImplicitAny:禁止隐式 any 类型。
  • module:指定模块代码生成方式,这里使用 commonjs
  • target:指定编译后的 JavaScript 目标版本,这里使用 ES6。
  • jsx:指定 JSX 代码的编译方式,react 表示将 JSX 转换为 React.createElement 调用。

更多关于 tsconfig.json 的说明,可参考 tsconfig.json 文档

编写代码

接下来,让我们用 React 编写一段 TypeScript 代码。

创建 Hello 组件

src/components 目录下创建一个名为 Hello.tsx 的文件。

以下是使用函数组件的写法:

typescript 复制代码
import * as React from 'react';

export interface HelloProps {
  compiler: string;
  framework: string;
}

export const Hello = (props: HelloProps) => (
  <h1>
    Hello from {props.compiler} and {props.framework}!
  </h1>
);

也可以使用类组件的写法:

typescript 复制代码
import * as React from 'react';

export interface HelloProps {
  compiler: string;
  framework: string;
}

// 'HelloProps' 描述了 props 的结构。
// 由于不涉及 state,因此使用 '{}' 类型。
export class Hello extends React.Component<HelloProps, {}> {
  render() {
    return (
      <h1>
        Hello from {this.props.compiler} and {this.props.framework}!
      </h1>
    );
  }
}

创建入口文件

接下来,在 src 目录下创建 index.tsx 文件:

typescript 复制代码
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import { Hello } from './components/Hello';

ReactDOM.render(
  <Hello compiler="TypeScript" framework="React" />,
  document.getElementById('example')
);

注意,导入 Hello 组件时使用的是相对于当前文件的路径 ./components/Hello,而不是模块名。这一点很重要,否则 TypeScript 只会尝试在 node_modules 文件夹中查找。

创建 HTML 页面

在项目根目录 proj 下创建一个 index.html 文件,用于在浏览器中展示我们的组件:

html 复制代码
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello React!</title>
    </head>
    <body>
        <div id="example"></div>

        <!-- Dependencies -->
        <script src="./node_modules/react/umd/react.development.js"></script>
        <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>

        <!-- Main -->
        <script src="./dist/main.js"></script>
    </body>
</html>

这里直接引用了 node_modules 中的 React 文件,方便快捷。你也可以将这些文件拷贝到其他目录,或者使用 CDN 上的 React 版本。Facebook 官方提供了一系列可用于生产环境的 React 文件,可参考这里

创建 webpack 配置文件

在项目根目录下创建一个 webpack.config.js 文件:

javascript 复制代码
module.exports = {
  mode: 'production',

  // Enable sourcemaps for debugging webpack's output.
  devtool: 'source-map',

  resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: ['.ts', '.tsx'],
  },

  module: {
    rules: [
      {
        test: /\.ts(x?)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader',
          },
        ],
      },
      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      {
        enforce: 'pre',
        test: /\.js$/,
        loader: 'source-map-loader',
      },
    ],
  },

  // When importing a module whose path matches one of the following, just
  // assume a corresponding global variable exists and use that instead.
  // This is important because it allows us to avoid bundling all of our
  // dependencies, which allows browsers to cache those libraries between builds.
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
};

如果你对 externals 字段感到疑惑,这里做一下额外说明:

我们不想把 React 的所有代码打包进最终的文件中。这样做有两个好处:

  1. 减少编译时间。
  2. 浏览器可以缓存这些不常变化的第三方库,从而提升页面加载速度。

理想情况下,我们希望在浏览器中直接通过模块的方式引入 React,但大多数浏览器尚未原生支持模块系统。因此,大多数库(如 jQuery_ 等)会将自身挂载到一个全局变量上,这种模式被称为“命名空间”模式。webpack 允许我们通过 externals 配置来兼容这种模式。

例如,通过配置 "react": "React",webpack 会将代码中对 "react" 的导入自动转换为从全局变量 React 中加载。

关于 webpack 配置的更多资料,可参考 webpack 官方文档

构建并运行

在项目根目录下执行以下命令:

text 复制代码
npx webpack

构建完成后,在浏览器中打开 index.html,你将会看到页面显示:

text 复制代码
Hello from TypeScript and React!

至此,你已经成功地将 TypeScript、React 和 webpack 整合在一起,并完成了一个可运行的示例项目。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。