<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)探索與實踐

          共 15028字,需瀏覽 31分鐘

           ·

          2021-04-17 00:29

          前文

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

          ?

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

          ?

          為什么使用源碼

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

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

          源碼架構(gòu)

          架構(gòu)圖
          ?

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

          ?

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

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

          分工組織

          對于一個頁面,無論是 react 還是 rax,其實都是 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)在之前文章中都有介紹到

          ?

          補充

          ?

          這里補充下動態(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 (
              <Fragment>
                <Picture
                  x-if={!comLoaded}
                  ref={placeHolderRef}
                  style={{ width: 750, height: 500, marginTop: 20 }}
                  source={{ uri: PLACEHOLDER_PIC }}
                  resizeMode={"contain"}
                />
                <ImportWrap x-if={canLoad} {...wrapProps} />
              </Fragment>
            );
          }

          /**
           * 動態(tài)加載
           * @param props
           */
          function ImportWrap(props: IWrapperProps) {
            const { path, ...otherProps } = props;
            const [Com, error] = useImport(path);
            if (Com) {
              return <Com {...otherProps} />;
            } 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 < (screen.height || screen.availHeight || 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)行強校驗。填錯則分發(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 校驗

          ?


          效果


          頁面容器

          ?

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

          ?

          改造點「基于 body 滾動」

          因為我們目前頁面都是 h5 頁面了,之前則是 weex 的。所以對于容器的底層,之前使用的 RecycleView :固定 div 高度,基于 overflow 來實現(xiàn)滾動的。

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

          舊版容器功能點

          ?

          源碼頁面中使用的部分

          ?

          重構(gòu)后的使用

          ?

          基本沒有太大改變

          ?

          簡單拆解實現(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;
              /**
               * 點擊回滾到頂部是否有動畫
               */

              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 (
          <Fragment>
          <ContentWrap
          x-if={errorType === "" && !showPlaceHolder}
          renderFooter={renderFooter}
          customStyles={customStyles}
          renderHeader={renderHeader}
          onEndReachedThreshold={onEndReachedThreshold}
          toTopProps={toTopProps}
          hiddenScrollToTop={hiddenScrollToTop}
          >
          {children}
          </ContentWrap>
          {renderPlaceHolder && showPlaceHolder && renderPlaceHolder()}
          <ErrorPage type={errorType} x-if={errorType} />
          <VConsole x-if={isDebug}/>
          </Fragment>
          );
          }

          export { APP_CONTAINER_EVENTS };

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

          ?

          相關(guān) hooks 功能點完全區(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

          推薦使用 simpleSourcecustomStateManage

          頁面模板地址: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)境、安裝依賴、直接運行

          ?

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

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


          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  第14色网站 | 中文字幕在线免费视频 | 亚洲xxxxx | 综合久久狼人 | 日日插,日日操 |