精讀《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
重點在於阻礙了內部狀態的使用,移除了生命周期,所以提高了組件的可控性,也就拓寬了組件的使用場景。
受控與非受控組件都有其適用場景,像非常基礎的底層組件庫,往往傾向提供兩套機制,通過 value
與 defaultValue
決定是否受控。擁有這樣能力的組件源碼就沒法通過 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:前端开发 |