什么是React
React 是一個用于構(gòu)建用戶界面的,由Facebook開源的JavaScript庫召娜,以聲明式編寫 UI础钠,創(chuàng)建具有各自狀態(tài)的組件,再通過基礎(chǔ)組件組合為各種復(fù)雜的業(yè)務(wù)組件孝赫,組件邏輯使用JavaScript編寫而非模版,因此你可以輕松地在應(yīng)用中傳遞數(shù)據(jù)红符,并使得狀態(tài)與DOM分離青柄。
React的組件分為有狀態(tài)的普通組件和無狀態(tài)的函數(shù)組件。有狀態(tài)的普通組件除了使用外部數(shù)據(jù)(通過 this.props 訪問)以外预侯,組件還可以維護(hù)其內(nèi)部的狀態(tài)數(shù)據(jù)(通過 this.state 訪問)致开。當(dāng)組件的狀態(tài)數(shù)據(jù)改變時,組件會再次調(diào)用 render() 方法重新渲染對應(yīng)的標(biāo)記萎馅。組件名稱必須以大寫字母開頭双戳,因為React會將以小寫字母開頭的組件視為原生 DOM 標(biāo)簽。
在最新的React16.8中引入了Hook糜芳,它可以讓你在不編寫有狀態(tài)組件的情況下使用state以及其他的React特性飒货。
React中配合使用 TSX,TSX 可以很好地描述 UI 應(yīng)該呈現(xiàn)出它應(yīng)有交互的本質(zhì)形式峭竣。TSX 可能會使人聯(lián)想到模版語言塘辅,但它具有 JavaScript 的全部功能。
React分別支持瀏覽器端渲染皆撩,服務(wù)器端渲染兩種方式扣墩。
React中具體內(nèi)容我們將在具體開發(fā)中陸續(xù)介紹。
增加路由控制
添加React程序需要的路由控制包
yarn add react-router-dom
以及類型定義文件
yarn add -D @types/react-router-dom
react-router-dom包比react-router多了一些預(yù)制的DOM標(biāo)簽扛吞,其他功能都一樣呻惕,所以我們只需要導(dǎo)入前者即可。路由這塊有個坑滥比,請參考 0X-各種疑難雜癥
按照如下路徑創(chuàng)建對應(yīng)的組件
my-app/
├─ dist/
└─ src/
└─ components/
└─ About.tsx
└─ Header.tsx
└─ Top.tsx
└─ containers/
└─ App.tsx
Header.tsx定義了所有頁面公用的導(dǎo)航鏈接亚脆,是一個無狀態(tài)的函數(shù)控件。Link是react-router-dom的預(yù)制標(biāo)簽盲泛,最終會解析為a標(biāo)簽濒持。
import * as React from 'react'
import { Link } from "react-router-dom";
import { PATHS } from '../constants';
const Header: React.SFC = () => {
return (
<ul>
<li><Link to={PATHS.TOP}>Top</Link></li>
<li><Link to={PATHS.ABOUT}>About</Link></li>
</ul>
);
}
export default Header;
Top.tsx是一個有狀態(tài)的類控件
import * as React from "react";
export interface TopProps {
}
class Top extends React.Component<TopProps> {
constructor(props: TopProps) {
super(props);
}
render() {
return (<div>Welcome to my app!!</div>);
}
}
export default Top;
About.tsx
import * as React from "react";
const About: React.SFC = () => {
return (<div>About</div>);
}
export default About;
容器組件App.tsx
import * as React from 'react'
import { Route, Router } from "react-router-dom";
import { createBrowserHistory } from "history";
import Top from '../components/Top';
import About from '../components/About';
import Header from '../components/Header';
import { PATHS } from '../constants';
let history = createBrowserHistory()
class App extends React.Component {
render() {
return (
<Router history={history}>
<Header />
<Route exact path={PATHS.TOP} component={Top}></Route>
<Route exact path={PATHS.ABOUT} component={About} ></Route>
</Router>
);
}
}
export default App;
容器組件App.tsx是有不同組件組合而成的,比如通用的Header及根據(jù)路由顯示不同內(nèi)容的組件查乒。通過Router標(biāo)簽的history屬性將使用Html5 History API的屬性注入到所包含的所有組件內(nèi)弥喉,通過組件的props可以訪問到history郁竟,location,match等與路由相關(guān)的屬性玛迄。
Route標(biāo)簽關(guān)聯(lián)了路由及顯示的組件,exact屬性是exact=true的簡寫棚亩,保證了路由嚴(yán)格只能匹配到一個組件蓖议。
最后修改index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import App from "./containers/App";
ReactDOM.render(
<App/>,
document.getElementById("app")
);
根目錄添加constants.ts管理程序所有常量
// 路由配置
export const PATHS = {
TOP: '/',
ABOUT: '/about',
}
到此為止虏杰,我們在應(yīng)用程序上實現(xiàn)了簡單的路由功能。
按需加載
我們通過React自帶的lazy, Suspense來實現(xiàn)按需加載勒虾。注意此方式僅支持客戶端渲染纺阔。
在App.tsx,首先導(dǎo)入對應(yīng)函數(shù)及標(biāo)簽
import { lazy, Suspense } from "react";
將原有的直接引用方式
import About from '../components/About';
替換為
const About = lazy(() => import('../components/About'));
使用Suspense標(biāo)簽來包含路由標(biāo)簽
<Suspense fallback={() => <div>Loading...</div>}>
<Route exact path={PATHS.ABOUT} component={About} ></Route>
</Suspense>
以此完成動態(tài)加載修然。重新編譯后報錯:
ERROR in ./src/containers/App.tsx
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: C:\work\temp\my-app\src\containers\App.tsx: Support for the experimental syntax 'dynamicImport' isn't currently enabled (9:26):
7 | import Top from '../components/Top';
8 |
> 9 | const About = lazy(() => import('../components/About'));
| ^
10 | let history = createBrowserHistory()
11 |
12 | class App extends React.Component {
Add @babel/plugin-syntax-dynamic-import (https://git.io/vb4Sv) to the 'plugins' section of your Babel config to enable parsing.
看來預(yù)制的presets沒有包含對應(yīng)的插件笛钝,這里需要手動添加插件支持。運行安裝命令yarn add -D @babel/plugin-syntax-dynamic-import
后愕宋,修改babel.config.js
const presets = [
[
"@babel/preset-env",
{
targets: {
"browsers": ["last 2 versions", "> 0.2%", "maintained node versions", "not dead"],
},
useBuiltIns: "usage",
corejs: 2
},
],
["@babel/preset-react"],
["@babel/preset-typescript"]
];
const plugins = [
["@babel/plugin-syntax-dynamic-import"]
]
module.exports = {
presets,
plugins
};
再執(zhí)行編譯
Version: webpack 4.35.0
Time: 2308ms
Built at: 2019-06-24 4:52:11 PM
Asset Size Chunks Chunk Names
0.bundle.js 1.47 KiB 0 [emitted]
index.html 320 bytes [emitted]
main.bundle.js 3.57 MiB main [emitted] main
Entrypoint main = main.bundle.js
首頁初始加載時沒有包括0.bundle.js的bundle文件
切換到About后玻靡,出現(xiàn)了0.bundle.js的bundle文件的引用
分割代碼
實際開發(fā)中對于node_modules中的庫代碼及程序中提供的通用工具庫的代碼,因為相對于業(yè)務(wù)代碼來說中贝,修改的頻率要小的多囤捻,所以為更好的使用瀏覽器的緩存功能,需要將其分離出來邻寿。分離出來后蝎土,總體代碼大小也會下降。
Webpack4里原有的CommonsChunkPlugin被移除绣否,取而代之的是 optimization.splitChunks 配置項來完成分割工作誊涯。
下面是 splitChunks 配置項的默認(rèn)值
splitChunks: {
chunks: "async",
// 分割獨立chunk的最小大小(單位字節(jié))
minSize: 30000,
// 分割前必須共享chunk的最小塊數(shù)
minChunks: 1,
// 按需加載的最大并行請求數(shù)
maxAsyncRequests: 5,
// 一個入口最大并行請求數(shù)
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
由此可知蒜撮,Webpack 默認(rèn)的分割標(biāo)準(zhǔn)為
- 新 bundle 被兩個及以上模塊引用醋拧,或者來自 node_modules
- 新 bundle 大于 30kb (壓縮之前)
- 異步加載并發(fā)加載的 bundle 數(shù)不能大于 5 個
- 初始加載的 bundle 數(shù)不能大于 3 個
簡單的說,Webpack 會把代碼中的公共模塊自動抽出來淀弹,變成一個包丹壕,前提是這個包大于 30kb,不然 Webpack 是不會抽出公共代碼的薇溃,因為增加一次請求的成本是不能忽視的菌赖。
注意 maxSize 比 maxInitialRequest 和 maxAsyncRequests 具有更高的優(yōu)先級。 實際優(yōu)先級為maxInitialRequest和maxAsyncRequests < maxSize < minSize沐序。
我們這里希望將 node_modules 里的代碼塊獨立出來琉用,因為這里的改動最小。通過splitChunks.cacheGroups我們可以自定義分割標(biāo)準(zhǔn)策幼。
修改 webpack.prod.js 配置邑时,添加 optimization 配置項
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/,
priority: 20,
reuseExistingChunk: true,
}
}
},
runtimeChunk: {
name: entrypoint => `manifest.${entrypoint.name}`
}
},
runtimeChunk 的作用是將包含 chunks 映射關(guān)系的 list單獨從入口文件里提取出來,因為每一個 chunk 的 id 基本都是基于內(nèi)容 hash 出來的特姐,所以你每次改動都會影響它晶丘,如果不將它提取出來的話,等于入口文件每次都會改變。緩存就失效了浅浮。
生產(chǎn)環(huán)境下編譯后
yarn run v1.16.0
$ webpack --config webpack.prod.js
Hash: aa35580d8ecf16c47c23
Version: webpack 4.35.0
Time: 15054ms
Built at: 2019-06-24 6:26:59 PM
Asset Size Chunks Chunk Names
3.7e0873588f0bd55f2579.js 227 bytes 3 [emitted]
3.7e0873588f0bd55f2579.js.map 486 bytes 3 [emitted]
index.html 495 bytes [emitted]
main.d49317ef696d1a92aee1.js 3.47 KiB 0 [emitted] main
main.d49317ef696d1a92aee1.js.map 5.12 KiB 0 [emitted] main
manifest.main.6e467d970b586c9e8edf.js 2.25 KiB 1 [emitted] manifest.main
manifest.main.6e467d970b586c9e8edf.js.map 11.8 KiB 1 [emitted] manifest.main
vendor.10bfb86250c9761c590b.js 150 KiB 2 [emitted] vendor
vendor.10bfb86250c9761c590b.js.map 445 KiB 2 [emitted] vendor
Entrypoint main = manifest.main.6e467d970b586c9e8edf.js manifest.main.6e467d970b586c9e8edf.js.map vendor.10bfb86250c9761c590b.js vendor.10bfb86250c9761c590b.js.map main.d49317ef696d1a92aee1.js main.d49317ef696d1a92aee1.js.map
這樣類似react等類庫被打包到 vendor.10bfb86250c9761c590b.js 中去了沫浆。
我們再進(jìn)一步測試一下,修改下 About.tsx后再重新編譯
yarn run v1.16.0
$ webpack --config webpack.prod.js
Hash: d564931b25d7ebed1c50
Version: webpack 4.35.0
Time: 4123ms
Built at: 2019-06-25 11:14:14 AM
Asset Size Chunks Chunk Names
3.4d7128957ac27d873d2d.js 230 bytes 3 [emitted]
3.4d7128957ac27d873d2d.js.map 493 bytes 3 [emitted]
index.html 495 bytes [emitted]
main.d49317ef696d1a92aee1.js 3.47 KiB 0 [emitted] main
main.d49317ef696d1a92aee1.js.map 5.12 KiB 0 [emitted] main
manifest.main.ae0e4a7ff5acc96f667e.js 2.25 KiB 1 [emitted] manifest.main
manifest.main.ae0e4a7ff5acc96f667e.js.map 11.8 KiB 1 [emitted] manifest.main
verdor.10bfb86250c9761c590b.js 150 KiB 2 [emitted] verdor
verdor.10bfb86250c9761c590b.js.map 445 KiB 2 [emitted] verdor
Entrypoint main = manifest.main.ae0e4a7ff5acc96f667e.js manifest.main.ae0e4a7ff5acc96f667e.js.map verdor.10bfb86250c9761c590b.js verdor.10bfb86250c9761c590b.js.map main.d49317ef696d1a92aee1.js main.d49317ef696d1a92aee1.js.map
可以看到關(guān)于About.tsx的bundle和manifest發(fā)生了變化滚秩,而入口及由 node_modules 打包出來的bundle沒有變化专执,這樣我們就可以使瀏覽器的緩存發(fā)揮作用了。