簡體   English   中英

Material-ui 從 react-router 添加 Link 組件

[英]Material-ui adding Link component from react-router

我正在努力將<Link/>組件添加到我的 material-ui AppBar

這是我的導航課:

class Navigation extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    var styles = {
      appBar: {
        flexWrap: 'wrap'
      },
      tabs: {
        width: '100%'
      }
    }

    return (
      <AppBar showMenuIconButton={false} style={styles.appBar}>
        <Tabs style={styles.tabs}>
          <Tab label='Most popular ideas'/>
          <Tab label='Latest ideas' />
          <Tab label='My ideas' />
        </Tabs>
      </AppBar>
    )
  }
}

看起來沒問題: 導航欄

標簽是可點擊的,有流暢的動畫,這很酷。 但是我如何將它們與react-router及其' <Link/>組件連接起來?

我試過像這樣添加onChange偵聽器:

<Tab
  label='My ideas'
  onChange={<Link to='/myPath'></Link>}
/>

但是我收到以下錯誤:

Uncaught Invariant Violation: Expected onChange listener to be a function, instead got type object

如果我嘗試將<Tab/>組件包裝到<Link/>組件中,我會收到<Tabs/>組件僅接受<Tab/>組件的錯誤。

這也不起作用(沒有產生錯誤,但單擊 Tab 不會將我帶到路徑):

<Tab label='Most popular ideas'>
  <Link to='/popular'/>
</Tab>

如何讓<Link/>組件與<Tabs><AppBar>一起工作? 如果這是不可能的,我可以使用material-ui庫中的任何其他組件來形成適當的菜單。

對於帶有 Typescript 的 Material UI 1.0:請參閱下面@ogglas 的這篇文章

對於帶有純 JS 的 Material-UI 1.0:

<Tabs value={value} onChange={this.handleChange}>
  {
    this.props.tabs.map(
      ({label, path})=><Tab key={label} 
                            label={label} 
                            className={classes.tabLink} 
                            component={Link} 
                            to={path} />
    )
  }
</Tabs>

classes.tabLink定義為:

tabLink : {
    display:"flex",
    alignItems:"center",
    justifyContent:"center"
}

這是如何工作的?

所有繼承自ButtonBase的 mui 1.0 組件,都支持component prop,請參閱ButtonBase 這個想法是允許您控制組件呈現為它的包裝器/根元素的內容。 Tab也具有此功能,盡管在撰寫此答案時此道具並未明確記錄,但由於Tab繼承自ButtonBase ,因此它的所有道具都會ButtonBase (並且文檔確實涵蓋了這一點)。

ButtonBase另一個特點是所有額外的 props 都沒有被ButtonBase或繼承的組件使用,都分布在指定的component 我們已經使用此行為通過將Link使用的to屬性提供給Tab控件來發送它。 您可以以相同的方式發送任何其他道具。 請注意,這對於ButtonBaseTab都有明確記錄。

感謝 @josh-l 要求添加此內容。

您現在可以這樣做:

<Tabs onChange={this.changeTab} value={value}>
   <Tab value={0} label="first" containerElement={<Link to="/first"/>} />
   <Tab value={1} label="second" containerElement={<Link to="/second"/>}/>
   <Tab value={2} label="third" containerElement={<Link to="/third"/>} />
 </Tabs>

你可以試試這個簡單的方法

 <Tab label='Most popular ideas'  to='/myPath' component={Link} />

由於我們使用的是 TypeScript,因此我無法使用 @hazardous 解決方案。 這就是我們為material-ui v1.0.0-beta.16react-router 4.2.0實現路由的方式。 我們之所以要拆分this.props.history.location.pathname是因為例如我們需要訪問/renewals/123 如果我們不這樣做,我們將收到以下警告,並且沒有選項卡顯示為活動: Warning: Material-UI: the value provided '/renewals/123' is invalid

帶導入的完整代碼:

import * as React from "react";
import * as ReactDOM from "react-dom";
import * as ReactRouter from "react-router";
import * as PropTypes from "prop-types";
import { Switch, Route, Redirect, Link  } from "react-router-dom";
import { Cases } from './../Cases';
import { SidePane } from './../SidePane';
import { withStyles, WithStyles } from 'material-ui/styles';
import Paper from 'material-ui/Paper';
import Tabs, { Tab } from 'material-ui/Tabs';
import { withRouter } from "react-router-dom";
import Badge from 'material-ui/Badge';
import Grid from 'material-ui/Grid';
import { Theme } from 'material-ui/styles';
import SimpleLineIcons from '../../Shared/SimpleLineIcons'

interface IState {
    userName: string;
}

interface IProps {
    history?: any
}

const styles = (theme: Theme) => ({
    root: theme.typography.display1,
    badge: {
        right: '-28px',
        color: theme.palette.common.white,
    },
    imageStyle:{
        float: 'left',
        height: '40px',
        paddingTop: '10px'
    },
    myAccount: {
        float: 'right'
    },
    topMenuAccount: {
        marginLeft: '0.5em',
        cursor: 'pointer'
    }
});

type WithStyleProps = 'root' | 'badge' | 'imageStyle' | 'myAccount' | 'topMenuAccount';

class Menu extends React.Component<IProps & WithStyles<WithStyleProps>, IState> {
    constructor(props: IProps & WithStyles<WithStyleProps>) {
        super(props);
        this.state = {
            userName: localStorage.userName ? 'userName ' + localStorage.userName : ""
        }
    }
    componentDidMount() {
        this.setState({ userName: localStorage.userName ? localStorage.userName : "" })
    }
    logout(event: any) {
        localStorage.removeItem('token');
        window.location.href = "/"
    }

    handleChange = (event: any, value: any) => {
        this.props.history.push(value);
    };

    render() {
        const classes = this.props.classes;
        let route = '/' + this.props.history.location.pathname.split('/')[1];
        return (
            <div>
                <Grid container spacing={24}>
                    <Grid item xs={12} className={classes.root}>
                        <img src="/Features/Client/Menu/logo.png" alt="Logo" className={classes.imageStyle} />
                        <div className={this.props.classes.myAccount}>
                        <span><span className={this.props.classes.topMenuAccount}>MY ACCOUNT</span><span className={classes.topMenuAccount}><SimpleLineIcons iconName={'user'} />&#x25BE;</span></span>
                            <span onClick={this.logout} className={classes.topMenuAccount}><SimpleLineIcons iconName={'logout'} /></span>
                        </div>
                    </Grid>
                    <Grid item xs={12} >
                        <div className="route-list">
                            <Tabs
                                value={route}
                                onChange={this.handleChange}
                                indicatorColor="primary"
                                textColor="primary"
                            >
                                <Tab label="Overview" value="/" />
                                <Tab label={<Badge classes={{ badge: classes.badge }} badgeContent={this.props.caseRenewalCount} color="primary">
                                    Renewals
                                   </Badge>} value="/renewals" />
                            </Tabs>
                        </div>
                    </Grid>
                </Grid>
            </div>
        );
    }
}
export default withStyles(styles)(withRouter(Menu))

這是通過使用求解<Link />從材料的UI而不是直接使用<Link /><NavLink />從反應路由器。 可以在此處的文檔中找到相同的示例。

https://material-ui.com/components/links/

此外<Button />標簽有一個組件道具來實現這一點

<Button color="inherit" component={Link} to={"/logout"}>Logout</Button>

可以在此處找到對此的廣泛討論

https://github.com/mui-org/material-ui/issues/850

路由器驅動選項卡的 TypeScript 實現。

對於那些尋找 TypeScript 實現的人。 易於配置。 tabs配置驅動。

interface ITabsPageProps {
  match: match<{page: string}>;
  history: History;
}

const tabs = [{
  label: 'Fist Tab',
  link: 'fist-tab',
  component: <FirstTabContent/>
}, {
  label: 'Second Tab',
  link: 'second-tab',
  component: <SecondTabContent/>
}, {
  label: 'Third Tab',
  link: 'third-tab',
  component: <ThirdTabContent/>
}];

export class TabsPage extends React.Component<ITabsPageProps> {
  handleChange(tabLink: string) {
    this.props.history.push(`/tabs-page/${tabLink}`);
  }

  render() {
    const currentTab = this.props.match.params.page;
    const selectedTab = tabs.find(tab => currentTab === tab.link);

    return (
      <Fragment>
        <Tabs
          value={currentTab}
          onChange={(event, value) => this.handleChange(value)}
        >
          {tabs.map(tab => (
            <Tab
              key={tab.link}
              value={tab.link}
              label={tab.label}
            />
          ))}
        </Tabs>

        {selectedTab && selectedTab.component}
      </Fragment>
    );
  }
}

所以我對此解決方案的解決方法非常可靠,盡管它可能比您想要做的更手動的解決方案。

我一直在使用的策略是實際上甚至不使用鏈接組件。 相反,您將利用 Tabs onChange 屬性作為可以響應 Tab 單擊的回調,並使用 Parent 上的 Props 手動跟蹤位置。

您可以從 react-router 導入一個名為 History 的實用程序,它允許您手動推送位置。 在使用 React-Router 時,您的組件樹將可以訪問 Location 屬性,該屬性具有帶有當前位置字符串的路徑名鍵。

我們將手動將此字符串解析為構成當前 URL 的組件,然后使用 Switch 語句來決定當前選擇哪個選項卡以及單擊選項卡時鏈接到的位置。 (這使您可以對導航進行相當多的控制)

(例如 ['', '最新'] )

這是集成此解決方案后您的組件可能是什么樣子的模型。

import React from 'react';
import {History} from 'react-router';

function parseLocation(location) {
    if (String(location)) {
        var locationArray = location.split('/');
        return locationArray;
    } else {
        return false;
    }
};
function filterPath(path) {
    let locationArray = parseLocation(path);
    return locationArray[locationArray.length - 1];
};
var Navigation = React.createClass({
      mixins: [History],
      getPage() {
        if (this.props.location.pathname) {
          let pathname = this.props.location.pathname;
          let pageName = filterPath(pathname);
          return pageName;
        } else {
          return false;
        } 
      },
      decideContent() {
        let page = this.getPage();
        let content;
        switch(page) {
           case 'popular':
              content = 0;
           case 'latest':
              content = 1;
           case 'myideas':
              content = 2;
           default:
              content = 0;
        }
        return content;
      },
      handleTabChange(value) {
        let location = false;
        switch (value) {
           case 0:
             location = 'popular';
             break;
           case 1:
             location = 'latest';
             break;
           case 2:
             location = 'myideas';
             break;
        }
        if (location && location !== this.getPage()) {
          this.history.pushState(null, '/'+location);
        }
      },
      render() {
         var styles = {
          appBar: {
           flexWrap: 'wrap'
          },
          tabs: {
           width: '100%'
          }
         };
         let content = this.decideContent();
         let tabs = <Tabs
                  onChange={this.handleTabChange}
                  value={content}
                >
                  <Tab label="Most Popular Ideas" value={0}  />
                  <Tab label="Latest Ideas" value={1}  />
                  <Tab label="My Ideas" value={2}  />
                </Tabs>;
        return (
         <AppBar showMenuIconButton={false} style={styles.appBar}>
           {tabs}
         </AppBar>
        );
      }
});

這是帶有鈎子的 React 的另一個實現、帶有選項卡的 Material-UI、帶有鏈接的 React Router 和 TypeScript。

import * as React from "react";
import { BrowserRouter as Router, Route, Redirect, Switch, Link, LinkProps } from 'react-router-dom';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import { default as Tab, TabProps } from '@material-ui/core/Tab';

import Home from './Home';
import ProductManagement from './ProductManagement';
import Development from './Development';
import HomeIcon from '@material-ui/icons/Home';
import CodeIcon from '@material-ui/icons/Code';
import TimelineIcon from '@material-ui/icons/Timeline';

const LinkTab: React.ComponentType<TabProps & LinkProps> = Tab as React.ComponentType<TabProps & LinkProps>;

function NavBar() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
    setValue(newValue);
  };
  return (
    <div >
      <AppBar position="static" >
        <Tabs value={value} onChange={handleChange} centered>
          <LinkTab label='Home' icon={ <HomeIcon />} component={Link} to="/" />
          <LinkTab label='Development' icon={<CodeIcon />} component={Link} to="/dev" />
          <LinkTab label='Product Management' icon={<TimelineIcon />} component={Link} to="/pm" />
        </Tabs>
      </AppBar>
    </div>
  )
};

export default function App() {
  return (
    <Router>
      <div>
        <NavBar />
        <Switch>
          <Route exact path="/" component={ Home } />
          <Route exact path="/dev" component={ Development } />
          <Route exact path="/pm" component={ ProductManagement } />
          <Redirect from="/" to="/" />
        </Switch>
      </div>
    </Router>
  )
}

檢查此鏈接,我實施了解決方案並為我工作

Material UI 中的合成

如果您使用 NextJs,您可以這樣做,並創建您自己的組件。

*我沒有用'a'標簽包裹標簽,因為它是自動添加的

const WrapTab = (props) => {
const { href } = props
return (
<Link href={href} style={{ width: "100%" }}>
  <Tab {...props} />
</Link>
)}

然后這是你的回報

    <Tabs
      value={value}
      indicatorColor="primary"
      textColor="primary"
      onChange={handleChange}
      variant="fullWidth"
    >
        <WrapTab href="/testPage/?tab=0" icon={<MenuIcon />} />
        <WrapTab href="/testPage/?tab=1" icon={<StampIcon2 />} />
        <WrapTab href="/testPage/?tab=2" icon={<ShopIcon />} />
        <WrapTab href="/testPage/?tab=3" icon={<PenIcon />} />
        <WrapTab href="/testPage/?tab=4" icon={<ProfileIcon />} />
    </Tabs>

鏈接到 material-ui 文檔: https : //material-ui.com/guides/composition/

對於希望使用 Next.js 鏈接包裝 Material-ui 鏈接的任何人

import Link from "next/link"
import MuiLink from "@material-ui/core/Link"

const CustomNextLink = ({href, alt}) => ({children, ...rest}) => (
<Link href={href} alt={alt}>
  <MuiLink {...rest}>
    {children}
  </MuiLink>
</Link>)

然后將其傳遞給您 Tab 組件

<Tab 
 key={...}
 label={title} 
 icon={icon} 
 component={CustomNextLink({href: to, alt: title})}
 style={...}
 className={...}
 classes={{selected: ...}}
 {...a11yProps(index)}
/>

使用href=""選項,如下所示:

<Tab
 href="/en/getting-started"
 label="Contact US"
 style={{ color: "white", textDecoration: "none" }}
/>

要消除點擊時的漣漪效果,請使用選項disableRipple

這似乎對我有用

import { Link as RouterLink } from 'react-router-dom';
import Link from '@mui/material/Link';

<Link to={menuItem.url} component={RouterLink} aria-current="page">
{menuItem.label}
</Link>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM