單頁面應用,TAB選項卡太多,頁面邏輯是糅合在一起還是重新分路由?

vue和react的單頁面應用,如果一個頁面中選項卡太多的話,是把所有的邏輯寫在一個組件中,用點擊顯示和隱藏的方式,還是拆分成多個路由會比較好呢?類似下面這樣的:

寫在一個組件的好處是:1. 不必要寫重複的代碼量(選項卡都是一樣的,只是激活和不激活的區別而已) 2. 邏輯相對簡單,我說的邏輯省掉了組件之間的交互(比如說變數傳值、返回等操作)

但是也有壞處:1. 如果組件業務太過複雜,那麼代碼量必定會增加,造成閱讀困難 2. 路由的跳轉問題(比如由一個tab切換到其他的tab,那麼路由變化了,返回之後還是返回當前的頁面,而實際中,我可能是要返回到上一個頁面中)3. 數據的傳輸問題,涉及到參數的傳遞,雖然可以很方便地取到,但是還是會有操作的成本浪費

我試過用子路由的方式,公用相同的選項卡,然後監聽hash的變化,在組件每次銷毀的時候,判斷tab選項卡是消失還是隱藏。看上去似乎很好,但是對於返回確實噩夢,因為每一個子路由也是路由呀,如果我在一個頁面的TAB中來回點擊的話,返回的次數可想而知,或許可以設置頁面類型來判斷,但是始終感覺挺複雜。

在此問一下各位大大,有什麼好的建議?


你想複雜了,留個坑,明天再說。

============華麗的分割線===========

昨天本來下班想寫的,但是一個妹子讓幫忙解決一個問題,弄了一個多小時...這裡補上。

其實這裡先分析一下,你這裡提了2個疑問:

(1) tabs 邏輯都寫在一起,直接通過 show, hide 指令的方式進行實現;

(2)使用路由,主要的問題是返回的時候,route stack 回退的問題。

第一種方案:什麼React, Angular, Vue,拿起 「jQuery」 就是干

這個方案也可以進行,你描述的代碼長,代碼重複量大(DRY),其實是可以避免一部分的。

這裡我會假設很多東西,因為你也沒描述太多場景和問題。這裡直接用偽代碼描述:

// 公用部分的數據邏輯
// 視圖的邏輯其實更加內聚更好,對外暴露只有公用的部分
let common = {
getData() { },
paramsDeal() { }
}

// 渲染的整個容器
// 這裡不用提取 Tab 容器,因為邏輯比較簡單,減少通信帶來的開銷
class TabApp extends Component {
state = {
index: 0 // default
}

handleChange(index) {
this.setState({ index });
}

render() {
const tabs = [Tab1, Tab2, Tab3].map(
(Tab, index) =&> (
&
)
)

return (
&
&
{tabs}
& &
{&}
& & )
}
}

// 渲染的內容容器
class ViewApp extends Component {
render() {
const {index} = this.props
const Views = [View1, View2, View3]

return (
&
{Views[index]}
& )
}
}

// 每個 Tab 子組件
const Tab1 = ({ onChange }) =&> {}
const Tab2 = ({ onChange }) =&> {}
const Tab3 = ({ onChange }) =&> {}

// 每個 View 子組件
const View1 = _ =&> {}
const View2 = _ =&> {}
const View3 = _ =&> {}

基本上這個方案就是把整個業務寫到一起,但是通過一點組織來解決部分問題,比如單文件文件內容過多,組件基本的公用上的問題。對於請求層,基本上你能在 `TabApp` 這個容器裡面做,這樣下面子樹的純度還是比較高的。當然,代碼不一定是可以運行的,只是一種描述。Vue 的話應該是差不多的,不過組件的模板渲染那裡會不一樣。這種情況下,你需要自己持久化當前 tab 的選項,下次用戶刷新才能恢復之前狀態。

第二種方案:路由

class TabApp extends Component {
handleLeave() { }

render() {
const tabs = [Tab1, Tab2, Tab3].map(
(tab, index) =&> &
)

return (
&
&{tabs}& {/* 這裡嵌入 View 組件以及組件邏輯 */}
&{this.props.children}& & )
}
}

其中你需要知道 Leave 的事件鉤子,然後點擊返回的時候直接切回上一個父級路由,其實你說的一直返回也可以從設計上解決嘛,給個直接回去的按鈕,哈哈!這個的好處是你不用自己去維護切換狀態。

下面是一些其他 idea

(1) 組件組合

這個比起第一種方案是修改外置,這樣修改的時候只修改子組件就行了,不用修改容器類,其實在業務穩定的情況下,基本修改都是局部的修改,這種情況就要避免去修改整體,樹的修改越低影響越小。容器可以控制一些公用的行為。

class TabApp extends Component {
render() {
const tabs = React.Children.map((child, index) =&> child)
return (
&
{tabs}
& )
}
}

class App extends Component {
render() {
return (
&
&
&
&
&
&

&
&
&
&
&

&

);
}
}

(2) 動態組件

這個其實和我的第一種方案有點像,這個就不貼代碼了...可以查查就有思路了。

總體來說代碼的耦合要看組織,組件的切換的話,其實本質上就是替換,無論什麼樣的替換方式都會有自己的問題,所以選個自己喜歡的。因為你可能還有 loading、error、default 等邏輯,遠比這複雜。


如果用 Angular 就毫不猶豫的分路由,不需要權衡那麼多。這是官方文檔中明確給出的最佳實踐。react不清楚,但用VUE應該可以參考 Angular 的最佳實踐。


我手頭的一個CMS需求也遇到了這個問題,說下我的思考,請大牛們指正

使用路由

QA 1:

Tab頁面的頻繁切換會使History層級比較深,用戶難以直接返回Tab外的頁面

Tab頁面使用子路由,讓結構更加清晰。切換路由的時候使用router.replace

QA 2:

切換路由會重新mounted組件,在數據量大的時候會有閃屏現象

使用transition做一下過渡,然後加個loading

QA 3:

需要監聽hash變化,修改Tab選項卡狀態

不知道你有沒有watch$route,感覺不算麻煩吧

watch: {
"$route" (to, from) {
console.log(to)
}
}

使用組件

QA 1:

無法分享鏈接,因為Tab的狀態是存儲在本地的

因為這個原因我棄用了

QA 2:

每個Tab頁面邏輯差不多,代碼重複。在代碼量大的時候難以閱讀

重複的代碼我們可以使用Mixin,公用的小組件可以拆出幾個Component

QA 3:

組件之間數據傳輸的成本高

這個還沒研究,覺得影響不大

QA 4:

路由歷史記錄的存儲

如果你想存儲在本地,推薦用store


五個子組件

一個父組件

父組件控制子組件顯示與否,不關心子組件內部邏輯。

用啥路由!!!


你要站在用戶角度考慮問題,打開tab1,哪些tab是希望看到的,哪些是根本不會用的,哪些是很少用的,或者哪些是看用戶的,不同用戶常用的tab不一樣。

這個整理出來了再考慮如何實現。

舉個例子,打開餘額頁面時,用戶希望提現,轉帳,查明細,而在這個頁面放一個產品搜索框,可能僅僅是方便你自己。

要是每個頁面用戶看到的tab都一樣,那這樣的tab不應該超過4個。


用路由或者hidden/show都可以,只要把每個tabContent做成組件,路由或是hidden/show兩種開發方式之間改不了多少代碼。

用路由看似對app做了 解耦和,但是考慮到實際情況其實帶來了麻煩,很多opensource-tabbar只支持hidden/show(為了頁面滑動切換時的transition),若想與xxx-router一起工作則需要相當的魔改和扭曲的代碼設計,

比如:

對於React版本antd mobile的Tabbar:https://mobile.ant.design/components/tab-bar-cn/,相關的扭曲代碼:TabBar結合react-router取巧解決辦法 · Issue #600 · ant-design/ant-design-mobi

對於Vue版本mintui的tabbar:給tabbar中的tab-item添加router-link裡面的icon就消失了 · Issue #488 · ElemeFE/mint-ui

當然,也有很大一部分tabbar原生推薦使用路由跳轉的方式,如:F7-Toolbar React Component

另,可以選擇不依賴框架的tabbar組件,如material-components/material-components-web-tabbar,樣式和行為想怎麼改就怎麼改,代碼很少。

所以,如果你的webapp使用了第三方或自己組的組件庫,且組件庫包含tabbar,按例子上推薦的方式來,節約生命,畢竟你從頭寫個沒有bug的tabbar還是不容易的(要不要支持icon tint?要不要支持message-dot小紅點?tab內容頁面緩存策略?)。


當然是使用vue-router,這樣不僅可以簡化代碼,優化項目結構,而且能使用code-split做非同步路由載入,優化首屏載入。

如果你擔心tab選項卡來回切換導致的資源損耗,可以使用vuex來存儲非同步請求到的數據,至於DOM的重寫,這點性能反正我是不在乎的。

另外有了vue-router和vuex後,也可以做SSR,體驗更好。

當然,如果是很小的一個項目,比如圖片輪播,那就不需要vue-router了,隨便寫一個就ok。


瀉藥~我覺得你已經思考的很全面啦。我個人推薦還是要使用路由,好處有以下幾個:

  1. 代碼更利於維護和開發。你如果全部都寫在一個組件裡面的話,一方面,這個組件會特別大,設計的頁面邏輯會很複雜,這對於你後期開發和維護特別不方便。比如你要修改或者新增一個功能,就會變得特別麻煩。而根據功能來劃分為不同組件的話,那麼開發和維護起來就會更加方便。
  2. 為將來做好準備。或許目前來說,你各個tab的邏輯都比較簡單,不過到後面可能會越來越複雜,如果你都寫在一個組件裡面的話,後面每個tab變得更加複雜就難以維護,之後也肯定是要抽出來作為單獨的組件實現的,所以為什麼不現在就做呢
  3. 頁面參數問題和路由問題。有些參數的傳遞的話,其實用路由表示會更加合適一點,而且你也考慮到了,如果你不是按路由的話,你切換到不同的tab後,輸入相同的路由,可能會回到初始的tab,當然你可以使用localStorage等來儲存用戶上次訪問的tab信息,不過最好的當然是直接使用路由不就更直接一點嗎。

以上只是我的個人觀點。


謝邀。

首先寫在一個頁面裡面,出現的問題不光是閱讀困難的問題,很有可能會掉進耦合的坑裡面,等到之後你想拆分的時候,拆都拆不出來。

其次,我也不建議在路由中控制,我覺得路由應該是一個歷史的記錄,比如說列表的分頁。或者是一個需要標記狀態或數據,比如說跳到分享頁面的某個位置、某某詳情頁面。

所以我的建議可能跟大家相反,我建議採用顯示和隱藏的方式

當然如果這個頁面需要分享,比如你在tab2中點擊分享,希望別人點擊鏈接可以直接跳到tab2,那麼要搭配著路由一起用,但是不要採用子路由的方式,直接用query就好了,至於切換history的問題,使用路由裡面的類似replace方法,vue是replace、H5是replaceState、react很久沒用了,不過應該也有。

我大概講一下採用顯示和隱藏的方式我會怎麼做的吧。

我一般會把組件分為基礎組件業務組件

基礎組件可以理解為你的tab,復用性很高,業務組件可以理解為你的tabs裡面的具體內容,它復用很低,或者根本不能復用。

tabs怎麼去做,就不過多的去說了,如果你自己手寫建議你先去看一下第三方的組件庫都是怎麼做的,react看ant desgin,vue看element-ui。

業務組件裡面我建議是不要嵌套太深,否則數據的層層傳遞會讓你寫到吐,要麼吐,要麼上vuex或者redux。而且,你也不需要寫那麼深。為什麼呢?

前端這個往前靠產品、交互,往後貼數據和業務的職位。一般開完需求分析會,或者聽完產品經理給你的需求之後,你嗶哩吧啦寫完之後,產品經理或者領導腦門一熱,來個大調整,各種交互流程大變樣,你會發現,你能留下的大概也就只有表單的處理,和後端返回來的數據處理的。

就算是不大變樣,你業務組件最好也是用基礎組件去拼,我覺得這才是vue和react的魅力所在。

這樣不管是把數據返回到父級還是代碼閱讀性都不會複雜。

好了。我嗶嗶完了,以上僅是個人觀點,非官網,非權威。


Angular直接分路由就好,不只是組件復用,路由調用仍然堆棧,性能跟正常tab沒有區別,不會有destroy的過程


如果狀態都是獨立的,傾向路由。如果tab都是屬於同一個狀態的(比如選項對話框),傾向show/hide。如果邏輯差不多可以封裝一個組件。


我都不看你的詳細內容,看標題決定答一發。

Tab內容多,一定要分開,把它當菜單導航,導航到不同的路由,每個路由只做一件事情。

不要在一個頁面,糅合太多太重的功能。

不要在一個頁面,糅合太多太重的功能。

不要在一個頁面,糅合太多太重的功能。

我司的產品,是非常具有代表性的這類產品,設計追求高大上狂拽酷炫吊炸天的UI UX,恨不得所有的功能都在這一個頁面完成,追求極致的用戶體驗。

在其他頁面,對某個功能的長相又似曾相識。針對開發來說,邏輯儼然不同。微笑.jpg

如果按著設計的思維做,那麼一個頁面會很長很長。所以就是一個字,拆!

tab在設計的眼裡,就是一個切換,重要的是你如何把這個看似切換的切換,做成導航。這是你可以決定的。

還有,撕逼和拒絕,在絕大多數,能幫你解決設計和產品的矛盾。

加油!


tabs相當於容器,tab相當於容器的一個元素,元素內容寫成組件,組件間交互通過事件傳遞。


之前也遇到過這個問題,我那時候有六七個這樣帶tab的獨立的頁面,採用了路由的方式來組織代碼。

比較麻煩的就是當重新刷新頁面的時候你tab的index你打算怎麼根據當前的tab內容來設置呢? 我想如果在render的時候加判斷那太繁瑣了(需求,布局什麼的改起來更麻煩)

所以我採用隱藏顯示的方法,希望對你有幫助


vue用的動態組件


把選項卡對應的所有面板坐成一個個組件文件,然後不需要路由直接import,點擊哪個對應哪個組件,根據業務的不同設計 props, slot, $emit 來拓展組件介面,完成各部分業務功能, 在選項卡那一個vue文件里,將獲取到的數據,拆分放入props,通過$emit監聽相應自定義事件,增加slot等就可以了


肯定是拆啊。

拆的越細越好。拆的越通用越好。

另外返回是可以控制的。比如history的replace方法


我這邊是在pc端,vue,用的是路由,頂部導航條下方專門有個tab標籤欄,裡面監聽路由的變化,每次路由變化了(點擊菜單或者跳轉) 就新增一個標籤,然後標籤直接支持切換,刷新....簡直累到吐血....

路由數據和當前路由下的數據用vuex存著

切換路由的時候,前一組件銷毀前先緩存數據,切換到已經存在路由,在created的時候將緩存的數據載入.......

暫時沒有想到更好的方式了....

主要是不同的路由會導致router-view產生變化,所以才用這種

如果是全部寫在單一組件裡面,都不用框架啦,監聽body標籤的onhashchange事件控制顯示隱藏好了


建議寫在一起,分開路由寫的話,有些問題的,比如後退,就像樓上的樓上說的,那是個Bug的


如果使用路由的話,你的問題似乎在於使用路由返回的問題。但是每一個路由庫都可以返回到指定路由啊,而不是只能返回上一層路由(goBack())。比如從A-&>B,B是tab頁,然後B-&>tab1-&>tab2-&>tab3。按返回鍵是可以直接跳到A頁面的。如果用的hash,直接改變hash即可。不過路由庫對這些都是做了封裝的。在H5頁面,利用的是history的pushState。在非H5頁面,才是使用hash的。

BrowserRouter

navigation-prop


推薦閱讀:

為何需要Angularjs、backbonejs、reactjs?
如何評價數據流管理架構 Redux?
Vue和React的使用場景和深度有何不同?
Redux有哪些最佳實踐?
Weex 和 React Native 的根本區別在哪裡?

TAG:React | Vuejs | SPASingle-PageApp |