如何設計 React 代碼結構?

2020-12-18 CSDN

怎樣設計一個項目的文件和組件的結構、甚至是某個組件的內部結構?這個問題永遠沒有正確答案。

作者 | Mathilde Wrstad

譯者 | 彎月,責編 | 郭芮

以下為譯文:

像許多話題一樣,許多人都持有不同的觀點,而且解決方案也有很多。

可能你自己的意見會受到個人經歷的影響,比如你對於何謂易讀、何謂易解析,甚至何謂漂亮都有自己的看法。當你加入一個團隊時,通常你們都需要在這些結構問題上達成一致,這並不是一件易事。

這類的討論就好像兩個人爭論哪種顏色最好看,是藍色還是紅色。他們都會主張自己的意見,闡明自己的立場,然後固執己見。他們永遠也不會折衷,比如認為紫色最好看。

因此,我並不期望能夠在這個問題上達成任何共識。在本文中,我只會介紹在設計React代碼結構方面的個人喜好,以及其中的原因。希望你能從中借鑑一二,或者至少可以從不同的角度理解這個問題。

拼圖

首先,我想簡單地介紹一下我是哪種程式設計師。在寫代碼時,我會需要構建的東西看成基本的組成部分,就像拼圖一樣。我編寫的每個功能、每個函數、每個組件都是一副拼圖中的一部分。

我很喜歡將我做的東西可視化。我喜歡用畫圖的方式解釋問題時,我希望能夠將用戶真正使用的UI功能反映到代碼中。

對於我而言,還有一件重要的事情,那就是能迅速看到整體樣貌,並儘可能簡單地理解組件的功能。這兩個目的都可以通過選擇正確的命名方式以及可視化結構來實現。

聖誕老人數位化

在這篇文章中我假想了一個項目,名叫聖誕老人的數字願望單。所有人都可以使用這個項目,創建用戶,添加願望,還可以查看個人信息。

下面是組件 EditMyInformationToSanta 的代碼,顯然它需要重構。我們可以通過這個組件添加你希望獲得的聖誕禮物。

import React, { useState } from 'react';import { saveMyInformationToSanta } from '../../api/santa-api';const EditMyInformationToSanta = () => { const [name, setName] = useState(''); const [age, setAge] = useState(''); const [gender, setGender] = useState(null); const [address, setAddress] = useState(''); const [hasFireplace, setHasFireplace] = useState(null); const [naughtyOrNice, setNaughtyOrNice] = useState(null); const [letterToSanta, setLetterToSanta] = useState(''); const [wish, setWish] = useState(''); const [wishList, setWishList] = useState([]); const submitMyInformationToSanta = async event => { event.preventDefault(); await saveMyInformationToSanta({ name, age, gender, address, hasFireplace, naughtyOrNice, letterToSanta, wishList, }); }; return (<div><h1>Hi, Santa! This is me</h1><form><h2>About me:</h2><label><span>My name is:</span><inputtype="text"value={name}placeholder="Write your name"onChange={event => setName(event.target.value)} /></label><label><span>My age is:</span><inputtype="text"value={age}placeholder="Tell Santa your age"onChange={event => setAge(event.target.value)} /></label><fieldset><legend>I am a...</legend><label><inputtype="radio"value="boy"checked={gender === 'boy'}onChange={event => setGender(event.target.value)} /> Boy</label><label><inputtype="radio"value="girl"checked={gender === 'girl'}onChange={event => setGender(event.target.value)} /> Girl</label></fieldset><label><span>My address is:</span><inputtype="text"value={address}placeholder="Where do you live?"onChange={event => setAddress(event.target.value)} /></label><fieldset><legend>I have a fireplace?</legend><label><inputtype="radio"value={true}checked={hasFireplace}onChange={event => setHasFireplace(event.target.value)} /> Yes</label><label><inputtype="radio"value={false}checked={hasFireplace === false}onChange={event => setHasFireplace(event.target.value)} /> No</label></fieldset><fieldset><legend>This year I have been naughty or nice?</legend><label><inputtype="radio"value="naughty"checked={naughtyOrNice === 'naughty'}onChange={event => setNaughtyOrNice(event.target.value)} /> Naughty</label><label><inputtype="radio"value="nice"checked={naughtyOrNice === 'nice'}onChange={event => setNaughtyOrNice(event.target.value)} /> Nice</label></fieldset><div><h2>My wishes this year:</h2><label><span>I want:</span><inputtype="text"value={wish}placeholder="Write a wish"onChange={event => setWish(event.target.value)} /></label><buttontype="button"value="Add wish"onClick={() => { setWishList(wishList.concat(wish)); setWish(''); }} /><h3>My wish list:</h3><ul> {wishList.map(wish => (<li>{wish}</li> ))}</ul></div><div><h2>Santa, I also want to tell you...</h2><textareaplaceholder="Do you want to say something to Santa?"onChange={event => setLetterToSanta(event.target.value)} value={letterToSanta} /></div><buttontype="submit"onClick={submitMyInformationToSanta} /></form></div> );};export default EditMyInformationToSanta;

組件本身並沒有複雜的內容,但由於這個表單十分龐大,包含很多信息,因此文件非常大,或者說非常長。為了改進這一點,我將代碼分割成了多個組件。

有幾個問題很明顯,比如重複的輸入框和單選按鈕的布局,所以我們可以將此作為切入點。如下便是重構後的代碼:

import React, { useState } from'react';import { saveMyInformationToSanta } from'../../api/santa-api';import TextInputWithLabel from'./TextInputWithLabel';import RadioToggle from'./RadioToggle';const EditMyInformationToSanta = () => {const [name, setName] = useState('');const [age, setAge] = useState('');const [gender, setGender] = useState(null);const [address, setAddress] = useState('');const [hasFireplace, setHasFireplace] = useState(null);const [naughtyOrNice, setNaughtyOrNice] = useState(null);const [letterToSanta, setLetterToSanta] = useState('');const [wish, setWish] = useState('');const [wishList, setWishList] = useState([]);const submitMyInformationToSanta = asyncevent => {event.preventDefault();await saveMyInformationToSanta({ name, age, gender, address, hasFireplace, naughtyOrNice, letterToSanta, wishList, }); };return ( <div> <h1>Hi, Santa! This is me</h1> <form> <h2>About me</h2> <TextInputWithLabel label="My name is:" placeholder="Write your name"value={name} onChange={event => setName(event.target.value)} /> <TextInputWithLabel label="My age is:" placeholder="Tell Santa your age"value={age} onChange={event => setAge(event.target.value)} /> <RadioToggle question="I am a..." label1="Boy" toggleValue1="boy" label2="Girl" toggleValue2="girl"value={gender} onChange={event => setGender(event.target.value)} /> <TextInputWithLabel label="My address is:" placeholder="Where do you live?"value={address} onChange={event => setAddress(event.target.value)} /> <RadioToggle question="I have a fireplace?" label1="Yes" toggleValue1={true} label2="No" toggleValue2={false}value={hasFireplace} onChange={event => setHasFireplace(event.target.value)} /> <RadioToggle question="This year I have been naughty or nice?" label1="Naughty" toggleValue1="naughty" label2="Nice" toggleValue2="nice"value={naughtyOrNice} onChange={event => setNaughtyOrNice(event.target.value)} /> <div> <h2>My wishes this year:</h2> <TextInputWithLabel label="I want:" placeholder="Write a wish"value={wish} onChange={event => setWish(event.target.value)} /> <button type="button"value="Add wish" onClick={() => { setWishList(wishList.concat(wish)); setWish(''); }} /> <h3>My wish list:</h3> <ul> {wishList.map(wish => ( <li>{wish}</li> ))} </ul> </div> <div> <h2>Santa, I also want to tell you...</h2> <textarea placeholder="Do you want to say something to Santa?" onChange={event => setLetterToSanta(event.target.value)}value={letterToSanta} /> </div> <button type="submit" onClick={submitMyInformationToSanta} /> </form> </div> );};export default EditMyInformationToSanta;

注意,儘管我不確定輸入框和單選按鈕是否會在應用程式的其他地方使用,但我還是決定將它們移動到單獨的文件中。這些文件應該保存在儘可能靠近原來組件的位置,以方便使用。

保持組件分離!

許多開發人員喜歡將類似上例中的局部組件放到一個文件中,但我傾向於一個文件最多只保存一個組件。對於我來說,在理解代碼全貌的時候,一個文件只包含一個組件的形式比一個文件中包含多個組件更容易。

在本例中,差別也許不是太大,由於我要重構的組件並不是太大,但組件規模增大後問題就會浮現。幾個星期後你就不得不在文件中來回上下滾動才能找到「根」組件,這會非常麻煩。

我喜歡簡單的規則,這樣就可以避免重構時的內部討論,也不需要討論什麼時候應該將組件放到單獨的文件中。

按照域提取

回到我們的例子。儘管我們將重複的代碼提取到了可重用的組件中,我認為依然還有可以重構的地方。我想按照代碼的功能,重構同一功能的代碼。在下面的例子中,我把表單每一部分的組件都提取出來,分別是AboutMe、LetterToSanta 和 MyWishes。

import React, { useState } from'react';import { saveMyInformationToSanta } from'../../api/santa-api';import AboutMe from'./AboutMe';import MyWishes from'./MyWishes';import LetterToSanta from'./LetterToSanta';const EditMyInformationToSanta = () => {const [me, setMeState] = useState({name: '',age: '',address: '',gender: null,hasFireplace: null,naughtyOrNice: null, });const [letterToSanta, setLetterToSanta] = useState('');const [wish, setWish] = useState('');const [wishList, setWishList] = useState([]);const submitMyInformationToSanta = async event => { event.preventDefault();await saveMyInformationToSanta({ ...me, letterToSanta, wishList, }); };return ( <div> <h1>Hi, Santa! This is me</h1> <form> <AboutMe me={me} onMeChange={updatedMeState => setMeState(updatedMeState)} /> <MyWishes wish={wish} wishList={wishList} onWishChange={setWish} onWishListChange={setWishList} /> <LetterToSanta letterToSanta={letterToSanta} onLetterChange={setLetterToSanta} /> <button type="submit" onClick={submitMyInformationToSanta} /> </form> </div> );};export default EditMyInformationToSanta;

項目的完整代碼在此(https://github.com/mathilwa/WishesToSanta)。

現在,這個目錄中除了文件 EditMyInformationToSanta.jsx 之外,還有一堆簡單的組件文件。每個文件都很小,很容易單獨理解。

表單每個部分對應的組件都僅限於 EditMyInformationToSanta.jsx 文件使用,因此我將它們放到了同一個目錄下。重構的目的是為了讓主文件看起來乾淨、容易理解。我們也可以將其他相關的文件放在這個目錄中,比如樣式、圖像、文本、工具函數或其他資源等。

域 vs 組件

應用程式會不斷增長,例如利用 SantaLocation API 來實時跟蹤聖誕老人的位置,或者顯示前十個最期待的禮物,總有一天你會遇到重用表現層組件的情況。我喜歡將代碼分成兩個目錄:/components 和 /domain,目的是將表現層組件分離開來。

/domain 包含應用程式的域邏輯。EditMyInformationToSanta 以及相關的文件就在這個目錄下。

/components 包含整個項目中所有可重用的組件。典型的例子就是 TextInputWithLabel,我們將它從 /domain/edit-my-information-to-santa 目錄移動到 /components/text-input-with-label。與 /domain 目錄一樣,這個目錄也可以包含相關文件,如文本、樣式等。

保持整潔

我們剛才一直在討論怎樣設計React代碼的布局。總結起來就是:

我喜歡將表現層邏輯提取到單獨的組件中,這樣利於重用,也利於簡化根組件。

我將每個組件放到各自的文件中,即使用戶界面變得十分複雜,也應該遵循這一條原則。原因在於,每個文件僅包含一個組件更加方便理解。每個文件都很短,因此可以看到整體情況,而好的組件命名(以及文件命名)可以幫助我們理解每個組件的作用。

將組件分割成一次性的域邏輯組件和可重用的組件,可以讓瀏覽代碼和刪除代碼更加容易。

作為一名諮詢師,你需要花大量時間去閱讀別人的代碼。確保閱讀代碼的過程順利非常重要。大多數時間我都在修改或擴展已有代碼,而簡單、健壯、易於理解的代碼結構可以幫你更快地完成工作。

接受他人的意見

最後我想用本文開頭的觀點來結束本文。團隊中不同的人有不同的選擇和喜好,接受這一點並不容易。但一定要接受嗎?我想說,是的。並不是說你要接受關於什麼是美觀,而是要接受在項目中使用某種代碼風格和結構,即使有些人並不完全同意。

讓整個團隊都在「整潔」或「易閱」問題上達成一致是不現實的。最重要的是,每個人都會表達自己的意見,每個人都會強調為何使用這種方式,並積極討論團隊應該使用這種方式。團隊中的大多數人(即使不是全部)都會發表觀點,但我認為,最好能夠接受統一的代碼風格和結構,而不是為了滿足每個人的喜好而混合多種風格。

不管最後的選擇是什麼,接受統一的解決方案,才能在項目中創建整潔、乾淨、易於理解且標準化的代碼風格。

原文:https://react.christmas/2019/22

本文為 CSDN 翻譯,轉載請註明來源出處。

相關焦點

  • 《精通react/vue組件設計》之快速實現一個可定製的進度條組件
    而是要根據已有前端的開發經驗,總結出一套自己的高效開發的方法.作為數據驅動的領導者react/vue等MVVM框架的出現,幫我們減少了工作中大量的冗餘代碼, 一切皆組件的思想深得人心.所以, 為了讓工程師們有更多的時間去考慮業務和產品迭代,我們不得不掌握高質量組件設計的思路和方法.所以筆者將花時間去總結各種業務場景下的組件的設計思路和方法,並用原生框架的語法去實現各種常用組件的開發,希望等讓前端新手或者有一定工作經驗的朋友能有所收穫
  • 如何編寫漂亮的 React 代碼?|javascript|程式語言|react|coffee...
    有一天,當我思考這個話題的時候,突然想到一個問題:我能在保持高生產力的同時,寫出既美觀又令人愉悅的 React 代碼嗎?我知道在其它程式語言和框架中,這個問題有非常有價值的答案。所有那些方法都會有不同程序的相同權衡,例如學習難度、能從 React 生態系統獲益的多少、圍繞它的工具如何等等。所有這些權衡都要根據項目的目標進行不同的衡量。
  • 精通react/vue組件設計教你實現一個極具創意的加載(Loading)組件
    正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先看看實現後的組件效果:因為動圖體積太大,就不給大家傳gif了,接下來我們具體分析一下該組件的特點.1.
  • 精通react/vue組件設計之實現一個Tag(標籤)和Empty(空狀態)組件
    "辛勤勞動",而是要根據已有前端的開發經驗,總結出一套自己的高效開發的方法.作為數據驅動的領導者react/vue等MVVM框架的出現,幫我們減少了工作中大量的冗餘代碼, 一切皆組件的思想深得人心.所以, 為了讓工程師們有更多的時間去考慮業務和產品迭代,我們不得不掌握高質量組件設計的思路和方法.所以筆者將花時間去總結各種業務場景下的組件的設計思路和方法,並用原生框架的語法去實現各種常用組件的開發,希望等讓前端新手或者有一定工作經驗的朋友能有所收穫
  • 如何用純css打造類materialUI的按鈕點擊動畫並封裝成react組件
    組件設計思路僅僅用上述代碼雖然可以實現一個按鈕點擊的動畫效果,但是並不通用, 也不符合作為一個經驗豐富的程式設計師的風格,所以接下來我們要一步步把它封裝成一個通用的按鈕組件,讓它無所不用.可插拔,可組合 基於以上幾點,我們來設計這個react組件.
  • 精通react/vue組件設計之實現一個輕量級可擴展的模態框組件
    正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先來解構一下Modal組件, 一個Modal分為以下幾個部分:編輯搜圖每一個區塊都可以自定義配置組件設計思路按照之前筆者總結的組件設計原則,我們第一步是要確認需求.
  • 《精通react/vue組件設計》之實現一個健壯的警告提示(Alert)組件
    其他業務類型所以我們在設計組件系統的時候可以參考如上分類去設計,該分類也是antd, element, zend等主流UI庫的分類方式.正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先看看實現後的組件效果:1. 組件設計思路按照之前筆者總結的組件設計原則,我們第一步是要確認需求.
  • 耐克react跑鞋耐磨嗎 耐克react跑鞋壽命為多少km
    耐克的緩震科技react是一款能夠和阿迪達斯的boost相提並論的科技,耐克的react跑鞋也是非常受歡迎的鞋款,那麼,耐克的react跑鞋的耐磨性怎麼樣呢?跑鞋的壽命又是多少呢?感興趣的朋友一起來看看吧!
  • 程序測評:Create React App 3.3中有哪些酷炫新功能?
    例如,如果要創建TypeScript React app,可以運行如下代碼:npx create-react-app foo-app--template typescript其中foo-app 是該應用程式項目的名稱。
  • react中關於hook介紹及其使用
    前言最近由於公司的項目開發,就學習了在react關於hook的使用,對其有個基本的認識以及如何在項目中去應用hook。簡單來說就是可以使用函數組件去使用react中的一些特性所要解決的問題:解決組件之間復用狀態邏輯很難得問題,hook能解決的就是在你無需修改之前組件結構的情況下復用狀態邏輯,在不使用hook的情況下,需要使用到一些高級的用法如高級組件、provider、customer等,這種方式對於新手來說不太友好
  • React知識點總結
    設計原則2.必須操作dom,再使用非受控組件ReactDOM from 'react-dom'import '.import React from 'react'import ReactDOM from 'react-dom'import '.
  • 精通React/Vue系列之帶你實現一個功能強大的通知提醒框
    正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先來解構一下Notification組件, 一個Notification分為以下幾個部分:每一個區塊都可以自定義配置, 也可以組合其他組件.並且我們可以配置提醒框出現的位置,就像antd
  • 快速在你的vue/react應用中實現ssr(服務端渲染)
    所以為了解決SPA應用遇到的這些問題, 我們必須考慮SSR:服務端渲染(ssr),是指由伺服器端完成頁面的HTML結構拼接,並且直接將拼接好的HTML發送到瀏覽器,然後為其綁定狀態與事件在使用這種方式的時候我們仍然要維護兩套代碼.
  • 耐克新技術react表現到底怎麼樣?
    大家都知道,去年年初的時候,面對阿迪boost系列鞋款越來越強勁的衝擊,Nike終於一改不斷復刻圈錢的慵懶模樣,轉而推出了他們的最新技術react。react並不是一種新型的氣墊,而是一種傳統的固態材料。但是老對手的boost,也是固態材料,而且廣受好評。
  • 無關潮流的小情懷,nike react presto和air presto
    身上有著明顯的延續比如整體鞋型結構,側面的軟塑料片設計,稍微了解一些的,便能一眼看出不過相對於air,react的鞋子因為全掌react加持,整體鞋型顯得更加大一點不過雖然現在的鞋底更加厚,比起巧思,還是air更加有設計感一些
  • 為什麼我喜歡 React
    原文連結:https://epicreact.dev/why-i-love-react/
  • AJ React Elevation開箱測評
    還記得這雙耐克的全新團隊鞋款Jordan React Elevation麼,剛爆出的時候很多小夥伴還以為是鑽石2代鞋款,其實小編覺得鞋款和威少2代才是最像的哈,前掌Zoom+後掌react的配置加持還是得到了很多人的關注的,下面一起來看看吧。
  • 與阿迪的boost相比,耐克的新科技react怎麼樣?
    於是在這種強烈的危機感之下,Nike公司在去年推出了一項全新的科技——react,對其的要求定位在了和Adidas的看家技術boost同一高度上。和boost一樣,react作為一項全新的緩震技術,它的一次亮相也同樣選在了籃球鞋上。而且之後為了和boost爭奪市場,react同樣被Nike運用到了其他的運動鞋之上。react一經出世,就被很多朋友拿來和boost對比。
  • 當React遇上Flyknit,實測耐克Epic跑鞋戰鬥力有多強!
    作為今年年初發布的新產品,Nike Epic React Flyknit使用的是Nike最新推出的新型中底材料react,功能性比大家熟悉的lunarlon更加強大。而且在Nike Epic React Flyknit的發布會上,Nike枕頭+彈簧的宣傳方式讓人感到印象深刻。
  • 耐克react上腳實測:枕頭般柔軟,像彈簧般回彈!
    Nike的react自從推出以來,已經有了一年的時間了。當初這個Nike用來對抗Adidas boost的最新材料,實際上腳的效果又如何呢?而在react系列的Nike鞋中,最為國內消費者所熟悉的一款,應該就是epic react了。