成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

React 歷史項目維護(hù)與優(yōu)化實踐

toddmark / 1026人閱讀

摘要:本文介紹了作者接手維護(hù)一個中型歷史項目時的一系列改進(jìn)實踐,包括模塊結(jié)構(gòu)拆分業(yè)務(wù)邏輯梳理打包優(yōu)化等。代碼中如菜單名稱結(jié)構(gòu)表單字段名等的各種硬編碼配置分散在各處。最后,在提升面向開發(fā)者的打包體驗方面,本次優(yōu)化中主要實現(xiàn)的是與的解耦。

本文介紹了作者接手維護(hù)一個中型 React 歷史項目時的一系列改進(jìn)實踐,包括模塊結(jié)構(gòu)拆分、業(yè)務(wù)邏輯梳理、Webpack 打包優(yōu)化等。

背景

這是一個 PC 的管理后臺類項目,沒有引入 react-router 和 redux。待維護(hù)的頁面所有模板和邏輯全部在一個千行級的 JSX 中實現(xiàn),包括調(diào)用組件庫、發(fā)送 fetch 請求、切換子頁面狀態(tài)等。并且,該項目實際上并不是單頁應(yīng)用,而是通過 Webpack 區(qū)分多個 entry 的方式實現(xiàn)了多入口頁面。

模塊拆分

在開始實現(xiàn)新增需求前,首先要做的是了解代碼,整理其結(jié)構(gòu)并適當(dāng)?shù)匾圆鸱帜K的形式逐步重構(gòu)之。在這一步中,并不涉及最令人畏懼的【重構(gòu)業(yè)務(wù)邏輯】,而更多地是【更高級的代碼美化】,在完整保留原有代碼邏輯和調(diào)用方式的前提下,利用一些 JS 的技巧,按照單一職責(zé)原則拆分不同的業(yè)務(wù)邏輯代碼到不同的模塊中,以提高【面條代碼】的模塊化程度。這一步處理要解決的主要問題是:

歷史代碼中混雜了 JSX 模板結(jié)構(gòu)、數(shù)據(jù)處理、異步控制、狀態(tài)管理的各種邏輯。

代碼中如菜單名稱結(jié)構(gòu)、表單字段名等的各種硬編碼配置分散在各處。

幾乎全部的業(yè)務(wù)邏輯均在一個扁平的組件中實現(xiàn)。

解決上述問題,并不涉及到具體業(yè)務(wù)邏輯的重寫,而是通過將同類功能提取為獨立模塊,通過一些簡單的語法糖來保證僅更改盡量少的業(yè)務(wù)代碼,就能實現(xiàn)初步的模塊拆分。

針對上述的幾個問題,初步的模塊拆分包括:

包含大多數(shù) React 組件方法的主頁面組件。

包含異步請求的 action 模塊。

包含各種硬編碼配置的 consts 模塊。

包含調(diào)用組件庫中表單等組件的配置文件 model 模塊。

然后就可以一步步將代碼邏輯遷移到新模塊中,在保證頁面的功能不受影響的前提下逐步實現(xiàn)初步的模塊拆分了。這個過程中多次用到的技巧包括:

將執(zhí)行異步請求的組件方法拆分至模塊中,再在構(gòu)造器中 bind 回組件。如一個典型的查詢邏輯:

// main.js
class Demo extends Component {
  fetchData () {
    fetch("...").then(data => {
      // 此處通常有冗長的業(yè)務(wù)邏輯
      this.setState({ data })
    })
  }
}

可將其先拆分至 action.js 模塊中,形如:

// action.js
// 業(yè)務(wù)邏輯完全保留,只是添加了 export function 前綴
export function fetchData () {
  fetch("...").then(data => {
    this.setState({ data })
  })
}

然后在原組件中加載并 bind 該函數(shù),從而實現(xiàn)模塊拆分:

import { fetchData } from "./actions"
 
class Demo extends Component {
  constructor() {
    // 在此 bind 即可
    this.fetchData = fetchData.bind(this)
  }
}

以及,將一些加載時引用了 this 的配置對象封裝至新模塊的工廠函數(shù)中:

render() {
  // 包含冗長表單配置的配置變量
  const demo = {
    // 直接將其提取至新模塊在此會報錯
    value: this.state.xxx
  }
}

新建一個返回 demo 的工廠函數(shù):

// model.js
export const getDemo  = () => ({
  // 在此的業(yè)務(wù)代碼同樣可原封不動地移動
  value: this.state.xxx
})

修改原有位置的調(diào)用邏輯:

import { getDemo } from "./model"

render() {
  // 在調(diào)用工廠函數(shù)時綁定上下文,即可使模塊中 this 指向正確
  const demo = getDemo.call(this)
}

實踐中在這一步完成后,其實已經(jīng)實現(xiàn)【將千行級代碼拆分至若干個百行級的模塊,每個模塊均僅包含類似的邏輯功能】了。

業(yè)務(wù)梳理

在初步整理模塊后,對代碼結(jié)構(gòu)也有了初步的了解,此時可以開始添加一些新的業(yè)務(wù)需求了。這時,對于與新需求相關(guān)的原有代碼,可以在理解基礎(chǔ)上進(jìn)行梳理與局部的重構(gòu),以實現(xiàn)新功能(注意這時重構(gòu)是為了實現(xiàn)新功能,而非重寫原有代碼以實現(xiàn)相同功能)。

這一步主要需要解決的問題是:

原代碼中有較多晦澀的 if-else 控制流邏輯,包含對某些狀態(tài)的組合判斷,這對新加入業(yè)務(wù)代碼會有一定的障礙。

在 JSX 中大量【嵌套的三目表達(dá)式】長度很長且不易讀(這實際上是 JSX 相對模板天生的問題),這也造成了一定的困擾。

由于業(yè)務(wù)邏輯的復(fù)用價值較低,這里較難通過代碼的形式給出【最佳實踐】的代碼,但通用的處理模式可總結(jié)如下:

通過一些簡單的 log 來判斷一個事件觸發(fā)流程中,基本的代碼調(diào)用和執(zhí)行順序。

對執(zhí)行過程中遇到的組件狀態(tài),在 React 開發(fā)工具中確認(rèn) state / props 執(zhí)行前后的變化,確定【某段業(yè)務(wù)邏輯所依賴的組件狀態(tài),及其觸發(fā)前后的組件狀態(tài)】

以【編寫輸入新需求下輸入狀態(tài),輸出新需求下輸出狀態(tài)】為目標(biāo),維護(hù)并編寫新業(yè)務(wù)邏輯代碼。

新邏輯完成后,逐步注釋并最終替換掉老代碼,漸進(jìn)地實現(xiàn)業(yè)務(wù)需求。

在這一步達(dá)到較高的完善程度后,可以重新審視新增的代碼段做局部重構(gòu),或提取一些可復(fù)用的邏輯到上一步中的相應(yīng)模塊中。到這一步為止,即可基本上將老項目像個人起手的項目一樣做到較為輕車熟路的開發(fā)維護(hù)了。

Webpack 優(yōu)化

在業(yè)務(wù)需求按時完成的前提下,才有必要進(jìn)行這一步的優(yōu)化。對一個配置文件多達(dá)數(shù)百行的穩(wěn)定期項目,切換當(dāng)時的 Webpack 1 到 Webpack 2 難度較大,但相應(yīng)的意義卻并不大。因此,在構(gòu)建方向上的優(yōu)化策略最后以這幾條為主:

分析多頁面的公共依賴配置,優(yōu)化公共依賴提取,去除冗余依賴。

修復(fù)已知問題。

優(yōu)化構(gòu)建速度。

首先,在優(yōu)化公共依賴方面,難點并不是【如何更改公共依賴】,而是如何獲知【有哪些依賴需要被提取為公共依賴】。在這方面,需要的是一個查看各 Bundle 內(nèi)容及尺寸的可視化工具,可以使用 webpack-bundle-analyzer 這一 Webpack 插件來實現(xiàn)。使用該插件的方式也很簡單,直接將其添加在 Webpack 的 plugins 配置中,重新執(zhí)行打包命令即可。打包成功后,會彈出瀏覽器窗口展示各 Bundle 的公共依賴,如下圖是優(yōu)化前的公共依賴配置:

可以發(fā)現(xiàn)原始的依賴配置中,位于圖中角落的 common 包僅包括了原始的 React,而組件庫、lodash、moment 等依賴在每個頁面包中都重復(fù)出現(xiàn)了。因此,在 Webpack 的 entry 配置字段中,為 common 包添加 ["babel-polyfill", "lodash", "moment"] 等依賴名后,即可實現(xiàn)公共依賴的提取。

實際上,提取公共依賴并不能減少每個頁面最終的打包輸出體積。只有去除冗余依賴,才能直接影響頁面最終的包大小。那么這樣的冗余依賴是否存在呢?答案是肯定的。在排查過程中發(fā)現(xiàn),導(dǎo)入 moment 這一非常常用的時間庫時,會默認(rèn)導(dǎo)入其對應(yīng)的多語言依賴 locale 包,而這對當(dāng)前項目是完全無用的。對于這種【依賴本身依賴了冗余依賴】的情形,Webpack 同樣提供了優(yōu)化方案。在 Plugins 中添加如下的一行即可:

new webpack.IgnorePlugin(/^./locale$/, /moment$/)

這一行代碼能夠直接減少開發(fā)環(huán)境 300K 的包大??!在進(jìn)行了依賴優(yōu)化后,得到的包體積可視化為下圖:

可以發(fā)現(xiàn),common 的大小得到了大幅增加,而各個頁面的業(yè)務(wù)包體積則減少了 2/3 以上。不過,在這個優(yōu)化方向上并沒有做到極致。由于 Webpack 1 不支持原生的 Tree Shaking 功能,導(dǎo)致了 UI 組件庫即便通過 import { xxx } 語法引入,最終還是會將整個組件庫導(dǎo)入公共依賴包中,沒有做到按需加載。而相應(yīng)的 import 插件又存在配置上的不便,其結(jié)果是最終沒有在這個項目中實現(xiàn) UI 組件庫的按需加載。當(dāng)然,隨著 Webpack 2 的普及,新項目中這應(yīng)當(dāng)不會成為問題。

接下來,在修復(fù)已知問題方面,優(yōu)化過程中修復(fù)了兩個較為常見的問題:common 包隨業(yè)務(wù)包變更而變更的問題;hash 值每次全量變更的問題。

在直接通過 CommonsChunkPlugin 拆分 common 包的配置方式下,每個頁面最終使用的包都是 common 包和業(yè)務(wù)包兩個。這時,在頁面 A 中修改業(yè)務(wù)邏輯,會造成 common 包的細(xì)微變動,導(dǎo)致新的打包文件中,common 包雖然沒有源碼變更,卻隨著業(yè)務(wù)包的變更而變更了。這會導(dǎo)致每次版本更新時包括 common 在內(nèi)的所有包都會被全量更新,沒有實現(xiàn)按需的更新。

解決方案是,在 CommonsChunkPlugin 的配置中,將 name 字段改為 names 字段,提供 ["common", "manifest"] 兩個公共依賴入口。這樣,在業(yè)務(wù)包變動時,只有 manifest 會隨之變動,而 common 的內(nèi)容不會受到影響,這也就實現(xiàn)了真正意義上的按需更新,更大限度地利用瀏覽器緩存。雖然這一實踐實際上是 Webpack 2 文檔中官方的推薦做法,但 Webpack 1 也完全支持。

另一個問題是,每次打包的產(chǎn)物文件中雖然都附帶了一個 hash 值,但對所有打包文件,該值都是一樣的。這同樣會導(dǎo)致僅有某個 bundle 變更時,全量的生產(chǎn)包名稱變更,造成緩存的失效。相應(yīng)的解決方案也很簡單:將 output 配置字段中的 [hash] 改為 [chunkhash],即可為每個包添加不同的 hash 值。

最后,在提升面向開發(fā)者的打包體驗方面,本次優(yōu)化中主要實現(xiàn)的是 lint 與 Webpack 的解耦。在使用 IDE 開發(fā)時,lint 的引入較為繁瑣,因此當(dāng)時采用的是將 lint 作為 Webpack 的 loader 形式引入,在每次增量打包后執(zhí)行 lint,對存在不符合風(fēng)格指南的代碼在終端報錯并不予編譯通過的策略。這個模式兼容性繞過了編輯器和 IDE 的配置,因而更加通用,但問題在于:

每次打包都需要重復(fù)的 lint 過程,降低了打包速度。

lint 規(guī)則較嚴(yán)格時,調(diào)試過程受到了較大的限制。如 class 方法必須存在對 this 的引用、函數(shù)參數(shù)必須全部被使用、不允許 return 后存在業(yè)務(wù)邏輯等 lint 策略,它們雖然確實能提高代碼質(zhì)量,但在調(diào)試過程中局部存在這樣的代碼非常常見,禁止編譯這些不存在語法問題的代碼,對開發(fā)效率存在較大的影響。

因而,在優(yōu)化中果斷去除了 Webpack 的 lint 配置,轉(zhuǎn)而通過 VSCode 等編輯器的 lint 插件實現(xiàn)開發(fā)過程中的動態(tài) lint 提示和自動美化。另外,對 Webpack 每次打包的輸出格式也進(jìn)行了優(yōu)化,去除了較多冗余的包信息 log 內(nèi)容,僅保留每次打包的 hash 信息即可。最后的開發(fā)體驗與新 Webpack 2 項目相近,實現(xiàn)了一定的開發(fā)效率提升。

總結(jié)

在維護(hù)過程中,首先還是理解已有業(yè)務(wù)代碼,然后循序漸進(jìn)地走改良路線,而不應(yīng)以【老代碼好亂】為理由貿(mào)然重寫,這會存在很大的風(fēng)險。雖然 React 本身設(shè)計較為松散,使得開發(fā)者更容易產(chǎn)出較無序的代碼,但 JS 目前的模塊和 OO 機(jī)制為無需重寫的填坑提供了很大的幫助,實踐中最后本質(zhì)上重寫的也只有新需求相關(guān)的部分,已有的邏輯得到了盡可能的保留和復(fù)用。而性能優(yōu)化則屬于錦上添花的【折騰向】內(nèi)容,優(yōu)先級較低,可以在時間相對寬松的時候處理,優(yōu)化方式上也有較多的工具和插件支持,相對需要實際編碼的業(yè)務(wù)而言,難度較低。

希望以上實踐經(jīng)驗對于更多開發(fā)者的踩坑 / 填坑路能夠有所幫助。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/83198.html

相關(guān)文章

  • 前端每周清單第 49 期:Webpack 4 Beta 嘗鮮,React Windowing s

    摘要:盡管等待了多年,但是最終還是發(fā)布了正式版本與上一個版本相比未有重大變化,主要著眼于部分錯誤修復(fù)與提升。能夠?qū)惒胶瘮?shù)移入獨立線程中,可以看做函數(shù)的單函數(shù)簡化版。不過需要注意的是,僅支持純函數(shù),其會在獨立的作用域中運(yùn)行這些函數(shù)。 showImg(https://segmentfault.com/img/remote/1460000013038757); 前端每周清單專注前端領(lǐng)域內(nèi)容,以對...

    muzhuyu 評論0 收藏0
  • android知識大總結(jié) - 收藏集 - 掘金

    摘要:中簡單搞定接口訪問,以及簡析掘金最近總結(jié)的一些經(jīng)驗,對于或中使用接口的一些心得。這里,本文將數(shù)據(jù)結(jié)構(gòu)之學(xué)習(xí)總結(jié)掘金前言前面介紹了的數(shù)據(jù)結(jié)構(gòu),今天抽空學(xué)習(xí)總結(jié)一下另一種數(shù)據(jù)結(jié)構(gòu)。淺析事件傳遞掘金中的事件傳遞主要涉及三個方法和。 Android 系統(tǒng)中,那些能大幅提高工作效率的 API 匯總(持續(xù)更新中...) - 掘金前言 條條大路通羅馬。工作中,實現(xiàn)某個需求的方式往往不是唯一的,這些不...

    luodongseu 評論0 收藏0
  • 前端每周清單第 50 期: AngularJS and Long Term Support, Web

    摘要:在該版本發(fā)布之后,開發(fā)團(tuán)隊并不會繼續(xù)發(fā)布新的特性,而會著眼于進(jìn)行重大的錯誤修復(fù)。發(fā)布每六個星期,團(tuán)隊就會創(chuàng)建新的分支作為發(fā)布通道,本文即是對新近發(fā)布的版本進(jìn)行簡要介紹。 showImg(https://segmentfault.com/img/remote/1460000013229009); 前端每周清單專注前端領(lǐng)域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點;分為新聞熱...

    DobbyKim 評論0 收藏0
  • 方案設(shè)計--如何看待前端框架選型 ?

    摘要:純前端開發(fā)主要是針對靜態(tài)頁面。自主權(quán)最大,正常是使用進(jìn)行輔助開發(fā),上線等。大致原因使用是為了和端的保持同步。四總結(jié)對于比較正式的項目,前端技術(shù)選型策略一定是產(chǎn)品收益最大化,用戶在首位。 對于前端團(tuán)隊,可以實現(xiàn)企業(yè)受益最大化要點。 一、技術(shù)選型的策略 1、保證產(chǎn)品質(zhì)量 (1)功能穩(wěn)健:網(wǎng)頁不白屏,不錯位,不卡死;操作正常;數(shù)據(jù)精準(zhǔn)。 (2)體驗優(yōu)秀:加載體驗,交互體驗,視覺體驗,無障礙訪...

    gnehc 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<