一步一步构件react后台系统 9 之 tab切换

一步一步构件react后台系统 9 之 tab切换

先看看效果

可以看到, 在面包屑下面多了个tab栏

tabs.png

思路

之前看到别人的后台系统, 有一个tab, 来记录所有跳转过的页面, 我就在想, 该怎么做?

然后看了一下别人的代码。
原理是这样的。

做一个tab, 然后点击导航的时候, 我们的props就会发生改变, componentWillReceiveProps 方法就会被调用, 然后就在里面监听, 判断当前页面在不在tab面板里面, 如果不在,则拿到当前页面路径到路由表里面进行寻找, 找到对应的对象并提取出来, 然后加入到所有tabs数组里面, 并在组件里面进行渲染。

在路由配置里面, 有component属性的, 里面保存着组件, 直接把这个渲染在tab的面板里面就可以了。

开始动手

  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 更新时调用
    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. 查看细节
  • props 改变的时候, 会触发
    // props 更新时调用
    componentWillReceiveProps (nextProps) {
        this.addRouteToAllTabPans(nextProps)
    }
  • 判断 点击的是当前tab页面, 则不做反应, 如果点击的是index页面, 同样不做反应。
  • 使用some判断当前路径是否已经存在 openPages(tabs数组)里面,
  • 如果不存在, 从路由表里面提取出改路径的路由对象, 并添加到openPages里面。 同时修改 currentPage当前路由对象
    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
                    })
                }
            }
        }
    }
  • 在这里, 就是循环渲染了。
  • <page.component routes={routes}></page.component> 这个就是路由对象的组件, 直接在TabPane里面渲染出来。
 {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上面。 可以根据自己的选择需要或不需要tab

  1. 修改components/header.js

因为menu的点击事件放在了menu标签上面, 而点击的时候, 可以拿到menuItem中的key
因此, 我们进行判断, 如果是tabs, 我们就触发事件

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

切换 tabs状态

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

我们的tabs需要在header组件与 index组件里面使用, 因此不能单纯的只是放在当前页面, 需要用redux进行管理。

  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的函数
                console.log(data);
                dispatch(receive(action_slidecollapsed.type,  'headerData', {
                    ...data
                }))
            }
        }
    }
}

修改rudexs.js
以前的数据, 是用redux里面控制, 现在我们要改成传参的形式。

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

components/header
添加 changeTabs 方法 。 render 里面引入 headerData 数据, 然后添加MenuItem标签

    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>

这样, 点击头部的'隐藏tabs'按钮的时候,就会进行切换,文字。

  • 修改views/index/index
    添加引入
    添加方法和属性
    添加this.props.headerData
    判断tabs 进行显示隐藏。
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 内添加方法

先从面包屑组件里面提取 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 )));

// 删除数组某项元素

export const removeArrItem = (arr, validFunx) => {
    arr.splice(arr.findIndex(item => validFunx(item)), 1)
    return arr
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,709评论 2 59
  • 作者 王守礼 这天傍晚,在路旁一个小饭店里简单吃了点东西...
    好故事创作室阅读 626评论 0 2
  • 亲爱的社长同志: 我刚刚做出了一个艰难的决定:我将退出北大音乐剧社。 原因也是很简单的,我在学习上,在生活上都面临...
    Leader_Three阅读 261评论 0 0
  • 姜,根茎供药用,鲜品或干品可作烹调配料或制成酱菜、糖姜。很多人不喜欢姜,是因为它有着一股辣劲,然而,这股辣劲和它独...
    YY不输阅读 655评论 0 3