Material-ui 从 react-router 添加 Link 组件

Posted

技术标签:

【中文标题】Material-ui 从 react-router 添加 Link 组件【英文标题】:Material-ui adding Link component from react-router 【发布时间】:2016-10-17 01:03:52 【问题描述】:

我正在努力将<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 及其'&lt;Link/&gt; 组件连接在一起?

我试过像这样添加onChange 监听器:

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

但是我收到以下错误:

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

如果我尝试将 &lt;Tab/&gt; 组件包装到 &lt;Link/&gt; 组件中,我会收到 &lt;Tabs/&gt; 组件仅接受 &lt;Tab/&gt; 组件的错误。

这也不起作用(没有产生错误,但单击 Tab 不会将我带到路径):

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

如何使&lt;Link/&gt; 组件与&lt;Tabs&gt;&lt;AppBar&gt; 一起工作?如果这不可能,我可以使用 material-ui 库中的任何其他组件来形成正确的菜单。

【问题讨论】:

【参考方案1】:

这似乎对我有用

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>

【讨论】:

【参考方案2】:

使用href="" 选项,如下所示:

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

要消除点击时的涟漪效应,请使用选项disableRipple

【讨论】:

【参考方案3】:

如果你使用 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/

【讨论】:

【参考方案4】:

对于希望使用 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)
/>

【讨论】:

【参考方案5】:

这是 React 的另一种实现,包括 hooks、Material-UI 和选项卡、React Router 和 Link 和 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>
  )

【讨论】:

看起来不错。有一个问题 - 如何设置选项卡的初始状态?您使用“0”,所以它是“/home”的路径,但是如果用户将以“/dev”路径开头怎么办?【参考方案6】:

检查此链接,我实施了解决方案并为我工作

Composition in material UI

【讨论】:

【参考方案7】:

这可以使用 material-ui 中的 &lt;Link /&gt; 解决,而不是直接使用 react-router 中的 &lt;Link /&gt;&lt;NavLink /&gt;。可以在此处的文档中找到相同的示例。

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

另外&lt;Button /&gt;标签有一个组件prop来实现这个

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

可以在这里找到关于此的广泛讨论

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

【讨论】:

【参考方案8】:

由于我们使用的是 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"  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))

【讨论】:

【参考方案9】:

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>
    );
  

【讨论】:

【参考方案10】:

你可以试试这个简单的方法

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

【讨论】:

这对我有用。但是,无论如何要删除“蓝色下划线”链接样式? 这应该是最新材料-ui 4+的正确答案 2020 年仍然是一个更简单的解决方案 为我抛出 Error: React.Children.only expected to receive a single React element child. 没有抛出错误,但它对我不起作用。运行 4.11 核心用户界面【参考方案11】:

对于带有 Typescript 的 Material UI 1.0:请参阅下面的 @ogglas 的 this post。

对于带有纯 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 属性,参见ButtonBase。这个想法是允许您控制组件作为其包装器/根元素呈现的内容。 Tab 也有这个功能,虽然在写这个答案的时候这个道具没有明确记录,但是因为Tab 继承自ButtonBase,它的所有道具都继承了(并且文档确实覆盖这个)。

ButtonBase 的另一个特点是所有额外的道具,ButtonBase 或继承的组件没有使用,都分布在指定的component 上。我们已经使用这种行为来发送Link 使用的to 属性,方法是将其交给Tab 控制。您可以以相同的方式发送任何其他道具。请注意,ButtonBaseTab 都明确记录了这一点。

感谢 @josh-l 要求添加此内容。

【讨论】:

您能否更具体地了解为“组件”和“到”API 传递的参数是什么?我正在查看材料 UI v1 api 选项卡文档,但它没有显示其中任何一个? material-ui-1dab0.firebaseapp.com/api/tab 对于大多数 mui 容器组件,您可以发送一个 component 属性,使其使用不同的 React 组件而不是默认组件。在这个例子中,我们让 Tab 渲染反应路由器的 Link 控件。在这种模式下,mui 组件会将任何额外的道具传递给该组件。由于 Link 需要 to 道具,我在 Tab 中传递了它。你会发现这种行为记录在 mui 的某个地方。将尽快更新我的答案。 非常感谢!得到它的工作!但是,是的,也许用更多细节更新答案可以帮助其他人。无论哪种方式都赞成!【参考方案12】:

您现在可以这样做:

<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>

【讨论】:

甜蜜。但我注意到文档没有这样说。是否所有元素都支持containerElement 我尝试过的那些(他们拥有它是有道理的)他们确实拥有它 在撰写本文时,这种技术似乎不适用于material-ui@next【参考方案13】:

所以我对这个解决方案的解决方案非常可靠,尽管它可能比您想要做的解决方案更手动。

我一直在使用的策略是实际上甚至不使用链接组件。相反,您将使用 Tabs onChange 属性作为回调,该回调可以响应 Tab 点击,并使用 Parent 上的 Props 手动跟踪位置。

您可以从 react-router 导入一个名为 History 的实用程序,该实用程序允许您手动推送位置。在使用 React-Router 时,您的组件树将可以访问 Location 属性,该属性的路径名键带有您当前位置的字符串。

我们将手动将此字符串解析为构成您当前 URL 的组件,然后使用 Switch 语句来决定当前选择了哪个选项卡以及单击选项卡时链接到的位置。 (这使您可以对导航进行相当程度的控制)

(例如 ['', 'latest'])

这是您的组件在集成此解决方案后的样子的模型。

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>
        );
      
);

【讨论】:

这对我来说似乎没问题。我会尝试将其添加到我的代码中。

以上是关于Material-ui 从 react-router 添加 Link 组件的主要内容,如果未能解决你的问题,请参考以下文章

React-Router 和 Material-UI:根据路由应用自定义主题

React、Redux、React-Router - 卡在安装 material-ui

在 Typescript 中将 React-Router Link 作为组件添加到 Material-UI?

如何为每个 Material-UI TableRow 添加 <Link> react-router?

如何在 React-Router 中的路由之间共享状态?

使用 React 和 Material-UI 的开源项目?