日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

React单页如何规划路由、设计Store、划分模块、按需加载

發(fā)布時間:2024/4/14 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React单页如何规划路由、设计Store、划分模块、按需加载 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
  • 本項目地址:react-coat-helloworld
  • react-coat 同時支持瀏覽器渲染(SPA)和服務器渲染(SSR),本 Demo 僅演示瀏覽器渲染,請先了解一下:react-coat

第一站:Helloworld

安裝

git clone https://github.com/wooline/react-coat-helloworld.git npm install

運行

  • npm start 以開發(fā)模式運行
  • npm run build 以產(chǎn)品模式編譯生成文件
  • npm run prod-express-demo 以產(chǎn)品模式編譯生成文件并啟用一個 express 做 demo
  • npm run gen-icon 自動生成 iconfont 文件及 ts 類型

查看在線 Demo

  • 點擊查看在線 Demo

關(guān)于腳手架

  • 采用 webpack 4.0 為核心搭建,無二次封裝,干凈透明
  • 采用 typescript 作開發(fā)語言,使用 Postcss 及 less 構(gòu)建 css
  • 不使用 css module,用模塊化命名空間保證 css 不沖突
  • 采用 editorconfig > prettier 作統(tǒng)一的風格配置,建議使用 vscode 作為 IDE,并安裝 prettier 插件以自動格式化
  • 采用 tslint、eslint、stylelint 作代碼檢查

PeerDependencies

開發(fā)環(huán)境需要很多的 dependencies,你可以自行安裝特定版本,如果特殊要求,建議本站提供的 react-coat-pkg 以及 react-coat-dev-pkg,它們已經(jīng)包含了絕大部分 dependencies。

TS 類型的定義

使用 Typescript 意味著使用強類型,我們把業(yè)務實體中 TS 類型定義分兩大類:API類型和Entity類型。

  • API 類型:指的是來自于后臺 API 輸入的類型,它們可能直接由 swagger 生成,或是機器生成。
  • Entity 類型:指的是本系統(tǒng)為業(yè)務實體建模而定義的類型,每個業(yè)務實體(resource)都會有定義。

理想狀況下,API 類型和 Entity 類型會保持一致,因為業(yè)務邏輯是同一套,但實際開發(fā)中,可能因為前后端并行開發(fā)、或者前后端視角不同而出現(xiàn)兩者各表。

為了充分的解耦,我們允許這種不一致,我們把 API 類型在源頭就轉(zhuǎn)化為 Entity 類型,而在本系統(tǒng)的代碼邏輯中,不直接使用 API 類型,應當使用自已定義的 Entity 類型,以減少其它系統(tǒng)對本系統(tǒng)的影響。

假定項目:旅途 web app

主要頁面:

  • 旅游路線展示
  • 旅途小視頻展示
  • 站內(nèi)信展示(需登錄)
  • 評論展示 (訪客可查看評論,發(fā)表則需登錄)

項目要求

  • web SPA 單頁應用
  • 主要用于 mobile 瀏覽器,也可以適應于桌面瀏覽器
  • 無 SEO 要求,但需要能將當前頁面分享給他人
  • 初次進入本站時,顯示 welcome 廣告,并倒計時

路由規(guī)劃

SPA 單頁不就一個頁面么?為什么還需要規(guī)劃路由呢?

  • 其一,為了用戶刷新時盡可能的保持當前展示
  • 其二,為了用戶能將當前展示通過 url 分享給他人
  • 其三,為了后續(xù)的 SEO

path 規(guī)劃

根據(jù)項目需求及 UI 圖,我們初步規(guī)劃主要路由 path 如下:

  • 旅行路線列表 photosList:/photos
  • 旅行路線詳情 photosItem:/photos/:photoId
  • 分享小視頻列表 videosList:/videos
  • 分享小視頻詳情 videosItem:/videos/:videoId
  • 站內(nèi)信列表 messagesList:/messages

參數(shù)規(guī)劃

因為列表頁是有分頁、有搜索的,所以列表類型的路由是有參數(shù)的,比如:

/photos?title=張家界&page=3&pageSize=20

我們估且將這部分查詢列表條件叫"ListSearch",但除了ListSearch之外,也可能會出現(xiàn)別的路由參數(shù),用來控制其它條件(本 demo 暫未涉及),比如:

/photos?title=張家界&page=3&pageSize=20&showComment=true

所以,如果參數(shù)一多,用扁平的一維結(jié)構(gòu)就變得不好表達。而且,利用 URL 參數(shù)存數(shù)據(jù),數(shù)據(jù)將全變成為字符串。比如id=2,你無法知道 2 是數(shù)字型還是字符型,這樣會讓后續(xù)接收處理變得繁重。所以,我們使用 JSON 來序列化第二級參數(shù),比如:

/photos?search={title:"張家界",page:3,pageSize:20}&showComment=true

這樣做也有個不好的地方,就是需要 encodeURI,然后特殊字符會變得比較丑。

路由參數(shù)默認值

為了縮短 URL 長度,本框架設(shè)計了參數(shù)默認值,如果某參數(shù)和默認值相同,可以省去。我們需要做兩項工作:

  • 生成 Url 查詢條件時,對比默認值,如果相同,則省去
原值:{title:"張家界",page:1,pageSize:20} 默認值: {title:"",page:1,pageSize:20},省去后為:{title:"張家界"}

原值:{title:"",page:1,pageSize:20} 默認值: {title:"",page:1,pageSize:20},省去后為:空

  • 收到 Url 查詢條件時,將查詢條件和默認值 merge
/photos?search={page:2} === photos?search={title:"",page:2,pageSize:20}

/photos === photos?search={title:"",page:1,pageSize:20}

  • 處理 null、undefined

由于接收 Url 參數(shù)時,如果某 key 為 undefined,我們會用相應的默值將其填充,所以不能將 undefined 作為路由參數(shù)值定義,改為使用 null。也就是說,路由參數(shù)中的每一項,都是必填的,比如:

// 路由參數(shù)定義時,每一項都必填,以下為錯誤示例 interface ListSearch{title?:string,age?:number } // 改為如下正確定義: interface ListSearch{title:string | null,age:number | null }
  • 區(qū)分:原始路由參數(shù)(SearchData) 默認路由參數(shù)(SearchData) 和 完整路由參數(shù)(WholeSearchData)。完整路由參數(shù)(WholeSearchData) = merage(默認路由參數(shù)(SearchData), 原始路由參數(shù)(SearchData))

    • 原始路由參數(shù)(SearchData)每一項都是可選的,用 TS 類型表示為:Partial<WholeSearchData>
    • 完整路由參數(shù)(WholeSearchData)每一項都是必填的,用 TS 類型表示為:Required<SearchData>
    • 默認路由參數(shù)(SearchData)和完整路由參數(shù)(WholeSearchData)類型一致

不直接使用路由狀態(tài)

路由及其參數(shù)本質(zhì)上也是一種 Store,與 Redux Store 一樣,反映當前程序的某些狀態(tài)。但它是片面的,是瞬時的,是不穩(wěn)定的,我們把它看作是 Redux Store 的一種冗余。所以最好不要在程序中直接依賴和使用它,而是控制住它的入口和出口,第一時間在其源頭進行消化轉(zhuǎn)換,讓其成為整個 Redux Store 的一部分,后續(xù)的運行中,我們直接依賴 Redux Store。這樣,我們就將程序與路由設(shè)計解耦了,程序有更大的靈活度甚至可以遷移到無 URL 概念的其它運行環(huán)境中。

模塊規(guī)劃

模塊與 Page 無關(guān)

劃分模塊可以很好的拆解功能,化繁為簡,并且對內(nèi)隱藏細節(jié),對外暴露少量接口。劃分模塊的標準是高內(nèi)聚,低耦合,而不是以 Page 或是 View,一個模塊包含某些完整的業(yè)務功能,這些功能可能涉及到多個 Page 或多個 View。

所以回過頭,看我們的項目需求和 UI 圖,大體上可以分為三個模塊:

  • photos //旅游線路展示
  • videos //分享視頻展示
  • messages //站內(nèi)消息展示

這三個模塊顯而易見,但是我們注意到:“圖片詳情”和“視頻詳情”都包含“評論展示”,而“評論展示”本身又具有分頁、排序、詳情展示、創(chuàng)建回復等功能,它具有自已獨立的邏輯,只不過在 view 上被 photoDetail 和 videoDetail 嵌套了,所以將“評論展示”獨立劃分成一個模塊是合適的。

另個,整個程序應當有個啟動模塊,它是“上帝視角模塊”,它可以做一些公共事業(yè),必要的時候也可以用來做多個模塊之間的協(xié)調(diào)和調(diào)度,我們叫把它叫做 applicatioin 模塊。

所以最終,本 Demo 被劃分為 5 個模塊:

  • app // 啟動模塊
  • photos //旅游線路展示
  • videos //分享視頻展示
  • messages //站內(nèi)消息展示
  • comments //評論展示

為模塊劃分 View

每個模塊可能包含一組 View,View 反映某些特定的業(yè)務邏輯。View 就是 React 中的 Component,那反過來 Component 就是 View 么?非也,它們之間還是有些區(qū)別的:

  • view 展現(xiàn)的是 Store 數(shù)據(jù),更偏重于表現(xiàn)特定的具體的業(yè)務邏輯,所以它的 props 一般是直接用 mapStateToProps connect 到 store。
  • component 體現(xiàn)的是一個沒有業(yè)務邏輯上下文的純組件,它的 props 一般來源于父級傳遞。
  • component 通常是公共的,而 view 通常非公用

回過頭,看我們的項目需求和 UI 圖,大體上劃分以下 view:

  • app views:Main、TopNav、BottomNav、LoginPop、Welcome、Loading
  • photos views:Main、List、Details
  • videos views:Main、List、Details
  • messages views:Main、List
  • comments views:Main、List、Details、Editor

目錄結(jié)構(gòu)

經(jīng)過上面的分析,我們有了項目大至的骨架,由于模塊比較少,所以我們就不再用二級目錄分類了:

src ├── asset // 存放公共靜態(tài)資源 │ ├── css │ ├── imgs │ └── font ├── entity // 存放業(yè)務實體TS類型定義 ├── common // 存放公共代碼 ├── components // 存放React公共組件 ├── modules │ ├── app │ │ ├── views │ │ │ ├── TopNav │ │ │ ├── BottomNav │ │ │ ├── ... │ │ │ └── index.ts //導出給其它模塊使用的view │ │ ├── model.ts //定義ModuleState和ModuleActions │ │ ├── api //將本模塊需要的后臺api封裝一下 │ │ ├── facade.ts //導出本模塊對外的邏輯接口(類型、Actions、路由默認參數(shù)) │ │ └── index.ts //導出本模塊實體(view和model) │ ├── photos │ │ ├── views │ │ ├── model.ts │ │ ├── api │ │ ├── facade.ts │ │ └── index.ts │ ├── videos │ ├── messages │ ├── comments │ ├── names.ts //定義模塊名,使用枚舉類型來保證不重復 │ └── index.ts //導出模塊的全局設(shè)置,如RootState類型、模塊載入方式等 └──index.tsx 啟動入口

facade.ts

其它目錄都好理解,注意到每個 module 目錄中,有一個 facade.ts 的文件,冒似它與 index.ts 一樣都是導出本模塊,那為什么不合并成一個呢?

  • index.ts 導出的是整個模塊的物理代碼,因為模塊是較為獨立的,所以我們一般希望將整個模塊的代碼打包成一個獨立的 chunk 文件。
  • facade.ts 僅導出本模塊的一些類型和邏輯接口,我們知道 TS 類型在編譯之后是會被徹底抹去的,而接口僅僅是一個空的句柄。假如在 ModuleA 中需要 dispatch ModuleB 的 action,我們僅需要 import ModuleB 的 facade.ts,它只是一個空的句柄而以,并不會引起兩個模塊代碼的物理依賴。

配置模塊

問:在 react-coat 中怎么配置一個模塊?包括打包、加載、注冊、管理其生命周期等?

答:./src/modules 根目錄下的 index.ts 文件為模塊總的配置文件,增加一個模塊,只需要在此配置一下

// ./src/modules/index.ts// 一個驗證器,利用TS類型來確保增加一個module時,相關(guān)的配置都同時增加了 type ModulesDefined<T extends {[key in ModuleNames]: any}> = T;// 定義模塊的加載方案,同步或者異步均可 export const moduleGetter = {[ModuleNames.app]: () => {return import(/* webpackChunkName: "app" */ "modules/app");},[ModuleNames.photos]: () => {return import(/* webpackChunkName: "photos" */ "modules/photos");},[ModuleNames.videos]: () => {return import(/* webpackChunkName: "videos" */ "modules/videos");},[ModuleNames.messages]: () => {return import(/* webpackChunkName: "messages" */ "modules/messages");},[ModuleNames.comments]: () => {return import(/* webpackChunkName: "comments" */ "modules/comments");}, };export type ModuleGetter = ModulesDefined<typeof moduleGetter>; // 驗證一下是否有模塊忘了配置// 定義整站Module States interface States {[ModuleNames.app]: AppState;[ModuleNames.photos]: PhotosState;[ModuleNames.videos]: VideosState;[ModuleNames.messages]: MessagesState;[ModuleNames.comments]: CommentsState; }// 定義整站的Root State export type RootState = BaseState & ModulesDefined<States>; // 驗證一下是否有模塊忘了配置

路由和加載

本 Demo 直接使用 react-router V4,路由即組件,所以并不需要什么特別的路由配置,直接在./app/views/Main.tsx 中:

const PhotosView = loadView(moduleGetter, ModuleNames.photos, "Main"); const VideosView = loadView(moduleGetter, ModuleNames.videos, "Main"); const MessagesView = loadView(moduleGetter, ModuleNames.messages, "Main");<Switch><Redirect exact={true} path="/" to="/photos" /><Route exact={false} path="/photos" component={PhotosView} /><Route exact={false} path="/videos" component={VideosView} /><Route exact={false} path="/messages" component={MessagesView} /><Route component={NotFound} /> </Switch>

使用 loadView()表示異步按需加載一個 View,如果你不想按需加載,完全可以直接 import:

import {Main as PhotosView} from "modules/photos/views"

載入 View 時自動載入其相關(guān)的模塊并初始化 Model。沒有 Model,view 是沒有“靈魂”的,所以在載入 View 時,框架會自動載入其 Model 并完成初始化,這個過程包含 3 步:

  • 1.載入模塊對應的 JS Chunk 包
  • 2.初始化模塊 Model,派發(fā) module/INIT Action
  • 3.模塊可以監(jiān)聽自已的 module/INIT Action,作出初始化行為,如獲取遠程數(shù)據(jù)等

Redux Store 結(jié)構(gòu)

module 的劃分不僅體現(xiàn)在工程目錄上,而體現(xiàn)在 Redux Store 中:

router: { // 由 connected-react-router 生成location: {pathname: '/photos',search: '',hash: '#refresh=true',key: 'gb9ick'},action: 'PUSH'},app: {...}, // app ModuleStatephotos: { // photos ModuleStateisModule: true, // 框架自動生成,標明該節(jié)點為一個ModuleStatelistSearch: { // 列表搜索條件title: '',page: 1,pageSize: 10},listItems: [ // 列表數(shù)據(jù){id: '1',title: '新加坡+吉隆坡+馬六甲6或7日跟團游',departure: '無錫',type: '跟團游',price: 2499,hot: 265,coverUrl: '/imgs/1.jpg'},...],listSummary: {page: 1,pageSize: 5,totalItems: 10,totalPages: 2}},messages: {...}, // messages ModuleStatecomments: {...}, // comments ModuleState }

具體實現(xiàn)

見 Demo 源碼,有注釋

美中不足

路由規(guī)劃的不足

到目前為止,本 Demo 完成了項目要求中的內(nèi)容,接下來,業(yè)務看了之后提出了幾個問題:

  • 無法分享指定的“評論”,評論是很重要的吸引眼球的內(nèi)容,我們希望分享鏈接時,可以指定評論。

目前可以分享的路由只有 5 種:

- /photos - /photos/1 - /videos - /videos/1 - /messages

看樣子,我們得增加:

/photos/1/comments/3 //展示id為3的評論
  • 評論內(nèi)容對以后的 SEO 很重要,我們希望路由能控制評論列表翻頁和排序:
/photos/1?comments-search={page:2,sort:"createDate"}
  • 目前我們的項目主要用于移動瀏覽器訪問,很多 android 用戶習慣用手機下面的返回鍵,來撤消操作,如關(guān)閉彈窗等,能否模擬一下原生 APP?

思考:android 用戶點擊手機下面的返回鍵會引起瀏覽器的后退,后退關(guān)閉彈窗,那就需要在彈出彈窗時增加一條 URL 記錄
結(jié)論:Url 路由不只用來記錄展示哪個 Page、哪個 View,還得標識一些交互操作,完全顛覆了傳統(tǒng)的路由觀念了。

路由效驗的不足

看樣子,路由會越來越復雜,到目前為止,我們還沒有在 TS 中很好的管理路由參數(shù),拼接 URL 時沒有做 TS 類型的校驗。對于 pathname 我們都是直接用字符串寫死在程序中,比如: if(pathname === "/photos"){.... }const arr = pathname.match(/^\/photos\/(\d+)$/);

這樣直接 hardcode 似利不是很好,如果后其產(chǎn)品想換一下名稱怎么搞。

Model 中重復寫同樣的代碼

注意到,photos/model.ts、videos/model.ts 中,90%的代碼是一樣的,為什么?因為它們兩個模塊基本上功能都是差不多的:列表展示、搜索、獲取詳情...

其實不只是 photos 和 videos,套用 RestFul 的理念,我們用網(wǎng)頁交互的過程就是在對“資源 Resource”進行維護,無外乎“增刪改查”這些基本操作,大部分情況下,它們的邏輯是相似的。由其是在后臺系統(tǒng)中,基本上連 UI 界面也可以標準化,如果將這部分“增刪改查”的邏輯提取出來,模塊可以省去不少重復的代碼。

下一個 Demo

既然有這么多美中不足,那我們就期待在下一個 Demo 中一步步解決它吧

進階:SPA(單頁應用)

總結(jié)

以上是生活随笔為你收集整理的React单页如何规划路由、设计Store、划分模块、按需加载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产成人精品免费看视频 | 欧美亚洲不卡 | 国产精品久久久久不卡 | 非洲黄色片| 日韩成人高清在线 | 亚洲精品久久久蜜桃网尤妮丝 | 中文字幕日韩亚洲 | 最新版天堂资源在线 | 日日爱886 | √天堂中文官网8在线 | 丝袜ol美脚秘书在线播放 | 日韩欧美亚洲一区 | 欧美大片xxxx | 超碰97在线人人 | 国产精品国产三级国产专播精品人 | 日韩中文一区二区三区 | 久久精品无码一区二区三区毛片 | 午夜嘿嘿| 日日操夜夜骑 | 99精品久久久久 | 久久99久久99精品蜜柚传媒 | 男生看的污网站 | 国产做受高潮 | av看片在线 | 天天干天天干天天干 | 欧美日韩在线观看一区 | 日韩国产欧美在线视频 | 你懂的91 | 欧美a级片视频 | 日日鲁鲁鲁夜夜爽爽狠狠视频97 | 在线免费观看av不卡 | 日韩有码视频在线 | 日本人妻不卡一区二区三区中文字幕 | 日韩二区三区 | 久草免费福利视频 | 在线观看精品视频 | 久久精品aaaaaa毛片 | 深夜福利网 | 婷婷丁香亚洲 | 一卡二卡三卡在线 | 毛片aa| 欧美三区四区 | 免费国产黄色片 | 好吊一区二区三区 | 亚洲黄色在线观看视频 | 在线观看深夜视频 | 9i看片成人免费高清 | 日本体内she精高潮 男女视频在线免费观看 | 朝鲜黄色片| 欧美日韩a级片 | 亚洲国产视频网站 | 亚洲成人av一区二区三区 | 日韩乱码一区二区三区 | 欧美xxx性| 撕开少妇裙子猛然进入 | 天天射影院 | 久久羞羞| 免费黄色短片 | www四虎精品视频免费网站 | 国产色99 | 女同vk| 亚洲ⅴ国产v天堂a无码二区 | 久草中文在线视频 | 伊人网中文字幕 | 91中文字幕永久在线 | 亚洲天堂网在线观看视频 | 国产精品亚洲无码 | 午夜视频在线观看免费视频 | 成人a毛片久久免费播放 | 中文字幕精品三区 | 国产黄色片免费观看 | 在线观看免费高清视频 | 奇米影视av | 国产精品乱码一区二三区小蝌蚪 | 露脸丨91丨九色露脸 | 国内自拍视频网站 | 91入囗| 人人妻人人澡人人爽精品欧美一区 | 国产a黄 | 96日本xxxxxⅹxxx70| 免费观看日批视频 | 韩国视频一区二区三区 | 中文字幕一区二区在线老色批影视 | 伊人激情网| 天天拍天天操 | 久草视频播放 | 视频在线不卡 | 亚洲人交配 | 精品成人无码一区二区三区 | 欧美午夜激情影院 | 99国产精品国产精品九九 | 一区二区三区免费 | 性av在线 | 美日韩av在线 | 打屁股无遮挡网站 | 成人激情自拍 | 蜜臀一区二区三区 | 丝袜诱惑一区 | 精品在线免费观看视频 |