<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端架構(gòu)探索與實(shí)踐

          共 6106字,需瀏覽 13分鐘

           ·

          2020-09-18 20:13

          前文

          從思考、到探索、到腳手架的產(chǎn)生,后面經(jīng)過(guò)一系列的項(xiàng)目開(kāi)發(fā),不斷優(yōu)化和改良。目前已經(jīng)成功應(yīng)用到房產(chǎn)中間頁(yè)(改名天貓房產(chǎn))中。這里,做一下總結(jié)。

          ?

          「僅為拋磚,希望看完這個(gè)系列的同學(xué)可以相互探討學(xué)習(xí)一下」

          ?

          為什么使用源碼

          目前,我們大多數(shù)頁(yè)面,包括搜索頁(yè)、頻道頁(yè)都是大黃蜂搭建的頁(yè)面。至于搭建的優(yōu)點(diǎn),這里就不多贅述了。而我們使用源碼編寫,主要是基于以下幾點(diǎn)思考:

          • 穩(wěn)定性要求高
          • 頁(yè)面模塊多而不定
          • 快速回滾方案
          • 模塊通信復(fù)雜

          源碼架構(gòu)

          架構(gòu)圖
          ?

          架構(gòu)圖需要調(diào)整。此為稿圖,位置放的有些不合理,表述不清

          ?

          底層技術(shù)支撐主要采用 Rax1.0 + TypeScript + Jest 編碼。通過(guò) pmcli生成項(xiàng)目腳手架。腳手架提供基礎(chǔ)的文件代碼組織和組件。包括 ComponentscommonUtilsdocumentmodules等。當(dāng)然,這些組件最終會(huì)被抽離到 puicom group 下。

          再往上,是容器層。容器提供一些可插拔的 hooks 能力。并且根據(jù) component 的配置來(lái)渲染不同的組件到頁(yè)面中,首屏組件和按需加載組件。最后,支撐到每一個(gè)對(duì)應(yīng)的頁(yè)面里面。

          分工組織

          對(duì)于一個(gè)頁(yè)面,無(wú)論是 react 還是 rax,其實(shí)都是 fn(x)=>UI 的過(guò)程。所以整理流程無(wú)非就是拿到接口屬于渲染到 UI 中。所以對(duì)于中間頁(yè)的架構(gòu)而言也是如此。

          首先拿到基本的接口數(shù)據(jù),通過(guò)自定義的狀態(tài)管理,掛載到全局 state 對(duì)應(yīng)的組件名下。容器層通過(guò)組件的配置文件,渲染對(duì)應(yīng)的組件。最終呈現(xiàn)出完成的一個(gè)頁(yè)面。當(dāng)然,其中會(huì)有一些額外的容器附屬功能,比如喚起手淘、監(jiān)聽(tīng)鍵盤彈起等這個(gè)按需插入對(duì)應(yīng) hooks 即可。屬于業(yè)務(wù)層邏輯。

          工程目錄

          工程結(jié)構(gòu)


          頁(yè)面結(jié)構(gòu)

          image.png

          模塊結(jié)構(gòu)


          ?

          以上結(jié)構(gòu)在之前文章中都有介紹到

          ?

          補(bǔ)充

          ?

          這里補(bǔ)充下動(dòng)態(tài)加載,以及入口 index 的寫法。理論上這部分,在使用這套架構(gòu)的同學(xué),無(wú)需關(guān)心

          ?

          index.tsx

          return?(
          ????<H5PageContainer
          ??????title={PAGE_TITLE}
          ??????showPlaceHolder={isLoading}
          ??????renderPlaceHolder={renderLoadingPage}
          ??????renderHeader={renderHeader}
          ??????renderFooter={renderFooter}
          ??????toTopProps={{
          ????????threshold:?400,
          ????????bottom:?203,
          ??????}}
          ??????customStyles={{
          ????????headWrapStyles:?{
          ??????????zIndex:?6,
          ????????},
          ??????}}
          ????>

          ??????{renderSyncCom(
          ????????asyncComConfig,
          ????????dao,
          ????????dispatch,
          ????????reloadPageNotRefresh,
          ????????reloadTick
          ??????)}
          ??????{renderDemandCom(
          ????????demandComConfig,
          ????????offsetBottom,
          ????????dao,
          ????????dispatch,
          ????????reloadPageNotRefresh,
          ????????reloadTick
          ??????)}
          ??????<BottomAttention?/>
          ????H5PageContainer>

          ??);

          模塊動(dòng)態(tài)加載

          /**
          ?*?按需按需加載容器組件
          ?*
          ?*?@export
          ?*?@param?{*}?props?按需加載的組件?props+path
          ?*?@returns?需按需加載的子組件
          ?*/

          export?default?function(props:?IWrapperProps)?{
          ??const?placeHolderRef:?any?=?useRef(null);
          ??const?{?offsetBottom,?...otherProps?}?=?props;
          ??const?canLoad?=?useDemandLoad(offsetBottom,?placeHolderRef);
          ??const?[comLoaded,?setComLoaded]?=?useState(false);

          ??//?加入?hasLoaded?回調(diào)
          ??const?wrapProps?=?{
          ????...otherProps,
          ????hasLoaded:?setComLoaded,
          ??};

          ??return?(
          ????
          ??????????????x-if={!comLoaded}
          ????????ref={placeHolderRef}
          ????????style={{?width:?750,?height:?500,?marginTop:?20?}}
          ????????source={{?uri:?PLACEHOLDER_PIC?}}
          ????????resizeMode={"contain"}
          ??????/>
          ??????
          ????

          ??);
          }

          /**
          ?*?動(dòng)態(tài)加載
          ?*?@param?props
          ?*/
          function?ImportWrap(props:?IWrapperProps)?{
          ??const?{?path,?...otherProps?}?=?props;
          ??const?[Com,?error]?=?useImport(path);
          ??if?(Com)?{
          ????return?;
          ??}?else?if?(error)?{
          ????console.error(error);
          ????return?null;
          ??}?else?{
          ????return?null;
          ??}
          }

          use-demand-load.ts

          import?{?useState,?useEffect?}?from?'rax';
          import?{?px2rem?}?from?'@ali/puicom-universal-common-unit';

          /**
          ?*?判斷組件按需加載,即將進(jìn)去可視區(qū)
          ?*/

          export?function?useDemandLoad(offsetBottom,?comRef):?boolean?{
          ????const?[canLoad,?setCanLoad]?=?useState(false);
          ????const?comOffsetTop?=?px2rem(comRef?.current?.offsetTop?||?0);

          ????useEffect(()?=>?{
          ????????if?(canLoad)?return;
          ????????if?(offsetBottom?>?comOffsetTop?&&?!canLoad)?{
          ????????????setCanLoad(true);
          ????????}
          ????},?[offsetBottom]);

          ????useEffect(()?=>?{
          ????????setCanLoad(comRef?.current?.offsetTop?0));
          ????},?[comRef]);

          ????return?canLoad;
          }

          模塊編寫與狀態(tài)分發(fā)

          模塊編寫

          types

          編寫模塊數(shù)據(jù)類型
          image.png
          掛載到 dao(dataAccessObject) 下
          image.png
          統(tǒng)一導(dǎo)出
          ?

          避免文件引入過(guò)多過(guò)雜

          ?
          • type/index.d.ts
          image.png

          reducers

          編寫模塊對(duì)應(yīng)reducer

          在 daoReducer 中統(tǒng)一掛載


          數(shù)據(jù)分發(fā)

          image.png

          componentConfig



          ?

          此處 keyName 是 type/dao.d.ts 下聲明的值。會(huì)進(jìn)行強(qiáng)校驗(yàn)。填錯(cuò)則分發(fā)不到對(duì)應(yīng)的組件中

          ?
          image.png

          component


          數(shù)據(jù)在 props.dataSource

          狀態(tài)分發(fā)

          • 模塊聲明需要掛載到 type/dao.d.ts
          • reducer ?需要 combine ?到 dao.reduer.ts
          • useDataInitdispatch 對(duì)應(yīng) Action
          • config ?中配置 (才會(huì)被渲染到 UI)

          Demo 演示

          ?

          以彈層為例

          ?


          將所有彈層看做為一個(gè)模塊,只是內(nèi)容不同而已。而內(nèi)容,即為我們之前說(shuō)的組件目錄結(jié)構(gòu)中的 components 內(nèi)容

          定義模塊 Models

          定義模塊類型

          編寫模塊屬于類型

          掛載到 dao 中
          image.png

          reducer

          編寫組件所需的 reducer
          image.png
          ?

          actions 的注釋非常有必要

          ?
          image.png
          combine 到 dao 中
          image.png

          編寫組件



          組件編寫

          carbon.png

          通信

          導(dǎo)入對(duì)應(yīng) action

          import?{?actions?as?modelActions?}?from?"../../../reducers/models.reducer";
          dispatch
          ?dispatch([modelActions.setModelVisible(true),modelActions.setModelType("setSubscribeType")])
          ?

          觸發(fā) ts 校驗(yàn)

          ?


          效果


          頁(yè)面容器

          ?

          基于拍賣通用容器組件改造

          ?

          改造點(diǎn)「基于 body 滾動(dòng)」

          因?yàn)槲覀兡壳绊?yè)面都是 h5 頁(yè)面了,之前則是 weex 的。所以對(duì)于容器的底層,之前使用的 RecycleView :固定 div 高度,基于 overflow 來(lái)實(shí)現(xiàn)滾動(dòng)的。

          雖然,在 h5 里面這種滾動(dòng)機(jī)制有些”難受“,但是罪不至”換“。但是尷尬至于在于,iOS 的橡皮筋想過(guò),在頁(yè)面滾動(dòng)到頂部以后,如果頁(yè)面有頻繁的動(dòng)畫或者 setState 的時(shí)候,會(huì)導(dǎo)致頁(yè)面重繪,重新回到頂部。與手動(dòng)下拉頁(yè)面容器的橡皮筋效果沖突,而「倒是頁(yè)面瘋狂抖動(dòng)」。所以。。。。重構(gòu)。

          舊版容器功能點(diǎn)

          ?

          源碼頁(yè)面中使用的部分

          ?

          重構(gòu)后的使用

          ?

          基本沒(méi)有太大改變

          ?

          簡(jiǎn)單拆解實(shí)現(xiàn)

          type

          import?{?FunctionComponent,?RaxChild,?RaxChildren,?RaxNode,?CSSProperties?}?from?'rax';

          export?interface?IHeadFootWrapperProps?{
          ????/**
          ?????*?需要渲染的子組件
          ?????*/

          ????comFunc?:?()?=>?FunctionComponent?|?JSX.Element;
          ????/**
          ?????*?組件類型
          ?????*/

          ????type:?"head"?|?"foot",
          ????/**
          ?????*?容器樣式
          ?????*/

          ????wrapStyles?:?CSSProperties;
          }

          /**
          ?*?滾動(dòng)到頂部組件屬性
          ?*/

          export?interface?IScrollToTopProps?{
          ????/**
          ?????*?距離底部距離
          ?????*/

          ????bottom?:?number;
          ????/**
          ?????*?zIndex
          ?????*/

          ????zIndex?:?number;
          ????/**
          ?????*?icon?圖片地址
          ?????*/

          ????icon?:?string;
          ????/**
          ?????*?暗黑模式的?icon?圖片地址
          ?????*/

          ????darkModeIcon?:?string;
          ????/**
          ?????*?icon寬度
          ?????*/

          ????iconWidth?:?number;
          ????/**
          ?????*?icon?高度
          ?????*/

          ????iconHeight?:?number;
          ????/**
          ?????*?滾動(dòng)距離(滾動(dòng)多少觸發(fā))
          ?????*/

          ????threshold?:?number;
          ????/**
          ?????*?點(diǎn)擊回滾到頂部是否有動(dòng)畫
          ?????*/

          ????animated?:?boolean;
          ????/**
          ?????*?距離容器右側(cè)距離
          ?????*/

          ????right?:?number;
          ????/**
          ?????*?展示回調(diào)
          ?????*/

          ????onShow?:?(...args)?=>?void;
          ????/**
          ?????*?消失回調(diào)
          ?????*/

          ????onHide?:?(...args)?=>?void;
          }
          /**
          ?*?內(nèi)容容器
          ?*/

          export?interface?IContentWrapProps{
          ????/**
          ?????*?children
          ?????*/

          ????children:RaxNode;
          ????/**
          ?????*?隱藏滾動(dòng)到頂部
          ?????*/

          ????hiddenScrollToTop?:boolean;
          ?????/**
          ?????*?返回頂部組件?Props
          ?????*/

          ????toTopProps?:?IScrollToTopProps;
          ????/**
          ?????*?渲染頭部
          ?????*/

          ????renderHeader?:?()?=>?FunctionComponent?|?JSX.Element;
          ????/**
          ?????*?渲染底部
          ?????*/

          ????renderFooter?:?()?=>?FunctionComponent?|?JSX.Element;
          ????/**
          ?????*?自定義容器樣式
          ?????*/

          ????customStyles?:?{
          ????????/**
          ?????????*?body?容器樣式
          ?????????*/

          ????????contentWrapStyles?:?CSSProperties;
          ????????/**
          ?????????*?頭部容器樣式
          ?????????*/

          ????????headWrapStyles?:?CSSProperties;
          ????????/**
          ?????????*?底部容器樣式
          ?????????*/

          ????????bottomWrapStyle?:?CSSProperties;
          ????};
          ????/**
          ?????*?距離底部多少距離開(kāi)始觸發(fā)?endReached
          ?????*/

          ????onEndReachedThreshold?:?number;
          }

          export?interface?IContainerProps?extends?IContentWrapProps?{
          ????/**
          ?????*?頁(yè)面標(biāo)題
          ?????*/

          ????title:?string;
          ????/**
          ?????*?頁(yè)面?placeHolder
          ?????*/

          ????renderPlaceHolder?:?()?=>?FunctionComponent?|?JSX.Element;
          ????/**
          ?????*?是否展示?placeH
          ?????*/

          ????showPlaceHolder?:?boolean;
          }

          index.tsx

          const isDebug = isTrue(getUrlParam('pm-debug'));
          export default function({
          children,
          renderFooter,
          renderHeader,
          title,
          onEndReachedThreshold = 0,
          customStyles = {},
          toTopProps = {},
          showPlaceHolder,
          renderPlaceHolder,
          hiddenScrollToTop=false
          }: IContainerProps) {
          if (!isWeb) return null;

          // 監(jiān)聽(tīng)滾動(dòng)
          useListenScroll();
          // 設(shè)置標(biāo)題
          useSetTitle(title);
          // 監(jiān)聽(tīng) error 界面觸發(fā)
          const { errorType } = useListenError();

          return (

          x-if={errorType === "" && !showPlaceHolder}
          renderFooter={renderFooter}
          customStyles={customStyles}
          renderHeader={renderHeader}
          onEndReachedThreshold={onEndReachedThreshold}
          toTopProps={toTopProps}
          hiddenScrollToTop={hiddenScrollToTop}
          >
          {children}

          {renderPlaceHolder && showPlaceHolder && renderPlaceHolder()}



          );
          }

          export { APP_CONTAINER_EVENTS };

          通過(guò) Fragment 包裹,主題是 ContentWrapErrorPageVConsoleHolder放置主體以外。

          ?

          相關(guān) hooks 功能點(diǎn)完全區(qū)分開(kāi)來(lái)

          ?

          廣播事件

          /**
          ?*?Events?以頁(yè)面為單位
          ?*/

          export?const?APP_CONTAINER_EVENTS?=?{
          ????SCROLL:?'H5_PAGE_CONTAINER:SCROLL',
          ????TRIGGER_ERROR:?'H5_PAGE_CONTAINER:TRIGGER_ERROR',
          ????END_REACHED:?'H5_PAGE_CONTAINER:END_REACHED',
          ????HIDE_TO_TOP:?'H5_PAGE_CONTAINER:HIDE_TO_TOP',
          ????RESET_SCROLL:?'H5_PAGE_CONTAINER:RESET_SCROLL',
          ????ENABLE_SCROLL:"H5_PAGE_CONTAINER:H5_PAGE_CONTAINER"
          }

          pm-cli

          詳見(jiàn):pm-cli腳手架,統(tǒng)一阿里拍賣源碼架構(gòu)

          安裝:tnpm install -g @ali/pmcli

          help

          這里在介紹下命令:

          基本使用

          pmc init

          • 在空目錄中調(diào)用,則分兩步工作:
            • 首先調(diào)用 tnpm init rax 初始化出來(lái) rax 官方腳手架目錄
            • 修改 package.jsonname 為當(dāng)前所在文件夾的文件夾名稱
            • 升級(jí)為拍賣源碼架構(gòu),下載對(duì)應(yīng)腳手架模板:init-project
          • 在已init rax后的項(xiàng)目中調(diào)用
            • 升級(jí)為拍賣源碼架構(gòu),下載對(duì)應(yīng)腳手架模板:init-project
          ?

          注意:經(jīng)過(guò) pmc 初始化的項(xiàng)目,在項(xiàng)目根目錄下回存有.pm-cli.config.json 配置文件

          ?

          pmc add-page

          在當(dāng)前 項(xiàng)目中新增頁(yè)面,選擇三種頁(yè)面類型

          img

          推薦使用 simpleSourcecustomStateManage

          頁(yè)面模板地址:add-page

          pmc add-mod

          根據(jù)所選擇頁(yè)面,初始化不同類型的模塊

          模塊模板地址為:add-mod

          pmc init-mod

          調(diào)用def init tbe-mod,并且將倉(cāng)庫(kù)升級(jí)為支持 ts 開(kāi)發(fā)模式

          pmc publish-init

          發(fā)布端架構(gòu)初始化,基于 react 應(yīng)用

          發(fā)布端架構(gòu)模板地址:publish-project

          pmc publish-add

          添加發(fā)布端模塊

          模塊模板地址:publish-mod

          pmc init-mod

          調(diào)用 def init tbe-mod 命令,并同時(shí)升級(jí)為 ts 編碼環(huán)境。

          ?

          配置環(huán)境、安裝依賴、直接運(yùn)行

          ?

          相關(guān)體驗(yàn)地址(部分無(wú)法訪問(wèn))

          • 阿里房產(chǎn)
          • 底層容器 (單獨(dú)抽離組件ing)
          • pmCli
          • ts tbeMod


          瀏覽 87
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天天日天天天干 | 久操7777 | 久久久一曲二曲三曲四曲免费听 | 婷婷在线成人视频精品 | 天天摸天天射 |