JS前端組件設(shè)計(jì)以業(yè)務(wù)為導(dǎo)向?qū)嵺`思考
引言
本文重點(diǎn)探討前端組件設(shè)計(jì)。我相信好的組件設(shè)計(jì)不僅需要考慮技術(shù)實(shí)現(xiàn),同時(shí)也需要考慮用戶體驗(yàn)、可擴(kuò)展性和易用性等多個(gè)方面。因此,我會(huì)重點(diǎn)強(qiáng)調(diào)這些方面的實(shí)現(xiàn)方法,從而更好地為產(chǎn)品的實(shí)際需求服務(wù)。我不打算在這里重復(fù)一些大家都耳熟能詳?shù)那岸私M件設(shè)計(jì)的原則和技巧。設(shè)計(jì)永遠(yuǎn)是為需求服務(wù)的。
下面,讓我們進(jìn)入正題。
一個(gè)組件設(shè)計(jì)的例子
考慮一個(gè)業(yè)務(wù)場景。一個(gè)表單組件,用來采集用戶的基本信息,每個(gè)甲方爸爸都有自己的定制需求。比如樣式、比如要展示的控件種類。分析表單控件類型,假設(shè)分別可能有文本輸入框、單選框、多選框、下拉框等等。
我們很容易就能想到用動(dòng)態(tài)表單來實(shí)現(xiàn),支持自定義控件類型,支持自定義表單元素的樣式和行為。api設(shè)計(jì)為了易于擴(kuò)展,泛型顯然更具可重用性。
一個(gè)基本的表單類型像這樣:
type FieldProps<T> = { label: string; name: string; value: T; onChange: (name: string, value: T) => void; };
創(chuàng)建一個(gè)接口,表示 Field 組件可以渲染的不同類型的表單元素:
interface FieldRenderer<T> { (props: FieldProps<T>): JSX.Element; }
接著,我們可以創(chuàng)建一個(gè)泛型的 Field 組件,根據(jù)傳入的泛型類型 T,來確定 Field 組件要渲染的表單元素類型以及 props 的類型。
function Field<T>({ label, name, value, onChange, render }: FieldProps<T> & { render: FieldRenderer<T> }) { return ( <div className="field"> <label htmlFor={name}>{label}</label> {render({ label, name, value, onChange })} </div> ); }
創(chuàng)建一些不同的 Field 組件渲染器,比如 TextInput、SelectInput、CheckboxInput。
const TextInput: FieldRenderer<string> = ({ label, name, value, onChange }) => ( <input type="text" id={name} name={name} value={value} onChange={(e) => onChange(name, e.target.value)} /> ); const SelectInput: FieldRenderer<string> = ({ label, name, value, options, onChange }) => ( <select id={name} name={name} value={value} onChange={(e) => onChange(name, e.target.value)}> {options.map(option=><option value={option.value}>{option.label}</option>)} </select> );
現(xiàn)在,我們可以在使用 Field 組件的時(shí)候,傳入不同的泛型類型,來渲染不同的表單元素了。
const DynamicForm = ({ fields, onSubmit }: { fields: FormField[], onSubmit: ()=>void }) => { //這里做一些映射之類的。。 return ( <form onSubmit={onSubmit}> {fields.map(field=><Field {...field} render={field.customComponent} />)} </form> ) }
以上,我們基本完成一個(gè)高度抽象化的組件設(shè)計(jì)。
現(xiàn)在,讓我們的目光從這個(gè)細(xì)節(jié)上挪開,切換到一個(gè)宏觀的視角上,重新審視項(xiàng)目整體架構(gòu)的組件設(shè)計(jì)。一個(gè)前端架構(gòu)通常會(huì)有其系統(tǒng)規(guī)范,比如統(tǒng)一的命名規(guī)范、代碼風(fēng)格,合理的文件組織結(jié)構(gòu),前端開發(fā)的基礎(chǔ)設(shè)施,性能優(yōu)化方案,依賴管理等。那么,我們?nèi)绾卧谶@個(gè)規(guī)范的框架下設(shè)計(jì)組件呢?
模塊化設(shè)計(jì)原則
從整體角度規(guī)劃方案,我們可以對(duì)項(xiàng)目進(jìn)行分層和模塊化的設(shè)計(jì),實(shí)現(xiàn)不同模塊之間的解耦合。
分層設(shè)計(jì)是指將整個(gè)系統(tǒng)分成分層模塊,每一層模塊都有自己的職責(zé)和功能。前端的分層設(shè)計(jì)主要涉及以下幾層:展示層、控制層、邏輯層、服務(wù)層。模塊化設(shè)計(jì)是指將整個(gè)系統(tǒng)分成小的模塊,每個(gè)模塊都有自己的功能,不同模塊之間通過明確的接口進(jìn)行通信和數(shù)據(jù)交換。在前端項(xiàng)目中,往往可以將不同的功能分配到不同的模塊中,甚至可以將某些通用的功能寫成獨(dú)立的模塊進(jìn)行引入。
在實(shí)際業(yè)務(wù)中,我們通常需要將分層設(shè)計(jì)與模塊化設(shè)計(jì)相結(jié)合。在不同的層次上實(shí)現(xiàn)代碼結(jié)構(gòu)的劃分和內(nèi)部邏輯的編寫,不同的功能分配到不同的模塊,通用的功能寫成獨(dú)立的模塊引入,將代碼封裝成可復(fù)用的單元。
業(yè)務(wù)組件的設(shè)計(jì)因素
業(yè)務(wù)組件是在實(shí)現(xiàn)業(yè)務(wù)過程中抽象出來的組件,作用是在應(yīng)用中復(fù)用業(yè)務(wù)邏輯。我們應(yīng)該進(jìn)行怎樣的抽象?簡單的功能如果抽象成組件,是否是一種過度設(shè)計(jì)?我們嘗試從以下幾個(gè)角度來思考這些問題。
1. 狀態(tài)與接口
在設(shè)計(jì)接口時(shí),我們都知道,接口應(yīng)該簡單清晰、易于擴(kuò)展。比如一個(gè)loading flag, 我們通常會(huì)用布爾值來切換loading狀態(tài),分別展示不同的UI界面。
比如一個(gè)發(fā)送驗(yàn)證碼的按鈕,我們可能用isEnd
就能滿足展示不同按鈕文字的需求。但如果,我們分別需要在點(diǎn)擊按鈕前、點(diǎn)擊后的倒計(jì)時(shí)階段、倒計(jì)時(shí)進(jìn)入到指定的時(shí)刻、倒計(jì)時(shí)結(jié)束后執(zhí)行不同的邏輯。那么,我們就需要考慮將這個(gè)接口設(shè)計(jì)成字符串,以便于擴(kuò)展。
同理,一些使用loading布爾值的場景,是否可以考慮設(shè)計(jì)為字符串,以便滿足更加個(gè)性化的需求?站在用戶的角度,你是否已經(jīng)厭倦了在等待一份大體積的數(shù)據(jù)時(shí)看著一個(gè)動(dòng)畫圈圈在轉(zhuǎn)動(dòng)?
現(xiàn)在,我們來考慮組件的使用場景。
這個(gè)組件是否與業(yè)務(wù)邏輯綁定?比如一個(gè)登錄功能,可以是彈窗、也可以是單獨(dú)的頁面,它往往帶有以下功能:用戶名和密碼的前端校驗(yàn)規(guī)則、對(duì)后端響應(yīng)的處理、完成登錄后的邏輯處理。像這樣的組件,就不需要抽象,因?yàn)樗y以通過修改參數(shù)就直接在其它系統(tǒng)中使用。
這個(gè)組件的功能有可能被重用嗎?如果是,我們?nèi)绾巫鲱A(yù)先的接口設(shè)計(jì)?比如一個(gè)表格組件,通常包含以下狀態(tài):要展示的數(shù)據(jù)、對(duì)數(shù)據(jù)的排序規(guī)則、數(shù)據(jù)過濾規(guī)則、用戶選擇器。我們初期可能據(jù)此做了4個(gè)接口:數(shù)據(jù)、排序、過濾、選擇器。隨著業(yè)務(wù)的擴(kuò)展,我們可以預(yù)見后期的數(shù)據(jù)量開始加大,我們可能考慮增加一個(gè)分頁接口。但是分頁接口是否真的需要被集成在這個(gè)表格組件中?這是一個(gè)開放性的問題,相信不同的CRUD專家會(huì)有不同的解決方案。
2. 調(diào)用方式
組件的調(diào)用方式有多種,比如在模版文件中引入組件標(biāo)簽直接調(diào)用,或通過函數(shù)調(diào)用(常見的message/loading類組件),或者通過接口調(diào)用(Ant Design的DatePicker)等。并沒有一套通用的標(biāo)準(zhǔn)來指定某種類型的組件的調(diào)用方式,總體還是取決于項(xiàng)目的需求和場景。
3. 測(cè)試
假設(shè)一個(gè)組件需要訪問api,這很常見。我們應(yīng)該如何設(shè)計(jì)以便于在測(cè)試組件時(shí)隔離組件的功能?
import APIService from './APIService'; function MyComponent({ apiService }) { const fetchData = () => { const data = apiService.get('/data'); // 處理數(shù)據(jù)并返回結(jié)果 } return <>{/* 渲染組件的內(nèi)容 */}</> } // 渲染組件時(shí),可以將 APIService 實(shí)例作為 props 傳遞 const apiService = new APIService(); ReactDOM.render(<MyComponent apiService={apiService} />, document.getElementById('root'));
在組件內(nèi)部,我們定義了一個(gè) fetchData
函數(shù),它可以在需要的時(shí)候調(diào)用 apiService.get()
來獲取數(shù)據(jù)。此時(shí),我們可以輕松地模擬 apiService
,以進(jìn)行單元測(cè)試,而不會(huì)對(duì) MyComponent
的實(shí)現(xiàn)產(chǎn)生任何影響。
大多數(shù)時(shí)候,一個(gè)單一職責(zé)的組件的功能測(cè)試,是要比復(fù)合組件更容易的。使用標(biāo)準(zhǔn)和通用的API和數(shù)據(jù)格式,并將組件的功能和狀態(tài)限制在組件內(nèi)部,確保組件可以獨(dú)立地進(jìn)行測(cè)試。此外,我們還要考慮邊界測(cè)試,比如組件接收到無效的或非預(yù)期的參數(shù)該如何處理?
當(dāng)然,組件不是顆粒度越細(xì)越好。是否遵循單一職責(zé)原則,不應(yīng)該以功能點(diǎn)的數(shù)量,而是以功能和目標(biāo)來衡量。
4. 文檔
編寫一份前端組件設(shè)計(jì)文檔,明確記錄項(xiàng)目的設(shè)計(jì)標(biāo)準(zhǔn)和項(xiàng)目迭代過程中的變更,創(chuàng)建標(biāo)準(zhǔn)的組件庫以便于多人協(xié)作時(shí)的組件復(fù)用。尤其在一個(gè)多人協(xié)作的項(xiàng)目里,文檔能夠提供統(tǒng)一的開發(fā)規(guī)范,使得不同的開發(fā)人員在編寫不同的代碼時(shí)能保持一致的開發(fā)習(xí)慣。
分析業(yè)務(wù)邏輯
我們已經(jīng)有了設(shè)計(jì)的原則和需要考慮的因素,下面我們開始分析業(yè)務(wù)邏輯。
通常我們會(huì)有原型圖展示各個(gè)業(yè)務(wù)操作的流程和業(yè)務(wù)環(huán)節(jié)的邏輯關(guān)系,在設(shè)計(jì)組件時(shí)我們需要考慮如何支持這些流程和操作。比如根據(jù)業(yè)務(wù)邏輯,將整個(gè)項(xiàng)目分解為多個(gè)獨(dú)立的組件,劃分組件的接口和參數(shù),指定其數(shù)據(jù)類型等。
此外,我們還要分析各種數(shù)據(jù)的處理和存儲(chǔ)方式。這里我們只討論前端的管理方式。比如一個(gè)給定的系統(tǒng),我們通常會(huì)有用戶數(shù)據(jù)、產(chǎn)品數(shù)據(jù)、訂單數(shù)據(jù)等。我們可能會(huì)考慮使用狀態(tài)管理工具、Context API、組件間的通信、本地存儲(chǔ)等方式來管理這些數(shù)據(jù)。不同的場景需要采用不同的數(shù)據(jù)管理方法。
最后,我們要分析用戶交互的影響,考慮控制用戶的交互范圍。比如表單校驗(yàn)、提高反饋信息和錯(cuò)誤處理。
總結(jié)
當(dāng)前端組件設(shè)計(jì)融合藝術(shù)與技術(shù),創(chuàng)造出優(yōu)美、流暢的用戶體驗(yàn)時(shí),似乎所有的麻煩都隨之而消失。通過組件化設(shè)計(jì)、數(shù)據(jù)管理、用戶交互分析等手段,我們可以打造出易維護(hù)、易擴(kuò)展的代碼,為用戶提供舒適愉悅的體驗(yàn)。在這個(gè)信息化的時(shí)代,前端組件設(shè)計(jì)越來越重要,只有注重細(xì)節(jié),不斷優(yōu)化性能和加強(qiáng)安全防范,我們才能成為一名真正的CRUD藝術(shù)家。
以上就是JS前端組件設(shè)計(jì)以業(yè)務(wù)為導(dǎo)向?qū)嵺`思考的詳細(xì)內(nèi)容,更多關(guān)于JS業(yè)務(wù)導(dǎo)向前端組件設(shè)計(jì)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實(shí)現(xiàn)留言板實(shí)戰(zhàn)案例
這篇文章主要給大家介紹了關(guān)于JavaScript實(shí)現(xiàn)留言板的相關(guān)資料,使用JavaScript來編寫留言板功能相信大家都不陌生,文中給出了詳細(xì)的示例代碼,需要的朋友可以參考下2023-07-07指定區(qū)域的圖片自動(dòng)按比例縮小的js代碼(防止頁面被圖片撐破)
有時(shí)候我們更新的內(nèi)容,有很多的大圖片,就會(huì)導(dǎo)致頁面變形或看不到全圖。一般情況我們用css的max-width控制,但有些瀏覽器不支持,我們也可以用js做個(gè)補(bǔ)充2014-02-02JavaScript 閉包在封裝函數(shù)時(shí)的簡單分析
近才開始系統(tǒng)的研究js,對(duì)js的興趣源于對(duì)JQuery的應(yīng)用。之前只會(huì)用js做簡單的計(jì)算函數(shù),后來由于需要做特效,故接觸JQ,看著API,基本的特效都能完成,但相反,如果用js去實(shí)現(xiàn),估計(jì)自己很難寫得出來,所以下定決心系統(tǒng)的看看js。2009-11-11js動(dòng)態(tài)修改表格行colspan列跨度的方法
這篇文章主要介紹了js動(dòng)態(tài)修改表格行colspan列跨度的方法,實(shí)例分析了javascript動(dòng)態(tài)修改html中table屬性的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03利用JS重寫Cognos右鍵菜單的實(shí)現(xiàn)代碼
我寫了一個(gè)利用JS禁用Cognos右鍵菜單,下面的JS可以實(shí)現(xiàn)重寫Cognos的右鍵菜單。只要將下面的代碼拷到一個(gè)HTML項(xiàng)里即可。2010-04-04