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