摘要:看完界面,直接的感覺下,然后我們來看下這個故事板的源碼,上面是的描述,描述了組件的有哪些部件以及里面映射的屬性,用來將和進行解耦。結(jié)構(gòu)模板樣式模板等同于上面描述的組件的與第一種方式不同的地方是能夠直接將結(jié)構(gòu)和樣式寫到元數(shù)據(jù)中。
一、前言
模式是一種規(guī)律或者說有效的方法,所以掌握某一種實踐總結(jié)出來的模式是快速學習和積累的較好方法,模式的對錯需要自己去把握,但是只有量的積累才會發(fā)生質(zhì)的改變,多思考總是好的。(下面的代碼實例更多是 React 類似的偽代碼,不一定能夠執(zhí)行,函數(shù)類似的玩意更容易簡單描述問題)
二、前端的關(guān)注點遷移這篇文章主要介紹現(xiàn)在組件化的一些模式,以及設(shè)計組件的一些思考,那么為什么是思考組件呢?因為現(xiàn)在前端開發(fā)過程是以組件為基本單位來開發(fā)。在組件化被普及(因為提及的時間是很早的或者說有些廠實現(xiàn)了自己的一套但是在整個前端還未是一種流行編寫頁面的單元)前,我們的大多數(shù)聚焦點是資源的分離,也就是 HTML、CSS、JavaScript,分別負責頁面信息、頁面樣式、頁面行為,現(xiàn)在我們編程上的聚焦點更多的是聚焦在數(shù)據(jù)和組件。
但是有時候會發(fā)現(xiàn)只關(guān)心到這一個層級的事情在某些業(yè)務(wù)情況下搞不定,比如組件之間的關(guān)系、通信、可擴展性、復用的粒度、接口的友好度等問題,所以需要在組件上進行進一步的延伸,擴展一下組件所參考的視角,延伸到組件模塊和組件系統(tǒng)的概念來指導我們編寫代碼。
概念可能會比較生硬,但是你如果有趣的理解成搭積木的方式可能會更好擴展思路一點。
三、數(shù)據(jù)之于組件在說組件之前,先來說下數(shù)據(jù)的事情,因為現(xiàn)在數(shù)據(jù)對于前端是很重要的,其實這是一個前、后端技術(shù)和工作方式演變形成的,以前的數(shù)據(jù)行為和操作都是后端處理完成之后,前端基本拿到的就是直接可用的 View 展示數(shù)據(jù),但是隨著后端服務(wù)化,需要提供給多個端的數(shù)據(jù)以及前后端分離工作模式的形成,前端就變得越來越復雜了,其實 SPA 的形成也跟這些有一定關(guān)系,一是體驗可能對于用戶好,二是演變決定了這種方式。此時,前端的數(shù)據(jù)層就需要設(shè)計以及復用一些后端在這一層級的成熟模式,在這里就產(chǎn)生了一種思想的交集。
比如現(xiàn)在有一個 RadioGroup 組件,然后有下面 2 種數(shù)據(jù)結(jié)構(gòu)可以選擇:
items = [{ id: 1, name: "A", selected: true }, { id: 2, name: "B", selected: false }];
data = { selected: 1 items: [{ id: 1, name: "A" }, { id: 2, name: "B" }] };
那么我們的組件描述(JSX)會怎么寫呢?
第一種:
items.map(item => return);
第二種:
data.items.map(item => const isSelected = item.id === data.selected; return);
當然,數(shù)據(jù)結(jié)構(gòu)的選擇上是根據(jù)需求,因為不同的數(shù)據(jù)結(jié)構(gòu)有不同的優(yōu)勢,比如這里第二種類似 Dict 的查詢很方便,數(shù)據(jù)也很干凈,第一種渲染是比較直接的,但是要理解組件的編寫方式其實很大程度上會跟數(shù)據(jù)產(chǎn)生一種關(guān)系,有時候編寫發(fā)現(xiàn)問題可以返過來思考是否換種結(jié)構(gòu)就變簡單了。
數(shù)據(jù)就談這些吧,不然都能多帶帶開話題了,接下來看下組件,如果要學習模式就需要采集樣本然后去學習與總結(jié),這里我們來看下 Android && iOS 中的組件長什么樣子,然后看是否能給我們?nèi)粘>帉?Web 組件提供點靈感,篇幅有限,本來是應(yīng)該先看下 GUI 的方式。
四、iOS 端的組件概覽假設(shè),先摒棄到 Web 組件的形態(tài)比其他端豐富,如果不假設(shè)那么這套估計不是那么適用。
4.1 iOSiOS 的 View 聲明能夠通過一個故事板的方式,特別爽,比如這里給按鈕的狀態(tài)設(shè)定高亮、選中、失效這種,方便得很。
看完界面,直接的感覺下,然后我們來看下這個故事板的源碼,上面是 XML 的描述,描述了組件的 View 有哪些部件以及 ViewController 里面映射的屬性,用來將 View 和 ViewController 進行解耦。
... ...
我這里定義的按鈕狀態(tài)、顏色都在這里,分別給他們命名:結(jié)構(gòu)描述、樣式描述。
那么具體怎么給用戶交互,比較編程化的東西在 ViewController,來看下代碼:
// 數(shù)據(jù)行為描述 // connection 中關(guān)聯(lián)的鉤子 @IBOutlet private weak var passwordTextField: UITextField! @IBOutlet private weak var tipValidLabel: UILabel! // 一個密碼輸入框的驗證邏輯,最后綁定給 tipValidLabel、loginButton 組件狀態(tài)上 let passwordValid: Observable= passwordTextField.rx.text.orEmpty .map { newPassword in newPassword.characters.count > 5 } passwordValid .bind(to: tipValidLabel.rx.isHidden) .disposed(by: disposeBag) passwordValid .bind(to: loginButton.rx.isEnabled) .disposed(by: disposeBag)
上面代碼整體可以看做是響應(yīng)式的對象,綁定3個組件之間的交互,密碼不為空以及大于5個字符就執(zhí)行 bind 地方,主要是同步另外2個組件的狀態(tài)。其實也不需要看懂代碼,這只是為了體會客戶端組件的方式的例子,ViewController 我這里就叫:數(shù)據(jù)行為描述。這樣就有組件最基本的三個描述了:結(jié)構(gòu)、樣式、數(shù)據(jù)行為,雖然樣本不多,但是這里直接描述它們就是一個組件的基本要素,整個故事板和 swift 代碼很好的描述。
五、什么是組件?結(jié)構(gòu)描述
樣式描述
數(shù)據(jù)描述
對于組件來說,也是一份代碼的集合,基本組成要素還是需要的,但是這三種要素存在和以前的 HTML, CSS, JS 三種資源的分離是不一樣的,到了組件開發(fā),更多的是關(guān)注如何將這些要素連接起來,形成我們需要的組件。
比如 React 中對這三要素的描述用一個 .js 文件全部描述或者將結(jié)構(gòu)、數(shù)據(jù)包裹在一起,樣式描述分離成 . 文件,這里就可能會形成下面 2 種形式的組件編寫。
=> 3 -> (JSX + styled-components)
// 組件樣式 const Title = styled.h1` font-size: 1.5em; text-align: center; `; // 組件內(nèi)容Hello World!
=> 2 + 1 -> (JSX + CSS Module)
export default function Button(props) { // 分離的樣式,通過結(jié)構(gòu)化 className 來實現(xiàn)連接 const buttonClass = getClassName(["lv-button", "primary"]); return ( ); }
可能最開始很多不習慣這樣寫,或者說不接受這類理念,那么再看下 Angular 的實現(xiàn)方式,也有 2 種:
(1) 采用元數(shù)據(jù)來裝飾一個組件行為,然后樣式和結(jié)構(gòu)能夠通過導入的方式連接具體實現(xiàn)文件。
@Component({ selector: "app-root", // 結(jié)構(gòu)模板 templateUrl: "./app.component.html", // 樣式模板 styleUrls: ["./app.component.css"] }) // 等同于上面描述的 iOS 組件的 ViewController export class AppComponent { }
(2) 與第一種方式不同的地方是能夠直接將結(jié)構(gòu)和樣式寫到元數(shù)據(jù)中。
@Component({ selector: "app-root", template: `{{title}}
無論實現(xiàn)的形式如何,其實基本不會影響太多寫代碼的邏輯,樣式是目前前端工程化的難點和麻煩點,所以適合自己思維習慣即可。這里需要理解的是學習一門以組件為核心的技術(shù),都能夠先找到要素進行理解和學習,構(gòu)造最簡單的部分。
雖然有了描述一個組件的基本要素,但是還遠不足以讓我們開發(fā)一個中大型應(yīng)用,需要關(guān)注其他更多的點。這里提取組件基本都有的特性:
1. 注冊組件
將組件拖到故事板
2. 組件接口(略)
別人家的代碼能夠修改組件的部分
3. 組件自屬性
組件創(chuàng)建之初,就有的一些固定屬性
4. 組件生命周期
組件存在到消失如何控制以及資源的整合
5. 組件 Zone
組件存在于什么空間下,或者說是上下文,很可能會影響到設(shè)計的接口和作用范圍,比如 React.js 可用于寫瀏覽器中的應(yīng)用,React Native 可以用來寫類似原生的 App,在設(shè)計上大多數(shù)能雷同,但是平臺的特殊地方也許就會出現(xiàn)對應(yīng)的代碼措施)
這些主要就是拿來幫助去看一門不懂的技術(shù)的時候,只要是組件的范圍,就先看看有沒有這些東西的概念能不能聯(lián)想幫助理解。
具體來看下代碼是如何來落地這些模式的。
1.組件注冊,其實注冊就是讓代碼識別你寫的組件
(1) 聲明即定義,導入即注冊
export SomeOneComponent {}; import {SomeOneComponent} from "SomeOneComponent";
(2) 直接了當?shù)捏w現(xiàn)注冊的模式
AppRegistry.registerComponent("ReactNativeApp", () => SomeComponent);
(3) 擁有模塊來劃分組件,以模塊為單位啟動組件
@NgModule({ // 聲明要用的組件 declarations: [ AppComponent, TabComponent, TableComponent ], // 導入需要的組件模塊 imports: [ BrowserModule, HttpModule ], providers: [], // 啟動組件, 每種平臺的啟動方式可能不一樣 bootstrap: [AppComponent] }) export class AppModule { }
2.組件的自屬性
比如 Button 組件,在平時場景下使用基本需要綁定一些自身標記的屬性,這些屬性能夠認為是一個 Component Model 所應(yīng)該擁有,下面用偽代碼進行描述。
// 將用戶的 touch, click 等行為都抽象成 pointer 的操作 ~PointerOperateModel { selected: boolean; disabled: boolean; highlighted: boolean; active: boolean; } ButtonModel extends PointerOperateModel { } LinkModel extends PointerOperateModel { } TabModel extends PointerOperateModel { } ... // 或者是具有對立的操作模型 ~ToggleModel { on: boolean; } OnOffModel extends ToggleModel { } SwitchModel extends ToggleModel { } MenuModel extends ToggleModel { } ... // 組件的使用 this.ref.attribute = value; this.ref.attribute = !value;
這些操作如果需要更少的代碼,也許能夠這樣:
~ObserverState{ set: (value: T) => void; get: (value: T) => T; changed: () => void; cacheQueue: Map ; private ___observe: Observe; } Model extends ObserverState { }
基本上組件的這些屬性是遍布在我們整個代碼開發(fā)過程中,所以是很重要的點。這里還有一個比較重要的思考,那就是表單的模型,這里不擴展開來,可以多帶帶立一篇文章分析。
3.組件的聲明周期
與其說是生命周期,更多的是落地時候的代碼鉤子,因為我們要讓組件與數(shù)據(jù)進行連接,也許需要在特定的時候去操作一份數(shù)據(jù)。在瀏覽器(宿主)中,要知道具體是否已經(jīng)可用是一個關(guān)鍵的點,所以任何在這個平臺的組件都會有這類周期,如果沒有的話用的時候就會很蛋疼。
最簡單的路線是:
mounted => update => destory
但是往往實際項目會至少加一個東西,那就是異常,所以就能夠開分支了,但是更清晰的應(yīng)該是平行的周期方式。
mounted => is error => update => destory
4.組件 Zone
組件在不同的 Zone 下可能會呈現(xiàn)不同的狀態(tài),這基本上是受外界影響的,然后自己做出反應(yīng)。這里可以針對最基本的組件使用場景舉例,但是這個 Zone 是一種泛化概念。
比如我們要開發(fā)一個彈框組件:Modal,先只考慮一個最基本需求:彈框的位置,這個彈框到底掛載到哪兒?
掛載到組件內(nèi)部;
掛載到最近的容器節(jié)點下;
掛載到更上層的容器,以至于 DOM 基礎(chǔ)節(jié)點。
每一種場景下的彈框,對于每種組件的方案影響是不同的:
組件內(nèi)部,如果組件產(chǎn)生了 render,很可能受到影響;
掛載到最近的容器組件,看似問題不大,但是業(yè)務(wù)組件的拆、合是不定的,對于不定的需求很可能代碼會改變,但是這種方案是不錯的,不用寫太遠,當然在 React 16 有了新的方案;
掛載到更高的層級,這種方案適合項目對彈框需求依賴比較強的情況吧,因為受到的影響更小,彈框其實對于前端更強調(diào)的是一種渲染或者說是一種交互。
5.組件的遞歸特性
組件能夠擁有遞歸是一個很重要的縱向擴展的特性,每一種庫或者框架都會支持,就要看支持對于開發(fā)的自然度,比如:
// React this.props.children // Angular
基本上可以認為現(xiàn)在面向組件的開發(fā)是更加貼近追求的設(shè)計即實現(xiàn)的理想,因為這是面向?qū)ο蠓椒ㄕ摬蝗菀拙邆涞?,組件是一種更高抽象的方法,一個組件也許會有對象分析的插入,但是對外的表現(xiàn)是組件,一切皆組件后經(jīng)過積累,這將大大提升開發(fā)的效率。六、如何設(shè)計組件?
經(jīng)過前面的描述,知道了組件的概念和簡單組件的編寫方法,但是掌握了這些東西在實際項目中還是容易陷入蛋痛的地步,因為組件只是組成一個組件模塊的基礎(chǔ)單元,慢慢的開發(fā)代碼的過程中,我們需要良好的去組織這些組件讓我們的模塊即實現(xiàn)效果的同時也擁有一定的魯棒性和可擴展性。這里將組件的設(shè)計方法分為 2 個打點:
橫向分類
縱向分層
其實這種思路是一直以來都有的,這里套用到平時自己的組件設(shè)計過程中,讓它幫助我們更容易去設(shè)計組件。
這種設(shè)計的方法論是一個比較容易掌握和把握的,因為它的模型是一個二維的(x, y)兩個方向去拆、合自己的組件。注意,這里基本上的代碼操作單元是組件,因為這里我們要組裝的目標是模塊^0^感覺很好玩的樣子,舉例來描述一下。
比如我們現(xiàn)在來設(shè)計比較常用的下拉列表組件(DropDownList),最簡單的有如下做法:
class DropDownList { render() { return (); } }{this.props.dataSource.map((itemData, index) => )}
現(xiàn)在自己玩的往上加點需求,現(xiàn)在我需要加一個列表前面都加一個統(tǒng)一的 icon, 首先我們要做的肯定是要有一個 Icon 的組件,這個設(shè)計也比較依賴場景,目前我們先設(shè)計下拉。現(xiàn)在就有2種方案:
在 DropDownList 組件里面加一個判斷,動態(tài)加一個組件就行;
重新寫一個組件叫 DropDownIconList。
第一種方案比較省事,但是其實寫個 if...else... 算是一個邏輯分支的代碼,以后萬一要加一個 CheckBox 或者 Radio 組件在前面...
第二種方案看上去美好,但是容易出現(xiàn)代碼變多的情況,這時候就需要再重新分析需求變化以及變化的趨勢。
這時候按垂直和水平功能上,這里拆分 DropDownIconList 組件可以看成一個水平的劃分,從垂直的情況來看,將下拉這一個行為做成一個組件叫 DropDown,最后就變成了下面的樣子:
class DropDown { render() {} } class DropDownList { render() { return (請選擇
{this.props.children}); } } class DropDownIconList { render() { return ( {this.props.dataSource.map((itemData, index) => )} ); } } {this.props.dataSource.map((itemData, index) => )}
這樣的缺點就是存在多個組件,也許會有冗余代碼,優(yōu)點就是以后增加類似組件,不會將代碼的復雜度都加到一份代碼中,比如我要再加一個下拉里面分頁、加入選中的項、下拉內(nèi)容分頁、下拉的無限滾動等等,都是不用影響之前那份代碼的擴展。
七、讓組件連接起來組件化的開發(fā)在結(jié)構(gòu)上是一種分形架構(gòu)的體現(xiàn),是一個應(yīng)用引向有序組件構(gòu)成的過程。組件系統(tǒng)的復雜度可以理解成 f(x) = g(x) + u(x), g(x) 表示特有功能,u(x)表示功能的交集或者說有一定重合度的集合。組件彈性體現(xiàn)在 u(x) -> 0(趨近)的過程中,這個論點可參考:面向積木(BO)的方法論與分形架構(gòu)
上面的過程中,有了組件、組件模塊,既然有了基礎(chǔ)的實體,那么他們或多或少會有溝通的需求(活的模塊)?;旧犀F(xiàn)在主流的方案可以用下面的圖來表示。
我們提取一下主要的元素:
Component 實體
Component 實體的集合:Container
Action
Action 的操作流
Service Or Store
狀態(tài)同步中心
Observable Effect
如果要說單向數(shù)據(jù)流和雙向綁定的體現(xiàn)基本可以理解成體現(xiàn)在虛線框選的位置,如果組件或者Store是一個觀察的模型,那么方案實現(xiàn)后就很可能往雙向綁定靠近。如果是手動黨連接 ViewValue 和 ModelValue,按照一條流下來可以理解成單向流。雖然沒有按定義完全約束,但是代碼的落地上會形成這種模式,這塊細講也會是一個多帶帶的話題,等之后文章再介紹各種模式。
組件的關(guān)系能夠體現(xiàn)在包含、組合、繼承、依賴等方面,如果要更好的松耦合,一般就體現(xiàn)在配置上,配置就是一種自然的聲明式,這是聲明式的優(yōu)勢同時也是缺點。
以上是一些對組件的思考,碼字好累,不一定很深入,但是希望能夠幫助到剛踏入組件化前端開發(fā)的小伙伴~如果覺得有幫助請幫忙推薦,也可以閱讀原文:小擼的博客
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90496.html
摘要:下的表格狂想曲前言歡迎大家閱讀從零開始的組件開發(fā)之路系列第一篇,表格篇。北京小李中的每一個元素是一列的配置,也是一個對象,至少應(yīng)該包括如下幾部分表頭該列使用行中的哪個進行顯示易用性與通用性的平衡易用性與通用性互相制衡,但并不是絕對矛盾。 React 下的表格狂想曲 0. 前言 歡迎大家閱讀「從零開始的 React 組件開發(fā)之路」系列第一篇,表格篇。本系列的特色是從 需求分析、API 設(shè)...
摘要:實際上是讓組件的接收函數(shù),由函數(shù)來渲染內(nèi)容。將通用的邏輯抽象在該組件的內(nèi)部,然后依據(jù)業(yè)務(wù)邏輯來調(diào)用函數(shù)內(nèi)渲染內(nèi)容的函數(shù),從而達到重用邏輯的目的。當然,上邊通過傳入了這屬于組件的增強功能。還有也提供了一種新模式來解決這個問題。 寫業(yè)務(wù)時,我們經(jīng)常需要抽象一些使用頻率較高的邏輯,但是除了高階組件可以抽象邏輯,RenderProps也是一種比較好的方法。 RenderProps,顧名思義就是...
閱讀 3844·2021-10-12 10:11
閱讀 3650·2021-09-13 10:27
閱讀 2558·2019-08-30 15:53
閱讀 1989·2019-08-29 18:33
閱讀 2203·2019-08-29 14:03
閱讀 1007·2019-08-29 13:27
閱讀 3329·2019-08-28 18:07
閱讀 802·2019-08-26 13:23