美文网首页React前端开发那些事儿
快来跟我一起学 React(Day3)

快来跟我一起学 React(Day3)

作者: vv_小虫虫 | 来源:发表于2021-03-19 18:35 被阅读0次

    简介

    上一节我们介绍了 React 官方提供的脚手架(create-react-app),然后用官方脚手架创建了一个 react-demo1 项目,用脚手架创建出来的项目算是 React 官方认为的最佳项目实践了,但是从上一节使用中我们可以看出,官方脚手架可还是有一些弊端,它只提供一些可改的 webpack 配置,如果你想自己添加一个 loaderplugin 什么的,你可能就需自定义 webpack 配置了,就不能在依赖脚手架了,既然是重新学习 React,我们就来挑战一下自己,从 0 开始搭建一个企业级的 React 项目,大家跟紧节奏吧。

    知识点

    • Webpack (打包编译)
    • TypeScript(语法规范)
    • React(js 框架)
    • html-webpack-plugin(webpack 插件)
    • Sass(css 预处理框架)
    • PostCss(css 预处理框架)
    • mini-css-extract-plugin(提取 css 模块插件)
    • Babel( es 语法转化)
    • Jsx & Tsx(jsx 语法)
    • ESLint(代码质量校验)
    • Optimization(项目优化)

    ok!要搭建一个企业级的项目需要准备的东西还是有点多的,我们分一下类:

    • Js 框架(Vue、TypeScript、Tsx、Jsx)
    • Css 样式(Sass)
    • 工程化工具(Eslint、Babel、webpack、webpack-chain)

    我们开始吧。

    项目初始化

    我们创建一个 cus-react-demo 目录,然后在 cus-react-demo 目录下执行 npx init -y 命令:

    mkdir cus-react-demo && cd cus-react-demo && npm init -y
    
    1

    如上图所示,初始化完毕后会看到一个 package.json 文件。

    webpack

    接下来我们需要安装 webpack 相关的依赖:

    安装 webpack

    webpack 核心库。

    在工程目录 cus-react-demo 执行以下命令安装 webpack:

    npm install -D webpack --registry https://registry.npm.taobao.org
    

    目前 webpack 的版本是 5.26.3

    安装 webpack-cli

    webpack 指令库。

    在工程目录 cus-react-demo 执行以下命令:

    npm install -D webpack-cli --registry https://registry.npm.taobao.org
    

    安装 webpack-dev-server

    webpack 开发者服务框架。

    在工程目录 cus-react-demo 执行以下命令:

    npm install -D webpack-dev-server --registry https://registry.npm.taobao.org
    

    安装 webpack-chain

    webpack 配置工具。

    在工程目录 cus-react-demo 执行以下命令:

    npm install -D webpack-chain --registry https://registry.npm.taobao.org
    

    创建 webpack 配置

    在工程目录 cus-react-demo 下创建一个 webpack.config.js 文件:

    touch webpack.config.js
    

    然后对 webpack.config.js 进行配置,用 webpack-chain 导入一个 webpack 配置:

    const config = new (require('webpack-chain'))();
    
    module.exports = config.toConfig();
    

    安装 cross-env

    定义 node 中的 process.env 中的变量。

    在工程目录 cus-react-demo 执行以下命令:

    npm install -D cross-env --registry https://registry.npm.taobao.org
    

    为了开发方便,我们在 package.json 中声明两个脚本 builddev

    {
      "name": "cus-react-demo",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "rimraf dist && cross-env NODE_ENV=production webpack --mode=production",
        "start": "cross-env NODE_ENV=development webpack serve --mode=development --progress"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "cross-env": "^7.0.3",
        "webpack": "^5.26.3",
        "webpack-chain": "^6.5.1",
        "webpack-cli": "^4.5.0",
        "webpack-dev-server": "^3.11.2"
      }
    }
    

    我们可以试着运行一下 buildstart 脚本命令:

    npm start
    
    1-2.png

    现在运行肯定是报错的,因为我们还没有对项目进行任何配置。

    入口与出口

    我们首先在工程目录 cus-react-demo 下创建一个 src 目录,然后在 src 目录下创建一个 main.tsx 文件:

    mkdir src && cd src && touch main.tsx && cd ..
    

    然后我们找到 webpack.config.js 文件,对 webpack 的入口和出口进行配置:

    const path = require('path');
    const config = new (require('webpack-chain'))();
    const isDev = process.env.NODE_ENV === 'development'; // 判断是否是开发环境
    config
        .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录
        .entry('app') // 入口文件名称为 app
            .add('./src/main.tsx') // 入口文件为 ./src/main.tsx
            .end()
        .output
            .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录
            .filename(isDev ? '[name].[hash:8].js' : '[name].[contenthash:8].js') // 打包出来的 bundle 名称为 "[name].[contenthash:8].js"
            .publicPath('/') // publicpath 配置为 "/"
            .end();
    module.exports = config.toConfig();
    

    可以看到,我们配置了一个 app 入口 ./src/main.ts,然后给所有的输出文件名称改成了 [name].[hash:8].js

    我们再次执行 build 命令:

    npm run build
    
    1-3.png

    可以看到,这次没有报错了。

    但是当我们给 src/main.tsx 添加点内容试试:

    let a: string = 'hello';
    

    我们再次运行 npm run build 命令:

    1-4.png

    可以看到,又报错了,这次是因为 webpack 没法去解析 main.tsx 中的 ts 语法,所以接下来我们就需要配置一下 webpackloader 了,让我们的项目能够支持 jsxtstsx 语法。

    Babel & TypeScript

    因为我们项目是需要支持 ts 语法的,所以我们需要安装一下 TypeScript 相关依赖:

    babel-preset-react-app

    很幸运哈,React 官方已经提供了一套 babel 插件集合,它会自动的帮我们添加 babel 相关的配置,让我们能够轻松的使用 jsxtstsx 语法,而且不需要考虑 es 语法的兼容性问题,会根据我们的浏览器配置自动做语法兼容等操作。

    在工程目录 cus-react-demo 执行以下命令:

    npm install -D babel-preset-react-app --registry https://registry.npm.taobao.org
    

    babel-loader

    webpack 加载器。

    在工程目录 cus-react-demo 下执行以下命令安装:

    npm install -D babel-loader --registry https://registry.npm.taobao.org
    

    ok!安装完 Babel 的一些依赖后,我们开始配置 webpack。

    找到 webpack.config.js 文件,然后添加 babel-loader 配置:

    const path = require('path');
    const config = new (require('webpack-chain'))();
    const isDev = process.env.NODE_ENV === 'development'; // 判断是否是开发环境
    config
        .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录
        .entry('app') // 入口文件名称为 app
            .add('./src/main.tsx') // 入口文件为 ./src/main.tsx
            .end()
        .output
            .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录
            .filename('[name].[contenthash:8].js') // 打包出来的 bundle 名称为 "[name].[contenthash:8].js"
            .publicPath('/') // publicpath 配置为 "/"
            .end()
        .resolve
            .extensions.add('.js').add('.jsx').add('.ts').add('.tsx').end() // 添加后缀自动解析
            .end()
        .module
            .rule('ts') // 配置 typescript
                .test(/\.(js|mjs|jsx|ts|tsx)$/)
                .exclude
                    .add(filepath => {
                        // Don't transpile node_modules
                        return /node_modules/.test(filepath)
                    })
                    .end()
                .use('babel-loader')
                    .loader('babel-loader')
                    .end()
                .end()
    module.exports = config.toConfig();
    

    然后我们需要在工程目录 cus-react-demo 下创建一个文件 babel.config.js

    touch babel.config.js
    

    然后写入以下代码到 babel.config.js 文件:

    module.exports = {
        presets: [
            [
                "babel-preset-react-app", // 添加 react-app 插件集合
            ],
        ]
    }
    

    TypeScript 配置

    因为我们需要进行 ts 语法的我们还需要在工程目录 cus-react-demo 下创建一个 ts 配置文件 tsconfig.json

    touch tsconfig.json
    

    然后写入以下代码到 tsconfig.json 文件:

    {
      "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "strict": true,
        "jsx": "preserve",
        "importHelpers": true,
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "suppressImplicitAnyIndexErrors": true,
        "resolveJsonModule": true,
        "sourceMap": true,
        "baseUrl": ".",
        "types": ["webpack-env"],
        "paths": {
          "@/*": ["src/*"]
        },
        "lib": ["esnext", "dom", "dom.iterable"]
      },
      "include": ["src/**/*.ts", "src/**/*.tsx",],
      "exclude": ["node_modules"]
    }
    

    ok!我们再次运行 npm run build 命令:

    1-5.png

    可以看到,这一次 webpack 正常完成了编译打包。

    React 配置

    我们首先安装一下 React 所需要的依赖:

    安装 react & react-dom

    React 核心 API。

    在工程目录 cus-react-demo 执行以下命令:

    npm install react react-dom @types/react @types/react-dom --registry https://registry.npm.taobao.org
    

    然后我们修改一下 src/main.tsx 文件来测试一下:

    import React from 'react';
    import ReactDOM from 'react-dom';
    ReactDOM.render(
        <div>hello react</div>,
        document.getElementById('root')
    );
    

    很简单,就是利用 tsx 语法在页面中输出了一句 “hello react” 字符串。

    如果需要开启 webpack-dev-server 的话,我们还需要配置一下 devServer

    我们找到 webpack.config.js 配置文件,然后添加 devServer 配置:

    const path = require('path');
    const config = new (require('webpack-chain'))();
    const isDev = process.env.NODE_ENV === 'development'; // 判断是否是开发环境
    config
        .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录
        .entry('app') // 入口文件名称为 app
            .add('./src/main.tsx') // 入口文件为 ./src/main.tsx
            .end()
        .output
            .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录
            .filename(isDev ? '[name].[hash:8].js' : '[name].[contenthash:8].js') // 打包出来的 bundle 名称为 "[name].[contenthash:8].js"
            .publicPath('/') // publicpath 配置为 "/"
            .end()
        .resolve
            .extensions.add('.js').add('.jsx').add('.ts').add('.tsx').end() // 添加后缀自动解析
            .end()
        .module
            .rule('ts') // 配置 typescript
                .test(/\.(js|mjs|jsx|ts|tsx)$/)
                .exclude
                    .add(filepath => {
                        // Don't transpile node_modules
                        return /node_modules/.test(filepath)
                    })
                    .end()
                .use('babel-loader')
                    .loader('babel-loader')
                    .end()
                .end()
        .end()
        .devServer
            .host('0.0.0.0') // 服务器外部可访问
            .disableHostCheck(true) // 关闭白名单校验
            .contentBase(path.resolve(__dirname, './public')) // 设置一个 express 静态目录
            .historyApiFallback({
                disableDotRule: true, // 禁止在链接中使用 "." 符号
                rewrites: [
                    { from: /^\/$/, to: '/index.html' }, // 将所有的 404 响应重定向到 index.html 页面
                ],
            })
            .port(8080) // 当前端口号
            .hot(true) // 打开页面热载功能
            .sockPort('location'); // 设置成平台自己的端口
    module.exports = config.toConfig();
    

    ok!然后我们在工程目录 cus-react-demo 下执行 npm start 开启 webpack-dev-server

    npm start
    

    执行完毕后,我们可以直接用浏览器访问一下打包过后的目录(http://127.0.0.1:8080/webpack-dev-server):

    1-6.png

    可以看到,webpack-dev-server 列出了 webpack 打包出来的所有 js 资源文件。

    接下来我们在工程目录 cus-react-demo 底下创建一个 public 目录,然后在 public 目录下创建一个 index.html 文件作为我们 app 的入口页面:

    mkdir public && touch public/index.html
    

    然后写入以下代码到 public/index.html 文件:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Title</title>
      </head>
      <body>
        <noscript>your browser should support javascript!</noscript>
        <!-- 挂载点 -->
        <div id="root"></div>
        <!-- 引入入口文件 -->
        <!-- 注意!这里的 64a558f4 每个人是不一样的 -->
        <script src="/app.64a558f4.js"></script>
      </body>
    </html>
    

    注意!这里的 64a558f4 每个人是不一样的)创建完毕后,我们重启服务:

    npm start
    

    然后我们可以直接用浏览器访问 http://127.0.0.1:8080/ 项目入口了:

    1-7.png

    可以看到,页面渲染了我们在 src/index.js 入口文件中定义的 “hello react” 字符串。

    html-webpack-plugin

    虽然浏览器中正常显示了我们的结果,但是我们现在还是手动去引入 app.xxx.js 文件的,而 xxx 是经常变化的,所以我们不可能每次都去改 public/index.html 文件,能不能自动去引入 js 资源文件到入口 index.html 呢?

    安装 html-webpack-plugin

    在工程目录 cus-react-demo 执行以下命令:

    npm install -D html-webpack-plugin --registry https://registry.npm.taobao.org
    

    然后修改一下 webpack.config.js 配置文件,引入 html-webpack-plugin 插件:

    const path = require('path');
    const config = new (require('webpack-chain'))();
    const isDev = process.env.NODE_ENV === 'development'; // 判断是否是开发环境
    config
            .target('web')
        .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录
        .entry('app') // 入口文件名称为 app
            .add('./src/main.tsx') // 入口文件为 ./src/main.tsx
            .end()
        .output
            .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录
            .filename(isDev ? '[name].[hash:8].js' : '[name].[contenthash:8].js') // 打包出来的 bundle 名称为 "[name].[contenthash:8].js"
            .publicPath('/') // publicpath 配置为 "/"
            .end()
        .resolve
            .extensions.add('.js').add('.jsx').add('.ts').add('.tsx').end() // 添加后缀自动解析
            .end()
        .module
            .rule('ts') // 配置 typescript
                .test(/\.(js|mjs|jsx|ts|tsx)$/)
                .exclude
                    .add(filepath => {
                        // Don't transpile node_modules
                        return /node_modules/.test(filepath)
                    })
                    .end()
                .use('babel-loader')
                    .loader('babel-loader')
                    .end()
                .end()
        .end()
        .plugin('html') // 添加 html-webpack-plugin 插件
            .use(require('html-webpack-plugin'), [
                {
                    template: path.resolve(__dirname, './public/index.html'), // 指定模版文件
                    chunks: ['app'], // 指定需要加载的 chunk
                    inject: 'body', // 指定 script 脚本注入的位置为 body
                },
            ])
            .end()
        .devServer
            .host('0.0.0.0') // 服务器外部可访问
            .disableHostCheck(true) // 关闭白名单校验
            .contentBase(path.resolve(__dirname, './public')) // 设置一个 express 静态目录
            .historyApiFallback({
                disableDotRule: true, // 禁止在链接中使用 "." 符号
                rewrites: [
                    { from: /^\/$/, to: '/index.html' }, // 将所有的 404 响应重定向到 index.html 页面
                ],
            })
            .port(8080) // 当前端口号
            .hot(true) // 打开页面热载功能
            .sockPort('location'); // 设置成平台自己的端口
    module.exports = config.toConfig();
    

    ok!配置完毕后,我们就可以去掉 public/index.html 中的入口 js 文件了,让其自动引入:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Title</title>
    </head>
    <body>
    <noscript>your browser should support javascript!</noscript>
    <!-- 挂载点 -->
    <div id="root"></div>
    <!-- html-webpack-plugin 将自动引入入口文件 -->
    </body>
    </html>
    

    我们再次执行 npm start 命令启动项目:

    npm start
    

    效果跟之前一致,我就不演示了。

    css 样式(Sass)

    安装 Sass

    Sass & Scss 语法核心 API。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D sass --registry https://registry.npm.taobao.org
    

    安装 sass-loader

    Sass & Scss 文件加载器。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D sass-loader --registry https://registry.npm.taobao.org
    

    安装 postcss-loader

    css 样式处理工具,自动添加浏览器适配前缀、压缩 css 样式等等。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D postcss-loader --registry https://registry.npm.taobao.org
    

    然后我们在工程目录 sy_webpack-wedding 下创建一个 postcss.config.js 作为 postcss 的配置文件:

    touch postcss.config.js
    

    写入以下代码到 postcss.config.js 文件:

    module.exports = {
      plugins: [
        require('autoprefixer')(), // 添加 autoprefixer 插件做自动添加样式前缀
        require('cssnano')({
          // 添加 cssnano 插件做 css 代码压缩
          preset: [
            'default',
            {
              mergeLonghand: false,
              cssDeclarationSorter: false,
            },
          ],
        }),
      ],
    };
    

    安装 cssnano

    压缩 css 代码。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D cssnano --registry https://registry.npm.taobao.org
    

    安装 autoprefixer

    自动添加浏览器样式适配前缀。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D autoprefixer@^9.8.5 --registry https://registry.npm.taobao.org
    

    要让 autoprefixer 插件起作用,我们还需要配置一个项目所支持的浏览器列表文件 .browserslistrc,于是我们在工程目录 sy_webpack-wedding 下创建一个 .browserslistrc 文件:

    touch .browserslistrc
    

    然后写入以下内容到 .browserslistrc 文件:

    > 0.25%, not dead
    

    意思就是告诉 autoprefixer 插件,我们项目需要兼容的浏览器是 “大于市面上 0.25% 使用率的浏览器,请根据我配置的浏览器列表自动判读是否应该添加 css 前缀做兼容”。

    安装 css-loader

    css 模块加载器。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D css-loader --registry https://registry.npm.taobao.org
    

    安装 mini-css-extract-plugin

    抽离 css 样式到独立的 .css 文件。

    在工程目录 sy_webpack-wedding 执行以下命令:

    npm install -D mini-css-extract-plugin --registry https://registry.npm.taobao.org
    

    ok!安装完 Sass 相关的依赖后,我们来修改一下 webpack.config.js 文件,让 webpack 支持 Sass

    const path = require('path');
    const config = new (require('webpack-chain'))();
    const isDev = process.env.NODE_ENV === 'development'; // 判断是否是开发环境
    config
        .target('web')
        .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录
        .entry('app') // 入口文件名称为 app
            .add('./src/main.tsx') // 入口文件为 ./src/main.tsx
            .end()
        .output
            .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录
            .filename(isDev ? '[name].[hash:8].js' : '[name].[contenthash:8].js') // 打包出来的 bundle 名称为 "[name].[contenthash:8].js"
            .publicPath('/') // publicpath 配置为 "/"
            .end()
        .resolve
            .extensions.add('.js').add('.jsx').add('.ts').add('.tsx').end() // 添加后缀自动解析
            .end()
        .module
            .rule('ts') // 配置 typescript
                .test(/\.(js|mjs|jsx|ts|tsx)$/)
                .exclude
                    .add(filepath => {
                        // Don't transpile node_modules
                        return /node_modules/.test(filepath)
                    })
                    .end()
                .use('babel-loader')
                    .loader('babel-loader')
                    .end()
                .end()
            .rule('sass') // sass-loader 相关配置
                .test(/\.(sass|scss)$/) // Sass 和 Scss 文件
                .use('extract-loader') // 提取 css 样式到单独 css 文件
                    .loader(require('mini-css-extract-plugin').loader)
                    .end()
                .use('css-loader') // 加载 css 模块
                    .loader('css-loader')
                    .end()
                .use('postcss-loader') // 处理 css 样式
                    .loader('postcss-loader')
                    .end()
                .use('sass-loader') // Sass 语法转 css 语法
                    .loader('sass-loader')
                    .end()
                .end()
            .end()
        .plugin('extract-css') // 提取 css 样式到单独 css 文件
            .use(require('mini-css-extract-plugin'), [
                {
                    filename: isDev ? 'css/[name].css': 'css/[name].[contenthash].css',
                    chunkFilename: isDev ? 'css/[id].css': 'css/[name].[contenthash].css',
                },
            ])
            .end()
        .plugin('html') // 添加 html-webpack-plugin 插件
            .use(require('html-webpack-plugin'), [
                {
                    template: path.resolve(__dirname, './public/index.html'), // 指定模版文件
                    chunks: ['app'], // 指定需要加载的 chunk
                    inject: 'body', // 指定 script 脚本注入的位置为 body
                },
            ])
            .end()
        .devServer
            .host('0.0.0.0') // 服务器外部可访问
            .disableHostCheck(true) // 关闭白名单校验
            .contentBase(path.resolve(__dirname, './public')) // 设置一个 express 静态目录
            .historyApiFallback({
                disableDotRule: true, // 禁止在链接中使用 "." 符号
                rewrites: [
                    { from: /^\/$/, to: '/index.html' }, // 将所有的 404 响应重定向到 index.html 页面
                ],
            })
            .port(8080) // 当前端口号
            .hot(true) // 打开页面热载功能
            .sockPort('location') // 设置成平台自己的端口
            .open(true)
    module.exports = config.toConfig();
    

    测试

    我们在 src 目录底下创建一个 main.scss 样式文件:

    touch ./src/main.scss
    

    然后写入点样式到 src/main.scss 文件:

    .root{
      color: red;
      font-size: 30px;
    }
    

    接着我们修改一下 src/main.tsx 文件,引入 main.scss 样式:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import "./main.scss";
    ReactDOM.render(
        <div className="root">hello react</div>,
        document.getElementById('root')
    );
    

    可以看到,我们引入了 main.scss 样式文件,然后给 div 元素上添加了一个 root 类名。

    我们重新运行项目看结果:

    npm start
    
    1-8.gif

    可以看到,当我们修改样式跟内容的时候,页面实时刷新了。

    这一节有点长,先到这里了。

    总结

    我们从 0 开始搭建了一个项目,完成了入口与出口的配置、ts 语法支持、react 基本库的安装、css 样式配置等工作,跟上的小伙伴要给你们点个赞哦,所以整个下来基本上是对 webpack 的配置工作(对 webpack 不熟的小伙伴,强烈推荐去看我前面的文章 《来和 webpack 谈场恋爱吧》:https://www.lanqiao.cn/courses/2893),到这我们只完成了一半的配置工作,我们下节继续。

    欢迎志同道合的小伙伴一起交流,一起学习,欢迎私信我~

    相关文章

      网友评论

        本文标题:快来跟我一起学 React(Day3)

        本文链接:https://www.haomeiwen.com/subject/sglbcltx.html