低成本可複用前端框架——Linke

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"業務背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前團隊內的開發模式多是面向組件的,UI層和邏輯層均強耦合在一起,由於業務的差異性,往往很難完全複用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閒魚前端業務處在高速發展不斷嘗試的階段,如何能更快更穩定地完成需求,更好的支撐業務發展絕對是一個值得探索的問題。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在接手一個複雜的老業務代碼時,經過較多人的修改,往往可維護性較差,有時只想修改某個小地方卻需要較大的理解成本,所以用一套統一的組件開發規範在長期維護中顯得格外重要。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閒魚技術體系經歷了從weex、rax0.x到現在rax1.x的變更,中間有過一些前端資產的積累,但是由於遷移的成本後期都不再維護,如何用更小的成本讓業務層平穩過渡到新的技術體系?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於以上的問題我們希望能用框架一併解決,對於該框架的目標主要包括:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提高代碼可複用性"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"規範代碼,降低長期維護成本"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"降低業務層與技術體系的關聯"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"思路"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於提效,其中比較重要的是相同的代碼不要重複寫,做更細的區分和提取,提高可複用的顆粒度。另一方面是解決現有開發下比較影響開發效率的問題。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"組件的分層"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們將面向組件的開發模式分爲UI層View和邏輯層Store,以Interface進行隔離和耦合。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/eb\/ebeff67e026c71b72320f17b07e80d2e.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"圖一:組件構成 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在UI層無需關心狀態的流轉,只負責展示和交互方法的調用,DOM相關的動畫交互等行爲邏輯也會放到該層中。 "}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/9d\/9dd62109f0124e166340f3f60818e6e3.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"圖二:組件分工"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在確認了分層的邏輯後自然就引入了Interface,主要分爲兩部分:一部分是IProps,申明該組件所需的Props,在使用者調用該組件時進行對應的提示和約束;另一部分則負責連接Store和View,其中包括狀態state和交互方法;見下面的Interface示例:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"export interface IMultiScrollerProps {\n tabs: string[];\n onTabChange?(i: number): void;\n}\n\nexport interface IMultiScroller extends IBase {\n readonly tabIndex: number;\n readonly tabSource: ITabItem[];\n readonly children: any[];\n\n onSwiperChange(i: number): void;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結一下:所有的state和交互方法都在store中管理,供View消費;View中只負責和dom相關的邏輯操作,View和store的職責分界線就是View和store分別單獨使用時其交互和效果都能保持不變;以此實現View和store分別能有更多的複用。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"狀態管理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現有的業務開發中基本所有的需求都是基於hooks的狀態管理,主要存在以下問題:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於較複雜的組件hooks在多次迭代後的維護成本會非常高;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有時候,你的useEffect依賴某個函數的不可變性,這個函數的不可變性又依賴於另一個函數的不可變性,這樣便形成了一條依賴鏈。一旦這條依賴鏈的某個節點意外地被改變了,那麼useEffect就被意外地觸發了後面的情況就會變得不可控。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步陷阱"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態的修改是異步的 useState返回的修改函數是異步的,並不會直接生效,所以此時讀取該值獲取到的是舊值。要在下次重繪才能獲取新值。不要試圖在更改狀態之後立馬獲取狀態。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"const [value, setValue] = useState(0);\nsetValue(100);\nconsole.log(value); \/\/ {\n console.log('setAnotherValue', value) \/\/ $$didMount -> $$unMount"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"demo"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Interface.ts"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { IBase } from '@ali\/idlefish-linke';\nexport interface IComponentProps { \/\/ 組件所需props\n tabs: string[];\n onTabChange?(i: number): void;\n}\nexport interface IComponent extends IBase { \/\/ 連接view和store的state&交互方法\n readonly items: any[];\n\n handleLoadmore(): void;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"index.tsx"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { observer } from \"@ali\/idlefish-linke\";\nimport Store from '.\/store';\nimport { IComponent, IComponentProps } from '.\/interface';\nfunction Component({items, handleLoadmore}: IComponent) {\n return (\n \n {\n items.map(item => {\n return {item.title}\n })\n }\n load more\n \n )\n}\nexport default observer(Component, Store);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"store.ts"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { makeAutoObservable } from \"@ali\/idlefish-linke\";\nimport { IComponent } from '.\/interface';\nexport default class ComponentStore implements IComponent {\n \/**\n * 所有狀態變化必須通過$$set來觸發Effect\n * $$set賦值來自於makeAutoObservable(this);\n * this.$$set('items', [])\n *\/\n $$set;\n \/**\n * 帶初始值的屬性會自動被觀測\n *\/\n items: any[] = [];\n page: 1;\n\n constructor() {\n \/\/ 自動observable該類\n makeAutoObservable(this);\n }\n $$setProps(props) {\n ... \/\/ 對props的處理可以放到這裏\n }\n\n $$didMount() { \/\/ 通過 $$didMount \/ $$unMount 來感知view的生命週期\n this.fetch();\n }\n\n fetch () {\n mtop.request('mtop.xxx', {page})\n .then(d => {\n this.$$set('items', d.list);\n })\n }\n\n handleLoadmore = () => {\n this.$$set('page', this.page++);\n this.fetch();\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面就是一個完整的組件demo。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"對比"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在的組件開發模塊模式如下圖六所示,以組件爲單位所有的邏輯是耦合在一起的,相互之間沒有分界,即便是相同的樣式也很難實現複用。無論是在代碼理解還是二次開發上都存在較大的成本和不穩定性風險。 "}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/fb\/fbfd4cc92273a9e2b5cebe90862591dd.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"圖六:原組件的開發模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於Linke的組件開發模式如下圖所示: "}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/a4\/a40626d82edef7ef68f8d2b88fdf6444.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":" 圖七:基於Linke的組件開發模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"View和Store相對獨立沒有強耦合性,這樣的好處顯而易見:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"• 通過閱讀Interface就能知道Store\/View的基本邏輯,減少理解成本"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"• 數據邏輯和View邏輯分別在Store和View中管理,真正實現各司其職,減少維護成本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"• 最重要的一點是通過分離讓Store和View分別實現了複用,組合不同的Store\/View生成不同的組件 "}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/0f\/0f19ffaed3a2cb85a9274b27a428c57b.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"圖八:Store分別和不同的View組合"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/42\/42e8ccb19731ec05e284292b3c953a94.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"圖九:不同的Store和同一個View組合"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前Linke已經應用在了閒魚前端各個新項目中,包括2個線上項目和3個正在開發的項目收益明顯,什麼功能的代碼在什麼位置一目瞭然配合Interface中的註釋大大減少了接手項目的理解成本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通用基礎組件和業務組件都在有序的抽離中,同時隨着View\/Store庫的不斷豐富,可以複用的物料資源增加,不同業務和同一業務不同場景中可以複用的View\/Store越來越多,在一定程度上大大減少開發成本提高效率。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"展望"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前新財年除了現有的H5業務外,最大的特點是會對各個小程序做一些流量探索,比如淘系輕應用、微信小程序、支付寶輕應用等,這些應用的特點是與端內的H5業務及其相似,但是會有各自的細微差異。所以我們也在探索基於Linke對此類業務場景的提效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:閒魚技術(ID:XYtech_Alibaba)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/uv_h2cePAEVo6zElKVdL1A","title":"xxx","type":null},"content":[{"type":"text","text":"低成本可複用前端框架——Linke"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章