04-深入React開發(fā)

什么是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ù)控件。Linkreact-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ā)揮作用了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郁油,一起剝皮案震驚了整個濱河市本股,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桐腌,老刑警劉巖痊末,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哩掺,居然都是意外死亡凿叠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門嚼吞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盒件,“玉大人,你說我怎么就攤上這事舱禽〕吹螅” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵誊稚,是天一觀的道長翔始。 經(jīng)常有香客問我,道長里伯,這世上最難降的妖魔是什么城瞎? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮疾瓮,結(jié)果婚禮上脖镀,老公的妹妹穿的比我還像新娘。我一直安慰自己狼电,他們只是感情好蜒灰,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肩碟,像睡著了一般强窖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上削祈,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天翅溺,我揣著相機(jī)與錄音,去河邊找鬼。 笑死未巫,一個胖子當(dāng)著我的面吹牛窿撬,可吹牛的內(nèi)容都是我干的启昧。 我是一名探鬼主播叙凡,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼密末!你這毒婦竟也來了握爷?” 一聲冷哼從身側(cè)響起墩邀,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤薄坏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后范咨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刹碾,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡燥撞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了迷帜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片物舒。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖戏锹,靈堂內(nèi)的尸體忽然破棺而出冠胯,到底是詐尸還是另有隱情,我是刑警寧澤锦针,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布荠察,位于F島的核電站,受9級特大地震影響奈搜,放射性物質(zhì)發(fā)生泄漏悉盆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一馋吗、第九天 我趴在偏房一處隱蔽的房頂上張望舀瓢。 院中可真熱鬧,春花似錦耗美、人聲如沸京髓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堰怨。三九已至,卻和暖如春蛇摸,著一層夾襖步出監(jiān)牢的瞬間备图,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留揽涮,地道東北人抠藕。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蒋困,于是被迫代替她去往敵國和親盾似。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 函數(shù)是面向過程的雪标,函數(shù)的調(diào)用不需要主體零院,而方法是屬于對象的,調(diào)用方法需要一個主體-即對象村刨。 npm install...
    Gukson666閱讀 465評論 0 3
  • 在這個教程里告抄,我們會從一個例子React應(yīng)用開始學(xué)習(xí)react-router-dom。其中你會學(xué)習(xí)如何使用Link...
    uncle_charlie閱讀 34,711評論 1 40
  • React-Router v4 1. 設(shè)計理念1.1. 動態(tài)路由1.2. 嵌套路由1.3. 響應(yīng)式路由 2. 快速...
    wlszouc閱讀 8,547評論 0 14
  • 我的愿望是做一個有彈性的人嵌牺,這樣的人就算能清清楚楚看得到他人的不明事理打洼,不思進(jìn)取,乖戾叛逆逆粹,依舊能夠真誠坦蕩待之募疮。...
    海婉姑姑閱讀 244評論 0 0
  • 隨便說說看,不清楚 板凳子上
    紫檀澗閱讀 86評論 0 1