新聞中心
工廠流水線生產(chǎn)的東西用久了,總想著自己手工是否也能做出來(lái),就如同工藝品和藝術(shù)品一般,雖然效果相似,但藝術(shù)品往往比工藝品更有韻味。

成都創(chuàng)新互聯(lián)公司是專(zhuān)業(yè)的高安網(wǎng)站建設(shè)公司,高安接單;提供網(wǎng)站建設(shè)、做網(wǎng)站,網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專(zhuān)業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行高安網(wǎng)站開(kāi)發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專(zhuān)業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專(zhuān)業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!
作為一名前端工程師,總是用一些腳手架來(lái)快速搭建新項(xiàng)目的基本結(jié)構(gòu),因此今天嘗試著一步步搭建一個(gè) React 的項(xiàng)目環(huán)境,看看需要處理哪些問(wèn)題,查漏補(bǔ)缺!
一、需求分析
首先分析我們的訴求:
- 應(yīng)用級(jí)別的項(xiàng)目,是需要支持打包構(gòu)建。
- 需要考慮兼容性,支持代碼 pollyfill。
- 支持 React 框架下的開(kāi)發(fā)環(huán)境。
- 支持代碼類(lèi)型提示。
- 支持前端路由。
- 支持前端狀態(tài)管理。
- 代碼規(guī)范、自動(dòng)格式化、Git 提交規(guī)范。
- 基礎(chǔ)的 UI 組件庫(kù)。
針對(duì)上面的訴求,其實(shí)也是絕大部分項(xiàng)目都會(huì)需要,因此也有了常見(jiàn)的解決方案:
- Webpack 5。
- Babel。
- React 17、React-dom。
- TypeScript。
- React-router-dom v6。
- Redux、React-redux。
- ESlint、Prettier、Lint-staged、Husky、@commitlint。
- Arco Design。
二、項(xiàng)目打包構(gòu)建
因?yàn)槭?2022 年了,所以我們的項(xiàng)目所有依賴(lài)項(xiàng)全部用最新的工具庫(kù)版本,搞起來(lái)!
首先是把項(xiàng)目的基本構(gòu)建能力搭建好,讓項(xiàng)目先跑起來(lái)!
1、 初始化 package.json
mkdir webpack-react
cd webpack-react
npm init --y
git init
然后稍微改改 package.json 文件如下:
{
"name": "webpack-react",
"private": true,
"version": "0.1.0",
"description": "一個(gè)基于 Webpack 構(gòu)建的 React開(kāi)發(fā)環(huán)境",
"main": "index.js",
"scripts": {
"dev": "",
"build": "",
"preinstall": "npx only-allow yarn"
},
"keywords": [],
"author": "DYBOY",
"license": "ISC"
}
由于沒(méi)有安裝一些三方庫(kù),所以該文件還比較“簡(jiǎn)陋”,所以接下來(lái)逐個(gè)安裝模塊,配置環(huán)境!
2、安裝配置 React 和 Typescript
根據(jù)需求,我們先安裝一些必要的模塊。
首先是 React 的基本模塊。
yarn add react react-dom
yarn add @types/react @types/react-dom
然后是 TypeScript 類(lèi)型模塊。
yarn add typescript -D
有了 TypeScript,就可以直接通過(guò) TS 生成一個(gè) tsconfig.json 的配置文件
yarn tsc --init
根據(jù)需要,稍微改改后如下:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2015",
"lib": ["DOM", "ES2015"],
"jsx": "react-jsx",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"module": "ESNext",
"rootDir": "./src",
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"resolveJsonModule": true,
"allowJs": true,
"outDir": "./dist",
"removeComments": true,
"isolatedModules": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/"]
}
*關(guān)于 tsconfig.json 文件的配置解析可以參閱:《會(huì)寫(xiě) TypeScript 但你真的會(huì) TS 編譯配置嗎?[1]》。
此時(shí)可以創(chuàng)建文件和文件夾,有一個(gè)初步的項(xiàng)目結(jié)構(gòu)。
項(xiàng)目結(jié)構(gòu)
其中:
- dist/:是用于存儲(chǔ)打包的文件。
- public/:是用于存放打包的模板入口 HTML 文件。
- src/:是用于開(kāi)發(fā)人員主要編碼的文件夾。
- .gitignore:用于配置 Git 忽略哪些文件或文件夾。
- tsconfig.json:TypeScript 的項(xiàng)目配置文件。
- yarn.lock:依賴(lài)模塊的版本信息,用于保證開(kāi)發(fā)環(huán)境一致性。
此時(shí)就可以簡(jiǎn)單的寫(xiě)支持 TS 和 React 的應(yīng)用了。
3、 Webpack 相關(guān)
因?yàn)槭且粋€(gè)項(xiàng)目,我們需要通過(guò)構(gòu)建工具,幫助我們快速的實(shí)現(xiàn)打包,以及開(kāi)發(fā)環(huán)境下的預(yù)覽,因此第二步就是安裝和配置 Webpack。
yarn add webpack webpack-cli webpack-dev-server webpack-merge -D
后兩個(gè)模塊分別是用于開(kāi)啟開(kāi)發(fā)時(shí)的本地 HTTP 服務(wù),和用于 Merge webpack 配置的工具函數(shù)。
(1) Webpack 配置文件結(jié)構(gòu)
首先,先完善 package.json 中的 scripts(開(kāi)發(fā)指令和構(gòu)建指令):
+ "dev": "cross-env NODE_ENV=development webpack serve -c scripts/webpack.dev.js",
+ "build": "yarn ts:checker && cross-env NODE_ENV=production webpack -c scripts/webpack.prod.js",
+ "ts:checker": "tsc --noEmit",
同時(shí)安裝一下 cross-env,該模塊主要是用于支持在不同的操作系統(tǒng)下保證環(huán)境變量正確。
yarn add cross-env -D
通過(guò)指令,我們需要三個(gè) Webpack 的配置文件:
(2) webpack.common.js 通用配置
這是公共的 Webpack 配置,主要配置了如下幾個(gè)地方。
const path = require("path");
const chalk = require("chalk");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const pkgJSON = require("../package.json");
console.log("process.env.NODE_ENV: ", process.env.NODE_ENV);
module.exports = {
entry: path.resolve(__dirname, "../src/index.tsx",
output: {
filename: "[name].[hash:8].js",
path: path.resolve(__dirname, "../dist"),
publicPath: "/",
clean: true,
},
resolve: {
extensions: [".ts", ".tsx", ".js"],
alias: {
"@": path.resolve(__dirname, "../src"),
},
},
module: {
rules: [
{
test: /\.tsx?$/,
use: ["ts-loader"],
exclude: /node_modules/,
},
{
test: /\.(jpe?g|png|svg|gif)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 25 * 1024, // 25kb
},
},
generator: {
filename: "assets/imgs/[name].[hash:8][ext]",
},
},
],
},
plugins: [
new webpack.DefinePlugin({
// 定義在代碼中可以替換的一些常量
__DEV__: process.env.NODE_ENV === "development",
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
title: pkgJSON.name,
meta: {
description: {
type: "description",
content: pkgJSON.description,
},
},
minify: "auto",
}),
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
}),
],
};個(gè)人有一個(gè)觀點(diǎn),開(kāi)發(fā)環(huán)境和構(gòu)建環(huán)境應(yīng)該在配置上相似性需要尋找平衡,開(kāi)發(fā)環(huán)境尋求的是熱更新快,構(gòu)建環(huán)境尋求的是兼容性好,且盡可能和開(kāi)發(fā)環(huán)境看到效果相同!
針對(duì)缺失的模塊還需要安裝到開(kāi)發(fā)依賴(lài)中:
# 支持 ts 和 tsx 文件的處理
yarn add ts-loader -D
# 美化終端輸出,安裝特定版本是為了處理模塊化包的問(wèn)題
yarn add chalk@4.1.2 -D
# 將 /public/index.html 作為模板入口文件打包
yarn add html-webpack-plugin -D
# 美化 webpack 編譯時(shí)候的進(jìn)度條
yarn add progress-bar-webpack-plugin -D
(3) webpack.dev.js 開(kāi)發(fā)配置
然后再配置下開(kāi)發(fā)環(huán)境下的 Webpack 配置,主要是支持熱更新、本地預(yù)覽功能,以及一些和生產(chǎn)環(huán)境差異的配置。
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development", // 開(kāi)發(fā)模式
devServer: {
hot: true, // 熱更新
open: true, // 編譯完自動(dòng)打開(kāi)瀏覽器
compress: false, // 關(guān)閉gzip壓縮
port: 7878, // 開(kāi)啟端口號(hào)
historyApiFallback: true, // 支持 history 路由重定向到 index.html 文件
},
module: {
// 插件的執(zhí)行順序從右到左
rules: [
{
test: /\.(css|scss|sass)$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["autoprefixer"]],
},
},
},
"sass-loader",
],
// 排除 node_modules 目錄
exclude: /node_modules/,
},
],
},
stats: "errors-only", // Webpack 在編譯的時(shí)候只輸出錯(cuò)誤日志,終端更清爽
});
這里增加了對(duì) scss/css 文件的處理,因此還需要安裝相關(guān)的模塊:
# style-loader 將 css 注入到 HTML 的內(nèi)聯(lián)樣式
# css-loader 用于加載 CSS 文件,轉(zhuǎn)化 CSS 為 CommonJS
yarn add style-loader css-loader -D
# postcss 用于處理 CSS 兼容性
# autoprefixer 用于自動(dòng)根據(jù)兼容需求增加 CSS 屬性的前綴
yarn add postcss postcss-loader autoprefixer -D
# sass 主要是用于支持 “CSS 編程”
# sass-loader 會(huì)將 .scss 后綴文件編譯成 CSS
yarn add sass sass-loader -D
講到了 CSS 自動(dòng)前綴處理兼容性,因此可以將需要兼容瀏覽器版本的配置放到 package.json -> browserslist 屬性下:
{
...
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 9",
"last 3 iOS versions"
]
}
...
}(4) webpack.prod.js 生產(chǎn)配置
針對(duì) Webpack 的構(gòu)建環(huán)境下(mode: "production")的配置,實(shí)際上在 Webpack 5 版本中默認(rèn)就集成了很多優(yōu)化,更多自定義訴求可以參考:Webpack Optimization[2] 配置。
const { merge } = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "production",
optimization: {
minimize: true,
minimizer: [
"...",
new TerserPlugin({
terserOptions: {
format: {
comments: false,
},
},
extractComments: false,
}),
],
},
module: {
rules: [
{
test: /\.(css|scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["autoprefixer"]],
},
},
},
"sass-loader",
],
exclude: /node_modules/,
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "assets/css/[hash:8].css",
}),
],
});需要安裝依賴(lài):
# 用于將 CSS 導(dǎo)出到單獨(dú)文件
yarn add mini-css-extract-plugin -D
# 用于做源代碼壓縮
yarn add terser-webpack-plugin -D
(5) 開(kāi)發(fā)&構(gòu)建
弄好了上面的 Webpack 配置,就可以實(shí)際的開(kāi)發(fā)了。
/src/index.tsx 文件如下:
執(zhí)行:yarn dev,會(huì)自動(dòng)打開(kāi)瀏覽器頁(yè)面:http://localhost:7878/。
執(zhí)行:yarn build,會(huì)將項(xiàng)目編譯打包輸出到 ./dist/ 文件夾下。
4、 Bable 處理兼容性
我們的項(xiàng)目可能會(huì)在各種瀏覽器中運(yùn)行,為了盡可能兼容大多數(shù)用戶的設(shè)備,因此引入 Babel 來(lái)統(tǒng)一處理兼容性。
在 webpack.common.js 配置文件中增加:
...
rules: [
{
test: /\.tsx?$/,
use: [
+ {
+ loader: "babel-loader",
+ options: {
+ presets: [
+ [
+ "@babel/preset-env", // 預(yù)制配置
+ {
+ corejs: {
+ version: 3,
+ },
+ useBuiltIns: "usage", // 按需引入 pollyfill
+ },
+ ],
+ "@babel/preset-react", // React 環(huán)境
+ ],
+ plugins: ["@babel/plugin-transform-runtime"],
+ },
+ },
"ts-loader",
],
exclude: /node_modules/,
},
...
],
...
放到 webpack.common.js 文件下也是為了考慮在開(kāi)發(fā)環(huán)境下驗(yàn)證引入 pollyfill 的正確性。
同時(shí)還需要安裝如下依賴(lài):
# 安裝 babel 核心和加載器
yarn add @babel/core babel-loader -D
# core-js 中有各種各樣的 pollyfill,用于提升兼容性
# https://github.com/zloirock/core-js
yarn add core-js -D
# 預(yù)制環(huán)境
yarn add @babel/preset-env @babel/preset-react -D
# 統(tǒng)一的 pollyfill,打包時(shí)候加載到代碼中,減少冗余代碼
yarn add @babel/plugin-transform-runtime -D
三、路由 React-router-dom
前端的頁(yè)面一般是多頁(yè)面的,因此我們需要一個(gè)統(tǒng)一的路由來(lái)方便管理,這里用到了 react-router-dom v6[3] 版本。
多路由的使用方式基本相似,因此官方提煉出了 useRoutes 的 Hooks,用于便捷生成路由,相較于 V5 版本,確實(shí)方便太多了。
安裝作為應(yīng)用依賴(lài):
yarn add react-router-dom
1、 統(tǒng)一管理的路由配置首先是配置
路由 /src/config/router.tsx 文件:
import { RouteObject } from "react-router-dom";
import HomePage from "@/pages/home";
const ROUTER_CONFIG: RouteObject[] = [
{
path: "/",
element: ,
},
{
path: "*",
element: <>404 Not Found!>,
},
];
export { ROUTER_CONFIG };
之后如果新增任意頁(yè)面,都可以在 /src/pages/ 文件夾下新增任,并且都可以放到 /src/config/router.tsx 文件來(lái)統(tǒng)一管理,嵌套路由同樣適用,只需要根據(jù) RouteObject 類(lèi)型聲明規(guī)范即可:
/**
* A route object represents a logical route, with (optionally) its child
* routes organized in a tree-like structure.
*/
export interface RouteObject {
caseSensitive?: boolean; // 大小寫(xiě)敏感
children?: RouteObject[]; // 子路由
element?: React.ReactNode; // 組件
index?: boolean; // 在子路由中,默認(rèn)為父級(jí)路由的首頁(yè)
path?: string; // URL 路徑
}
2、 項(xiàng)目中引入
然后在 /src/app.tsx 文件中使用 useRoutes() 并嵌入到應(yīng)用中:
import { useRoutes } from "react-router-dom";
import { ROUTER_CONFIG } from "./config/router";
const App = () => {
const appRoutesElement = useRoutes(ROUTER_CONFIG);
return appRoutesElement;
};
export default App;
最后在 /src/inde.tsx 使用 BrowserRouter 包裹 組件。
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./app";
render(
,
document.getElementById("root")
);
此時(shí)的項(xiàng)目目錄結(jié)構(gòu)如下:
目錄結(jié)構(gòu)
如此就可以愉快的編寫(xiě)任意頁(yè)面啦!
3、 [優(yōu)化]延遲按需加載頁(yè)面
雖然路由集中管理了,但是首屏加載的 js 文件太大,會(huì)使得白屏?xí)r間較長(zhǎng),增加了用戶等待時(shí)間。
因此考慮延遲按需加載頁(yè)面方式,使用 import() 和 React.lazy() 來(lái)主動(dòng)優(yōu)化。
新建一個(gè)通用組件 LazyWrapper 在 /src/components/lazy-wrapper/index.tsx 文件。
import { FC, lazy, Suspense } from "react";
interface LazyWrapperProps {
/** 組件路徑: 在 src/pages 目錄下的頁(yè)面路徑,eg: /home => src/pages/home/index.tsx */
path: string;
}
/**
* 懶加載組件包裝器
*/
const LazyWrapper: FC = ({ path }) => {
const LazyComponent = lazy(() => import(`/src/pages${path}`));
return (
loading... 鏈接URL:http://m.5511xx.com/article/copiecj.html


咨詢(xún)
建站咨詢(xún)
