1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 6215字,需瀏覽 13分鐘

         ·

        2020-12-08 10:45

        前文

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

        ?

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

        ?

        為什么使用源碼

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

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

        源碼架構(gòu)

        架構(gòu)圖
        ?

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

        ?

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

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

        分工組織

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

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

        工程目錄

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


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

        image.png

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


        ?

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

        ?

        補(bǔ)充

        ?

        這里補(bǔ)充下動態(tài)加載,以及入口 index 的寫法。理論上這部分,在使用這套架構(gòu)的同學(xué),無需關(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>

        ??);

        模塊動態(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"}
        ??????/>
        ??????
        ????

        ??);
        }

        /**
        ?*?動態(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)出
        ?

        避免文件引入過多過雜

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

        reducers

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

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


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

        image.png

        componentConfig



        ?

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

        ?
        image.png

        component


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

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

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

        Demo 演示

        ?

        以彈層為例

        ?


        將所有彈層看做為一個模塊,只是內(nèi)容不同而已。而內(nèi)容,即為我們之前說的組件目錄結(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)入對應(yīng) action

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

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

        ?


        效果


        頁面容器

        ?

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

        ?

        改造點(diǎn)「基于 body 滾動」。

        因?yàn)槲覀兡壳绊撁娑际?h5 頁面了,之前則是 weex 的。所以對于容器的底層,之前使用的 RecycleView :固定 div 高度,基于 overflow 來實(shí)現(xiàn)滾動的。

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

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

        ?

        源碼頁面中使用的部分

        ?

        重構(gòu)后的使用

        ?

        基本沒有太大改變

        ?

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

        type

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

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

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

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

        ????wrapStyles?:?CSSProperties;
        }

        /**
        ?*?滾動到頂部組件屬性
        ?*/

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

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

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

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

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

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

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

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

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

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

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

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

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

        ????children:RaxNode;
        ????/**
        ?????*?隱藏滾動到頂部
        ?????*/

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

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

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

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

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

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

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

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

        ????onEndReachedThreshold?:?number;
        }

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

        ????title:?string;
        ????/**
        ?????*?頁面?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)聽滾動
        useListenScroll();
        // 設(shè)置標(biāo)題
        useSetTitle(title);
        // 監(jiān)聽 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 };

        通過 Fragment 包裹,主題是 ContentWrap,ErrorPage、VConsole、Holder放置主體以外。

        ?

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

        ?

        廣播事件

        /**
        ?*?Events?以頁面為單位
        ?*/

        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

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

        安裝:tnpm install -g @ali/pmcli

        help

        這里在介紹下命令:

        基本使用

        pmc init

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

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

        ?

        pmc add-page

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

        img

        推薦使用 simpleSource、customStateManage

        頁面模板地址:add-page

        pmc add-mod

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

        模塊模板地址為:add-mod

        pmc init-mod

        調(diào)用def init tbe-mod,并且將倉庫升級為支持 ts 開發(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 命令,并同時升級為 ts 編碼環(huán)境。

        ?

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

        ?

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

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


        如果你喜歡探討技術(shù),或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當(dāng)然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。魚頭的微信號是:krisChans95 也可以掃碼關(guān)注公眾號,訂閱更多精彩內(nèi)容。


        瀏覽 54
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            免费开心激情五月天在线视频 | 色婷婷六月 | 日韩A级黄片 | 美女艹逼视频网站 | 欧美亚洲日韩国产 | www.黄色av | 操大奶子 | 日韩中文字幕网站 | 人人妻人人超 | 特黄一级裸片 |