React組件開(kāi)發(fā)實(shí)踐
基于 React 的組件化開(kāi)發(fā)方式,為富前端 web 應(yīng)用提供大量技術(shù)實(shí)踐,社區(qū)逐漸形成了穩(wěn)定的組件規(guī)范,本文從 API 層面歸納出 6 種組件類(lèi)型,分析其優(yōu)缺點(diǎn)和適用場(chǎng)景,為日常組件開(kāi)發(fā)提供一個(gè)方法指南。6 種類(lèi)型分別為結(jié)構(gòu)型組件、樣式型組件、組合型組件、配置型組件、受控型組件、非受控組件。
結(jié)構(gòu)型組件與樣式型組件
結(jié)構(gòu)型組件定義了組件大體結(jié)構(gòu),結(jié)構(gòu)的具體實(shí)現(xiàn)由外部傳遞。樣式型組件確定了組件結(jié)構(gòu)細(xì)節(jié),外部只需傳遞參數(shù)即可渲染預(yù)期樣式。樣式型組件是較為常用的組件類(lèi)型,很少有開(kāi)發(fā)者會(huì)根據(jù)一份設(shè)計(jì)稿來(lái)推斷組件未來(lái)可能的改動(dòng),這也導(dǎo)致了樣式型組件在復(fù)用性與拓展性上偏弱。對(duì)于比較通用的組件,例如 Button 按鈕、Modal 彈框、Form 表單等,不應(yīng)僅提供樣式型實(shí)現(xiàn),應(yīng)該抽象出結(jié)構(gòu)型組件。
這兩種類(lèi)型并不是非此即彼的關(guān)系,樣式型組件固定的 API 參數(shù)可以降低使用成本,結(jié)構(gòu)型組件彈性的 API 設(shè)定可以提供擴(kuò)展性,結(jié)合兩者的優(yōu)點(diǎn)可以構(gòu)造出既簡(jiǎn)單又可拓展的組件。關(guān)于兩者結(jié)合的優(yōu)勢(shì)最具說(shuō)服力的實(shí)踐是通用組件庫(kù),結(jié)構(gòu)型組件可顯著降低業(yè)務(wù)方的溝通成本與接入風(fēng)險(xiǎn),如下示意圖演示了業(yè)務(wù)方與組件庫(kù)之間的兩種溝通模型:
樣式型組件庫(kù)與業(yè)務(wù)方的溝通模型
結(jié)構(gòu)型組件庫(kù)與業(yè)務(wù)方的溝通模型
以上兩種模型每一個(gè)箭頭為一個(gè)工時(shí),樣式型組件庫(kù)完成一次需求變更需要三個(gè)工時(shí),業(yè)務(wù)方要等待組件庫(kù)實(shí)現(xiàn)功能后才能進(jìn)行下一步。結(jié)構(gòu)型組件庫(kù)給予業(yè)務(wù)方更多的自主性,不用等待組件庫(kù)實(shí)現(xiàn)新特性,通過(guò)自定義結(jié)構(gòu)滿(mǎn)足當(dāng)前需求,組件庫(kù)有充足的時(shí)間分析需求是否通用,是否值得提供新 API,結(jié)構(gòu)型組件在這個(gè)過(guò)程中扮演了緩沖區(qū)的角色,使得業(yè)務(wù)方與組件庫(kù)可以并行協(xié)同開(kāi)發(fā),確保各自的研發(fā)效率與節(jié)奏。
組合型組件與配置型組件
組合型組件以 JSX 為主體,通過(guò)組件間的嵌套組合描述業(yè)務(wù)邏輯。配置型組件通過(guò) props 傳遞數(shù)據(jù)結(jié)構(gòu),組件內(nèi)部根據(jù)預(yù)先設(shè)定好的邏輯渲染視圖。日常開(kāi)發(fā)傾向于寫(xiě)配置型組件,組合型組件更多的出現(xiàn)在通用組件庫(kù)中。
組合型組件結(jié)構(gòu)清晰,擴(kuò)展性高,組件使用者通過(guò)閱讀 JSX 的 render 函數(shù)即可了解業(yè)務(wù)邏輯,但組件間聯(lián)系微弱,ref 引用相互隔離,難以構(gòu)建復(fù)雜的交互組件。配置型組件需要寫(xiě)的代碼量少,但組件內(nèi)部渲染處于黑箱,使用者難以理解組件邏輯,使其在拓展性上偏弱。比較基礎(chǔ)的組件,例如 Form 表單,Select 選框等,建議采用組合型,有利于使用者組織業(yè)務(wù)代碼,復(fù)雜交互組件可使用配置型。
組合型組件最具代表性的實(shí)踐是 Ant Design,整個(gè)組件庫(kù)的 API 設(shè)計(jì)嚴(yán)格遵循組合型優(yōu)先原則,為同一組件的不同位面分別提供組合型結(jié)構(gòu),使其在拓展性和易用性上都達(dá)到了很高的水準(zhǔn)。如下示意圖演示了用兩種組件類(lèi)型開(kāi)發(fā) Select 選框的演化模型。
Select 簡(jiǎn)單選框,組合型與配置型,都能提供清晰易用的接口
比較復(fù)雜的 Select 選框組,組合型組件通過(guò)提供新的子組件,仍可保持簡(jiǎn)潔的 API 調(diào)用。配置型組件有兩種實(shí)現(xiàn)方式:提供新的屬性或者擴(kuò)展原屬性,兩種方式都會(huì)產(chǎn)生一定認(rèn)知成本。
對(duì)于需要自定義的 Select 選框組,組合型組件得益于 JSX 的嵌套結(jié)構(gòu),可以很從容的提供自定義 API。配置型組件實(shí)現(xiàn)同樣的功能,需要再一次拓展屬性配置。
受控型組件與非受控組件
這兩種類(lèi)型有另一種表述:無(wú)狀態(tài)組件和有狀態(tài)組件。受控型組件內(nèi)部只負(fù)責(zé)展示,僅對(duì)外提供回調(diào),以表達(dá)改變的期望,其最終行為完全由外部驅(qū)動(dòng)。非受控組件由內(nèi)部處理某些行為,并不強(qiáng)制外部狀態(tài)同步。官方推薦輸出無(wú)狀態(tài)受控組件,但是有狀態(tài)的組件在項(xiàng)目開(kāi)發(fā)中仍是必要的。
受控型組件在自身層面規(guī)范了單向數(shù)據(jù)流,可以與其他數(shù)據(jù)層框架整合,但是開(kāi)發(fā)一個(gè)復(fù)雜的受控型組件,開(kāi)發(fā)者可能需要向外提供數(shù)不清的接口與回調(diào)。非受控組件較為智能,組件可以自主維護(hù)狀態(tài),但開(kāi)發(fā)者常常因此懶于做狀態(tài)同步,上層組件重新渲染時(shí),非受控組件會(huì)丟失內(nèi)部的狀態(tài),失憶,日常開(kāi)發(fā)中大多數(shù)的 bug 因此而來(lái)。
我們經(jīng)常會(huì)以?xún)?nèi)部是否擁有 state 來(lái)衡量一個(gè)受控型組件與非受控組件,但是完全遵守這條標(biāo)準(zhǔn)將很難提供一個(gè)簡(jiǎn)單易用的大型受控組件,所有狀態(tài)都由外部控制,使用者需要寫(xiě)大量配置代碼才能跑通一個(gè)大型組件,使用成本極高。官方提供的解決方案通過(guò)兩者結(jié)合的方式來(lái)處理受控與易用的矛盾,如下示意圖展示了一個(gè) Input 組件可以接受的參數(shù)類(lèi)型。
按照類(lèi)型定義可推導(dǎo)出如下三種使用方式,分別對(duì)應(yīng)一種受控型用法,和兩種非受控用法。
開(kāi)發(fā)一個(gè)受控與非受控兼具的組件,對(duì)組件本身的開(kāi)發(fā)與維護(hù)有更高的要求,其難度隨組件本身復(fù)雜度的增加而增加。但是對(duì)組件使用者來(lái)說(shuō),這種兩者兼具的組件最能適應(yīng)快速開(kāi)發(fā)與后期代碼調(diào)優(yōu)。任何有輸入輸出特性的組件(各種表單,配置 + 回調(diào)組件),都可參照上述類(lèi)型定義提供 API。
總結(jié)
React 組件本質(zhì)上是 JS 函數(shù)的另一種形態(tài),一切與函數(shù)有關(guān)的思想都可以反映在組件里,每一種組件都有其適用場(chǎng)景,開(kāi)發(fā)一個(gè)大型 Web 項(xiàng)目需要搭配使用不同類(lèi)型的組件,如何做出合適搭配則需要長(zhǎng)時(shí)間的開(kāi)發(fā)積累,在真正的項(xiàng)目里尋找最優(yōu)解。