最近一直在看React的一些東西,其實很早前就想開始重拾前端,可是一直提不起興趣再去看JavaScript,對CSS這種佈局方式也不是很來感,說白了,就是懶吧😂。去年年末開始在公司app裏開始嘗試接入Weex,因此不得不把JavaScript再從新擼了一遍,順帶着把ES6的一些新特性也瞭解了一下,更好的函數調用方式,Class的引入,Promise的運用等等,其實最吸引個人仍是在用了Weex以後,感覺到了Component帶來的UI複用,高效開發的快感。Weex是運用Vue.js來調用,渲染native控件,來達到one code, run everywhere。不論是Vue.js,仍是React,最終都是朝着W3C WebComponent的標準走了(今年會發布的Vue 3.0在組件上的語法基本上跟React同樣了)。這篇就來說講我對React Component的理解,還有怎麼把這個標準也能在native上面作運用前端
demo源碼weex
iOS UI開發的痛點數據結構
對iOS開發來講,最經常使用的UI組件就是UICollectionView了,就是所謂的一個列表頁,如今的app大部分頁面都是由一個列表來呈現內容的。對iOS開發者來講,咱們能夠封裝每一個UICollectionViewCell,從而能夠在每一個頁面的UICollectionView中可以複用,可是痛點是,這個複用僅僅是UI上的複用,在每寫一個新的頁面(UIViewController)的時候,仍是須要新建一個UICollectionView,而後再把UICollectionView的DataSource和Delegate方法再實現一遍,把這些Cell再在這些方法裏從新生成一遍,才能讓列表展示出來。比方說咱們首頁列表底部有猜你喜歡的cell,我的中心頁面底部也有猜你喜歡的cell,這兩個頁面,都須要在本身擁有的UICollectionView中註冊這個猜你喜歡的cell,返回這個猜你喜歡cell的高度,設置這個cell的model並刷新數據,若是有Header或者Footer的話,還得從新設置這些Header跟Footer。因此新寫一個列表頁面,對iOS開發者來講,仍是很麻煩。app
使用Weex或者RN開發原生列表頁函數
使用Weex開發列表頁的時候,咱們組內的小夥伴都以爲很爽,很高效,基本上幾行代碼就能繪製出一個列表頁,舉個RN和weex的例子佈局
// Reactrender() { const cells = this.state.items.map((item, index) => { if (item.cellType === 'largeCell') { return} else if (item.cellType === 'mediumCell') { return} else if (item.cellType === 'smallCell') { return} }); return({ cells });}// Vueconst測試
waterfall對應的就是iOS中的UICollectionView,waterfall這個組件中有cell的子組件,這些cell的子組件能夠是咱們本身定義的不一樣類型樣式的cell組件。LargeCell,MediumCell,SmallCell對應的就是原生中的咱們自定義的UICollectionViewCell。這些Cell子組在任何waterfall組件下面均可以使用,在一個waterfall組件下面,咱們只須要把咱們把在這個列表中須要展現的cell放進來,經過props把數據傳到cell組件中便可。這種方式對iOS開發者來講,真的是太舒服了。在以爲開發很爽的同時,我也在思考,既然這種Component的方式用起來很爽,那麼能不能也運用到原生開發中呢?畢竟咱們大部分的業務需求仍是基於原生來開發的。this
React的核心思想atom
先來解釋下React中的React Element和React Componentspa
React Elements
constelement =
這段JSX表達式返回的就是一個React Element,React element描述了用戶將在屏幕上看到的那個UI,跟DOM elements不同的是,React elements是一個單純的對象,僅僅是對將要呈現到屏幕上的UI的一個描述,並非真正渲染好的UI,建立一個React element開銷是極其小的,渲染的事情是由背後的React DOM來處理的。上面的那段代碼至關於:
constelement = React.createElement('div', {id:'login-button'},'Login')返回的React element對象至關於 =>{ type:'div', props: { children:'Login', id:'login-button'}}
React Components
React中最核心的一個思想就是Component了,官方的解釋是Component容許咱們將UI拆分爲獨立可複用的代碼片斷,組件中能夠包含多個其餘組件,這樣將組件一個個單獨抽離出來,並最終再組合到一塊兒,大大提升了代碼的可讀性(Readability)、可維護性(Maintainability)、可複用性(Reusability)和可測試性(Testability)。這也是 React 裏用 Component 抽象全部 UI 的意義所在。
classButtonextendsReact.Component{ render() {constelement =
{ element }
</div>)}
複製代碼
這段代碼中Button就是一個React Component,這個component接受一個叫props的參數,返回描述UI的React element。
能夠看出React Component接受props是一個對象,也就是所謂的一種數據結構,返回React Element也是一種對象,所謂的另一種數據結構,因此我認爲的React Component其實就是一個function,這個function的主要功能就是將一種數據結構(描述原始數據)轉換成另一種數據結構(描述UI)。React element僅僅是一個描述UI的對象,能夠認爲是一箇中間狀態,咱們能夠用最小的開銷來建立或者銷燬element對象。
React的核心思想總結下來就是這樣的一個流程
原始數據到UI數據的轉化 props -> React Component -> React Element
React Element的做用是將Component的建立跟描述狀態分離,Component內部主要負責這個Component的構建,React Element主要用來作描述這個Component的狀態
多個Component返回的多個Elements,這個流程是進行UI組合
React Element並非一個渲染結果,React DOM的做用是將UI的狀態(即Element)和UI的渲染分離,React DOM負責element的渲染
最後一個流程就是UI渲染了
上述這幾個流程基本上表明瞭React的核心概念
怎麼在iOS中運用React Component概念
說了這麼多,其實iOS中缺乏的就是這個Component概念,iOS原生的流程是原始數據到UI佈局,再到UI繪製。複用的只是UI繪製結果的那個view(e.g. UICollectionViewCell)
在使用UICollectionView的時候,咱們的數據都是經過DataSource方法返回給UICollectionView,UICollectionView拿到這些數據以後,就直接去繪製UICollectionViewCell了。因此每一個列表頁都得從新建一個UICollectionView,再引入自定義的UICollectionViewCell來繪製列表,全部的DataSource跟Delegate方法都得走一遍。因此我在想,咱們能夠按照React的那種方式來繪製列表麼?將一個個UI控件抽象成一個個組件,再將這些組件組合到一塊兒,繪製出最後的頁面,React或者Weex的繪製列表其實就是waterfall這個列表component裏面按照列表順序插入自定義的cell component(組合)。那麼咱們其實能夠在iOS中也能夠有這個waterfall的component,這個component支持一個insertChildComponent:的方法,這個方法裏就是插入自定義的CellComponent到waterfall這個組件中,並經過傳入props來建立這個component。因此我就先定義了一個組件的基類BaseComponent
@protocolComponentProtocol/**
繪製組件
@param view 展現該組件的view
/- (void)drawComponentInView:(UIView)view withProps:(id)props;/**
組件的尺寸
@param props 該component的數據model
@return 該組件的size
*/+ (CGSize)componentSize:(id)props;@end@interfaceBaseComponent:NSObject- (instancetype)initWithProps:(id)props;@property(nonatomic,strong,readonly)idprops;
全部的Component的建立都是經過傳入props參數,來返回一個組件實例,每一個Component還遵照一個ComponentProtocol的協議,協議裏兩個方法:
有了這個Component概念以後,咱們原生的繪製流程就變成
建立Component,傳入參數props
Component內部執行建立代碼,保存props
當頁面須要繪製的時候(React中的render命令),component內部會執行- (void)drawComponentInView:(UIView *)view withProps:(id)props;方法來描述並繪製UI
原生代碼中想實現React element,其實不是一件簡單的事情,由於原生沒有相似JSX這種語言來生成一套只用來描述UI,並不繪製UI的中間狀態的對象(能夠作,比方說本身定義一套語法來描述UI),因此目前個人作法是在component內部,等到繪製命令來了以後,經過在- (void)drawComponentInView:(UIView *)view withProps:(id)props方法中,調用原生自定義的UIKit控件,經過props來繪製該UIKit
因此將經過封裝component的方式,咱們以前UIKit表明的UI組件轉換成組件,把這些組件一個個單獨抽離出來,再經過搭積木的方式,將各類組件一個個組合到一塊兒,怎麼繪製交給component內部去描述,而不是交給每一個頁面對應的UIViewController
Demo
Demo中,我會建立一個WaterfallComponent組件,還有多個CellComponent來繪製列表頁,每一個不同列表頁面(UIViewController)均可以建立一個WaterfallComponent組件,而後將不同的CellComponent按照順序插入到WaterfallComponent組件中,便可完成繪製列表,不須要每一個頁面再去處理UICollectionView的DataSource,Delegate方法。
Untitled.png
WaterfallComponent內部會有一個UICollectionView,WaterfallComponent的insertChildComponent方法中,會建立一個dataController來管理數據源,並用來跟UICollectionView的DataSource方法進行交互從而繪製出列表頁,最終UIViewController中繪製列表的方法以下:
self.waterfallComponent = [[WaterfallComponent alloc] initWithProps:nil];for(NSDictionary*propsindatas) {if([props[@"type"] isEqualToString:@"1"]) { FirstCellComponent *cellComponent = [[FirstCellComponent alloc] initWithProps:props]; [self.waterfallComponent insertChildComponent:cellComponent]; }elseif([props[@"type"] isEqualToString:@"2"]) { SecondCellComponent *cellComponent = [[SecondCellComponent alloc] initWithProps:props]; [self.waterfallComponent insertChildComponent:cellComponent]; }}[self.waterfallComponent drawComponentInView:self.view withProps:nil];
這樣,每一個咱們自定義的Cell就能夠以CellComponent的形式,被按照隨意順序插入到WaterfallComponent,從而作到了真正意義上的複用,Demo已上傳到GitHub上,有興趣的能夠看看
總結
React的核心思想是將組件一個個單獨抽離出來,並最終再組合到一塊兒,大大提升了代碼的可讀性、可維護性、可複用性和可測試性。這也是 React 裏用 Component 抽象全部 UI 的意義所在。
原生開發中,使用Component的概念,用Component去抽象UIKit控件,也能達到一樣的效果,這樣也能統一每一個開發使用UICollectionView時候的規範,也能統一對全部列表頁的數據源作一些統一處理,比方說根據一個邏輯,統一在全部列表頁,插入一個廣告cell,這個邏輯徹底能夠在WaterfallComponent裏統一處理。
思考
目前咱們只用到了Component這個概念,其實React中,React Element的概念也是很是核心的,React Element隔離了UI描述跟UI繪製的邏輯,經過JSX來描述UI,並不去生成,繪製UI,這樣咱們可以以最小的代價來生成或者銷燬React Elements,而後在交付給系統繪製elements裏描述的UI,那麼若是原生裏也有這一套模板語言,那麼咱們就能真正作到在Component裏,傳入props,返回一個element描述UI,而後再交給系統去繪製,這樣還能省去cell的建立,只建立CellComponent便可。其實咱們能夠經過定義一套語義去描述UI佈局,而後經過解析這套語義,經過Core Text去作繪製,這一套仍是值得我再去思考的。
本文源於第三方轉載,原文連接:www.jianshu.com/p/bc4b13a0d…
文章如有不對地方,歡迎批評指正,一個小而有用QQ交流羣:805558511