單頁面應用,TAB選項卡太多,頁面邏輯是糅合在一起還是重新分路由?
01-05
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。
瀉藥~我覺得你已經思考的很全面啦。我個人推薦還是要使用路由,好處有以下幾個:
- 代碼更利於維護和開發。你如果全部都寫在一個組件裡面的話,一方面,這個組件會特別大,設計的頁面邏輯會很複雜,這對於你後期開發和維護特別不方便。比如你要修改或者新增一個功能,就會變得特別麻煩。而根據功能來劃分為不同組件的話,那麼開發和維護起來就會更加方便。
- 為將來做好準備。或許目前來說,你各個tab的邏輯都比較簡單,不過到後面可能會越來越複雜,如果你都寫在一個組件裡面的話,後面每個tab變得更加複雜就難以維護,之後也肯定是要抽出來作為單獨的組件實現的,所以為什麼不現在就做呢
- 頁面參數問題和路由問題。有些參數的傳遞的話,其實用路由表示會更加合適一點,而且你也考慮到了,如果你不是按路由的話,你切換到不同的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 |