一步一步構(gòu)件react后臺(tái)系統(tǒng) 9 之 tab切換

一步一步構(gòu)件react后臺(tái)系統(tǒng) 9 之 tab切換

先看看效果

可以看到, 在面包屑下面多了個(gè)tab欄

tabs.png

思路

之前看到別人的后臺(tái)系統(tǒng), 有一個(gè)tab, 來(lái)記錄所有跳轉(zhuǎn)過(guò)的頁(yè)面, 我就在想, 該怎么做?

然后看了一下別人的代碼。
原理是這樣的。

做一個(gè)tab, 然后點(diǎn)擊導(dǎo)航的時(shí)候, 我們的props就會(huì)發(fā)生改變, componentWillReceiveProps 方法就會(huì)被調(diào)用, 然后就在里面監(jiān)聽(tīng), 判斷當(dāng)前頁(yè)面在不在tab面板里面, 如果不在,則拿到當(dāng)前頁(yè)面路徑到路由表里面進(jìn)行尋找, 找到對(duì)應(yīng)的對(duì)象并提取出來(lái), 然后加入到所有tabs數(shù)組里面, 并在組件里面進(jìn)行渲染。

在路由配置里面, 有component屬性的, 里面保存著組件, 直接把這個(gè)渲染在tab的面板里面就可以了。

開(kāi)始動(dòng)手

  1. 先貼出components/tabs.js完整代碼
import React, { Component } from 'react';
import { Tabs  } from 'antd';
import { filterData, deleObj, deepFlatten, removeArrItem } from '@/utils/index.js'
import { main as mainConfig } from '@/router/index'
import {connect} from "react-redux";
import {withRouter} from "react-router-dom";
import {crumbsMap} from "@/reducer/connect";

const TabPane  = Tabs.TabPane;
class MyTabs extends Component {
    constructor(props) {
        super(props)
        this.state = {
            currentPage: {},
            openPages: [],
            routerConfig: deepFlatten(mainConfig),
            mode: 'top',
        }
    }
    handleModeChange = () => {
        let mode = 'left';
        switch (this.state.mode) {
            case 'top':
                mode = 'left';
                break;
            case 'left' :
                mode = 'right';
                break;
            case 'right':
                    mode = 'bottom';
                    break;
            default:
                mode = 'top'
        }
        this.setState({ mode });
    }
    onEdit = (targetKey, action) => {
        if (action === 'remove') {
            this.removeTabs(targetKey)
        }
    }

    removeTabs = (targetKey) => {
        let openPages = removeArrItem(this.state.openPages, function(item){
            return item.path === targetKey
        })
        this.setState({
            openPages
        })
    }
    onTabClick = (activeKey) => {

    }
    componentDidMount () {
    }
    // props 更新時(shí)調(diào)用
    componentWillReceiveProps (nextProps) {
        this.addRouteToAllTabPans(nextProps)
    }
    addRouteToAllTabPans (props) {
        if (props.location.pathname !== this.state.currentPage.path && props.location.pathname !=='/index') {
            let {openPages} = this.state
            let isHasRoute = openPages.some(item =>item.path === props.location.pathname)
            if (!isHasRoute) {
                let currentRoute = this.getCurrentRoute(props.location.pathname)
                if (currentRoute) {
                    openPages.push(currentRoute)
                    this.setState({
                        openPages,
                        currentPage: currentRoute
                    })
                }
            }
        }
    }
    getCurrentRoute (path) {
        return this.state.routerConfig.filter(item => {
            if (item.path === path) return item
        })[0]
    }
    render() {
        let { location, getRouterConfig, routerConfig, routes } = this.props
        return (
            <div>
                <Tabs
                    hideAdd
                    defaultActiveKey={this.state.currentPage.path}
                    type="editable-card"
                    animated={true}
                    onEdit={this.onEdit}
                    onTabClick={this.onTabClick}
                    tabBarGutter={5}
                    hideAdd={true}
                    tabPosition={this.state.mode}
                    tabBarExtraContent={<span onClick={this.handleModeChange}>{this.state.mode}</span>}
                >
                    {this.state.openPages.map(page => {
                         return <TabPane forceRender tab={page.name} closable={page.closable} key={page.path}>
                            <page.component routes={routes}></page.component>
                        </TabPane>
                    })}
                </Tabs>
            </div>
        );
    }
}

export default connect(crumbsMap.mapStateToProps, crumbsMap.mapDispatchToProps)(withRouter(MyTabs))
  1. 查看細(xì)節(jié)
  • props 改變的時(shí)候, 會(huì)觸發(fā)
    // props 更新時(shí)調(diào)用
    componentWillReceiveProps (nextProps) {
        this.addRouteToAllTabPans(nextProps)
    }
  • 判斷 點(diǎn)擊的是當(dāng)前tab頁(yè)面, 則不做反應(yīng), 如果點(diǎn)擊的是index頁(yè)面, 同樣不做反應(yīng)。
  • 使用some判斷當(dāng)前路徑是否已經(jīng)存在 openPages(tabs數(shù)組)里面,
  • 如果不存在, 從路由表里面提取出改路徑的路由對(duì)象, 并添加到openPages里面。 同時(shí)修改 currentPage當(dāng)前路由對(duì)象
    addRouteToAllTabPans (props) {
        if (props.location.pathname !== this.state.currentPage.path && props.location.pathname !=='/index') {
            let {openPages} = this.state
            let isHasRoute = openPages.some(item =>item.path === props.location.pathname)
            if (!isHasRoute) {
                let currentRoute = this.getCurrentRoute(props.location.pathname)
                if (currentRoute) {
                    openPages.push(currentRoute)
                    this.setState({
                        openPages,
                        currentPage: currentRoute
                    })
                }
            }
        }
    }
  • 在這里, 就是循環(huán)渲染了。
  • <page.component routes={routes}></page.component> 這個(gè)就是路由對(duì)象的組件, 直接在TabPane里面渲染出來(lái)。
 {this.state.openPages.map(page => {
                         return <TabPane forceRender tab={page.name} closable={page.closable} key={page.path}>
                            <page.component routes={routes}></page.component>
                        </TabPane>
                    })}

大概完成思路就是這樣。

完善

這里我把切換功能放在了header上面。 可以根據(jù)自己的選擇需要或不需要tab

  1. 修改components/header.js

因?yàn)閙enu的點(diǎn)擊事件放在了menu標(biāo)簽上面, 而點(diǎn)擊的時(shí)候, 可以拿到menuItem中的key
因此, 我們進(jìn)行判斷, 如果是tabs, 我們就觸發(fā)事件

    handleClick = (e) => {
        if (e.key === 'tabs') {
            this.changeTabs()
        } else {
            this.setState({
                current: e.key,
            });
        }
    }

切換 tabs狀態(tài)

    changeTabs = (obj) => {
        this.props.toggleTabs({
                tabs: !this.props.headerData.tabs
            })
    }

我們的tabs需要在header組件與 index組件里面使用, 因此不能單純的只是放在當(dāng)前頁(yè)面, 需要用redux進(jìn)行管理。

  1. 修改 reducer/connect

修改 mapLogout,

export const mapLogout = {
    mapStateToProps: (state) => {
        return { headerData: { }, ...state.slidecollapsed}
    },
    mapDispatchToProps: (dispatch) => {
        return {onSlidecollapsed: () => dispatch(action_slidecollapsed), getRouterConfig: () => {
                return dispatch(routerConfig)
            }, toggleSlide: () => {
                dispatch({type: action_slidecollapsed.type})
            },
            onLogout: (data) => {
                return dispatch(fetchPosts('/logout', action_slidecollapsed.type, 'logoutData', data))
            },
            toggleTabs: (data) => {  // 添加切換tab的函數(shù)
                console.log(data);
                dispatch(receive(action_slidecollapsed.type,  'headerData', {
                    ...data
                }))
            }
        }
    }
}

修改rudexs.js
以前的數(shù)據(jù), 是用redux里面控制, 現(xiàn)在我們要改成傳參的形式。

const slidecollapsedFuc = (state = { slidecollapsed: false }, action) => {
    switch (action.type) {
        case SLIDECOLLAPSED:
            return Object.assign({}, state, action)
        default:
            return state
    }
}

components/header
添加 changeTabs 方法 。 render 里面引入 headerData 數(shù)據(jù), 然后添加MenuItem標(biāo)簽

    changeTabs = (obj) => {
        this.props.toggleTabs({
                tabs: !this.props.headerData.tabs
            })
    }



            let { slidecollapsed, headerData, toggleSlide, toggleTabs } = this.props
            let { tabs } = headerData

                   <Menu.Item key="tabs">
                                            <Icon type="notification" /> {tabs ? '隱藏tabs' : '顯示tabs'}
                                    </Menu.Item>

這樣, 點(diǎn)擊頭部的'隱藏tabs'按鈕的時(shí)候,就會(huì)進(jìn)行切換,文字。

  • 修改views/index/index
    添加引入
    添加方法和屬性
    添加this.props.headerData
    判斷tabs 進(jìn)行顯示隱藏。
import MyTabs  from '@/components/tabs.js'
import MySlider  from '@/components/slider'
import { connect  } from 'react-redux'
import { mapIndex } from '@/reducer/connect'

state = {
        currentPage: '',
        openPages: []
    }
    onEdit = (targetKey, action) => {
        this[action](targetKey);
    }
    onTabClick = (activeKey) => {
        // if (activeKey !== this.state.currentPage && activeKey === 'home') {
        //     this.props.history.push('/app/home');
        //     return;
        // }
        // if (activeKey !== this.state.currentPage) {
        //     this.props.history.push(MenuToRouter[activeKey]);
        // }
    }

            let { routes, headerData } = this.props
            console.log(this.props)
            let { tabs } = headerData


                                { tabs &&
                                <MyTabs routes={routes}></MyTabs>
                                }
                                {!tabs &&
                                <MyMain routes={routes}></MyMain>
                                }

                                export default connect(mapIndex.mapStateToProps)(Index);
  • redcer/connect
    添加 mapIndex方法。
export const mapIndex = {
    mapStateToProps: (state) => {
        return { headerData: { }, ...state.slidecollapsed}
    },
    mapDispatchToProps: (dispatch) => {
        return {
            toggleTabs: (data) => {
                console.log(data);
                dispatch(receive(action_slidecollapsed.type,  'headerData', {
                    ...data
                }))
            }
        }
    }
}
  • utils/index 內(nèi)添加方法

先從面包屑組件里面提取 deepFlatten 到公共方法, 然后 添加removeArrItem


export const deepFlatten = arr => [].concat(...arr.map(v => Array.isArray(v) ? deepFlatten(v) : ( typeof v === 'object' ? (Array.isArray(v.routes) ? deepFlatten(v.routes.concat(deleObj(v, 'routes'))) : v) : v )));

// 刪除數(shù)組某項(xiàng)元素

export const removeArrItem = (arr, validFunx) => {
    arr.splice(arr.findIndex(item => validFunx(item)), 1)
    return arr
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,009評(píng)論 2 59
  • 作者 王守禮 這天傍晚,在路旁一個(gè)小飯店里簡(jiǎn)單吃了點(diǎn)東西...
    好故事創(chuàng)作室閱讀 673評(píng)論 0 2
  • 親愛(ài)的社長(zhǎng)同志: 我剛剛做出了一個(gè)艱難的決定:我將退出北大音樂(lè)劇社。 原因也是很簡(jiǎn)單的,我在學(xué)習(xí)上,在生活上都面臨...
    Leader_Three閱讀 287評(píng)論 0 0
  • 姜,根莖供藥用,鮮品或干品可作烹調(diào)配料或制成醬菜、糖姜。很多人不喜歡姜,是因?yàn)樗兄还衫眲?,然而,這股辣勁和它獨(dú)...
    YY不輸閱讀 749評(píng)論 0 3

友情鏈接更多精彩內(nèi)容