標籤:

精讀《React 代碼整潔之道》

本期精讀的文章是:React 代碼整潔之道。

1 引言

編程也是藝術行為,當我們思考代碼復用、變數命名時,就是在進行藝術思考。

可能這篇文章沒法提高面試能力、開發效率,因為涉及的內容都是 「軟能力」。但如果與我一樣,時常害怕自己代碼不夠優雅,那就在茶餘飯後看看這篇文章,也許,可以解決一部分你心中的困惑。

2 內容概要

作者整理了幾個好的思維習慣,嘗試認同它,再看看如何實踐。

不冗餘

避免重複代碼段,對 JSX 同理:

// Dirtynconst MyComponent = () => (n <div>n <OtherComponent type="a" className="colorful" foo={123} bar={456} />n <OtherComponent type="b" className="colorful" foo={123} bar={456} />n </div>n);nn// Cleannconst MyOtherComponent = ({ type }) => (n <OtherComponent type={type} className="colorful" foo={123} bar={456} />n);nconst MyComponent = () => (n <div>n <MyOtherComponent type="a" />n <MyOtherComponent type="b" />n </div>n);n

但也不要過度優化,過度優化和搞破壞沒什麼區別。

可預測、可測試

如果使用 Jest 測試,可以考慮截圖測試插件:Jest Image Snapshot

自我解釋

儘可能減少代碼中的注釋。可以通過讓變數名更語義化、只注釋複雜、潛在邏輯,來減少注釋量,同時也提高了可維護性,畢竟不用總在代碼與注釋之間同步了。

// Dirtynconst fetchUser = (id) => (n fetch(buildUri`/users/${id}`) // Get User DTO record from REST APIn .then(convertFormat) // Convert to snakeCasen .then(validateUser) // Make sure the the user is validn);nn// Cleannconst fetchUser = (id) => (n fetch(buildUri`/users/${id}`)n .then(snakeToCamelCase)n .then(validateUser)n);n

上面的例子,方法 convertFormat 含義是 「轉換格式」,太過於籠統,以至於不得不添加註釋。如果換成 snakeToCamelCase(轉換為駝峰風格),這個名字就解釋了自己的功能。

斟酌變數名

布爾值或者返回值是布爾類型的函數,命名以 is has should 開頭:

// Dirtynconst done = current >= goal;n// Cleannconst isComplete = current >= goal;n

函數以其效果命名,而不是怎麼做的來命名

// Dirtynconst loadConfigFromServer = () => {n ...n};n// Cleannconst loadConfig = () => {n ...n};n

很多時候我也經常犯這種錯誤,畢竟寫代碼的時候總要考慮實現,一不小心就將實現的方式帶入了函數名中。

遵循設計模式

這裡的設計模式,並不是指工程上的,而是更廣泛的開發中的設計模式,比如 「你應該使用 tslint 校驗代碼格式」 「typescript 開啟 stricts 模式」 「編寫一個 React 函數,應當將 React 作為 peerDependency」 等等(當然,不要隨意設置 peerDependency也是一種江湖約定)。

對於 React,遵循以下幾個最佳實踐:

  • 單一責任原則, 確保每個功能都完整完成一項功能,比如更細粒度的組件拆分,同時也更利於測試。
  • 不要把組件的內部依賴強加給使用方。
  • lint 規則盡量嚴格。

根據我的體驗,尤為痛恨違背第二條的組件,比如當 React 組件使用了數據流,但必須依賴項目初始化該數據流才能執行,如果不是被生活所迫,我才不會使用這種組件。

第三條也一樣,如果你是一個知名輪子的作者,請毫不留情的使用最嚴格的 lint 規則。如果使用者的 lint 規則比你還嚴格,你的組件將無法使用。

考慮到以上幾點並不會降低編碼速度

編寫整潔的代碼在開始一定會放慢開發速度,因為你需要轉變自己的思維模式,但隨著不斷迭代,它的帶來的效率提升會逐漸彌補前面的損失,並不斷帶來開發效率的提升。

寫組件庫也是同理,用腳寫固然能快速完成,但後續往往要重構掉。我很羨慕函數式工作環境的開發者,他們幾乎只要為每個功能寫一遍,剩下的就是記住並調用它。

在 React 中的實踐

略過幾個沒意思的例子。。

在 React 使用 defaultProps 代替在代碼中動態判斷

顯然,利用 React 組件的規則,將入參的默認值預先定義好是最高效的。但順帶一句,如果在 ts 最嚴格的 stricts 模式里,依然會報錯:變數可能為定義。這是因為 defaultProps 依然是個約定,而不能通過強類型推導出,目前還沒有更優雅的解決思路。

渲染與判斷邏輯分開

// Dirtynclass User extends Component {n state = { loading: true };nn render() {n const { loading, user } = this.state;n return loadingn ? <div>Loading...</div>n : <div>n <div>n First name: {user.firstName}n </div>n <div>n First name: {user.lastName}n </div>n ...n </div>;n }nn componentDidMount() {n fetchUser(this.props.id)n .then((user) => { this.setState({ loading: false, user })})n }n}nn// Cleannimport RenderUser from ./RenderUser;nclass User extends Component {n state = { loading: true };nn render() {n const { loading, user } = this.state;n return loading ? <Loading /> : <RenderUser user={user} />;n }nn componentDidMount() {n fetchUser(this.props.id)n .then(user => { this.setState({ loading: false, user })})n }n}n

邏輯與渲染分離,便於維護,其次便於測試。

當然有人可能會問 「就算邏輯與渲染分離了,但組成的大組件不還是邏輯耦合的嗎」,對,這就像函數單一指責一樣,render是過程代碼,但過程中涉及到的邏輯,分配給單一指責的渲染組件渲染,如果把邏輯與渲染寫在一起,就類似一個函數把功能全做完,這樣做顯然諸事不利。

提倡無狀態組件

// Dirtynclass TableRowWrapper extends Component {n render() {n return (n <tr>n {this.props.children}n </tr>n );n }n}nn// Cleannconst TableRowWrapper = ({ children }) => (n <tr>n {children}n </tr>n);n

性能是一個原因,原文比較強調性能與代碼量。我認為 stateless 重點在於阻礙了內部狀態的使用,移除了生命周期,所以提高了組件的可控性,也就拓寬了組件的使用場景。

受控與非受控組件都有其適用場景,像非常基礎的底層組件庫,往往傾向提供兩套機制,通過 valuedefaultValue 決定是否受控。擁有這樣能力的組件源碼就沒法通過 stateless 寫,所以無狀態組件的面向對象並不是基礎底層組件,而且這些基礎組件也沒必要完全無狀態,兩者都提供是最好的選擇。

說到這,也就是考慮到成本問題,那麼無狀態組件也就更適合上層具有業務含義的組件。頁面級別組件狀態太多,不適合,所以我認為無狀態組件比較適合 Wrapper 層,也就是對基礎組件包裹並增強業務能力這一層。

解構

// Dirtynconst splitLocale = locale.split(-);nconst language = splitLocale[0];nconst country = splitLocale[1];nn// Cleannconst [language, country] = locale.split(-);n

ES6 新增的語法可以提升不少代碼可讀性,需要刻意訓練去培養這個習慣。

3 精讀

本周精讀已經融於內容概要中 ^_^。最後推薦在 typescript 中開啟 strict 模式,強制使用良好的開發習慣。

// BadnonChange(value => console.log(value.name))n// DirtynonChange((value) => {n if (!value) {n value = {}n }n console.log(value.name)n})n// CleannonChange((value = {}) => console.log(value.name))n// CleannonChange(value => console.log(value!.name))n

不要信任任何回調函數給你的變數,它們隨時可能是 undefined,使用初始值是個不錯的選擇,但有的時候初始值沒什麼意義,使用 !. 語法可以安全的訪問屬性,是時候拋棄 _.get 了。

4. 總結

我要回去重構代碼了,你呢?

更多討論

討論地址是:精讀《React 代碼整潔之道》 · Issue #46 · dt-fe/weekly

如果你想參與討論,請點擊這裡,每周都有新的主題,每周五發布。


推薦閱讀:

尤雨溪的 Live 說了哪些技術名詞?(上篇)
我寫我的 md,你騙你的 star,為什麼要在我的 GitHub 上做推廣!!
react-router 中的history
你不知道的前端演算法之熱力圖的實現

TAG:前端开发 |