保持 Material UI 选项卡和 React Router 同步
Keeping Material UI tabs and React Router in sync
有没有一种简单的方法可以使 Material UI 选项卡和 React 路由器保持同步?
基本上,我想在用户单击选项卡 [1] 时更改 URL 并且当用户导航到具有非选项卡的不同页面时选项卡应自动更改 link 或按钮,当然还有直接访问 [2] 和页面刷新。
此外,如果 React 路由器的 非精确 功能也很好,那么 /foo
选项卡应该对 /foo
和 /foo
都有效/foo/bar/1
.
[1] 其他 SO 答案建议直接使用历史 api,这是 react-router 的好习惯吗?
[2] 我不确定它叫什么,我的意思是当用户直接加载 /foo
而不是加载 /
然后导航到 /foo
时选项卡或 link
编辑:
我创建了一个包装器组件来完成这项工作,但有一些问题:
class CustomTabs extends React.PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
setActiveTab(id) {
this.setState({
activeTab: id
});
return null;
}
render() {
return (
<div>
{this.props.children.map((tab,index) => {
return (
<Route
key={index}
path={tab.props.path||"/"}
exact={tab.props.exact||false}
render={() => this.setActiveTab(index)}
/>
);
})}
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
style={{paddingLeft: '10px', paddingRight: '10px', height: '64px'}}
onActive={() => {
this.props.history.push(tab.props.path||"/")
}}
/>
);
})}
</Tabs>
</div>
);
}
}
我是这样使用它的:
<AppBar title="Title" showMenuIconButton={false}>
<CustomTabs history={this.props.history}>
<Tab label="Home" path="/" exact/>
<Tab label="Foo" path="/foo"/>
<Tab label="Bar" path="/bar"/>
</CustomTabs>
</AppBar>
但是:
- 我在我的控制台中收到此警告:
Warning: setState(...): Cannot update during an existing state transition (such as within render
or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount
.
我认为这是因为我在调用 render()
后立即设置状态 - 因为 Route.render
,但我不知道如何解决这个问题。
编辑#2
我终于解决了所有问题,但方式有点老套。
class CustomTabsImpl extends PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
componentWillMount() {
this.state.activeTab = this.pathToTab(); // eslint-disable-line react/no-direct-mutation-state
}
componentWillUpdate() {
setTimeout(() => {
let newTab = this.pathToTab();
this.setState({
activeTab: newTab
});
}, 1);
}
pathToTab() {
let newTab = 0;
this.props.children.forEach((tab,index) => {
let match = matchPath(this.props.location.pathname, {
path: tab.props.path || "/",
exact: tab.props.exact || false
});
if(match) {
newTab = index;
}
});
return newTab;
}
changeHandler(id, event, tab) {
this.props.history.push(tab.props['data-path'] || "/");
this.setState({
activeTab: id
});
}
render() {
return (
<div>
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
onChange={(id,event,tab) => this.changeHandler(id,event,tab)}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
data-path={tab.props.path||"/"}
style={{height: '64px', width: '100px'}}
/>
);
})}
</Tabs>
</div>
);
}
}
const CustomTabs = withRouter(CustomTabsImpl);
你可以使用 react routers NavLink 组件
import { NavLink } from 'react-router-dom';
<NavLink
activeClassName="active"
to="/foo"
>Tab 1</NavLink>
当 /foo 是路由时,active
class 将添加到此 link。 NavLink
也有一个 isActive
道具,可以传递一个函数来进一步自定义确定 link 是否处于活动状态的功能。
首先,感谢您回答您的问题。
我以不同的方式处理了这个问题,我决定 post 在这里感谢社区的赞赏。
我的推理是:"It would be simpler if I could tell the Tab instead the Tabs component about which one is active."
实现这一点非常简单,可以通过为 Tabs 组件设置一个已知的固定值并将该值分配给任何应该处于活动状态的选项卡来实现。
此解决方案要求托管选项卡的组件可以访问 react-router 中的位置和匹配等道具,如下所示
首先,我们创建一个工厂函数,从渲染方法中删除臃肿的代码。如果所需的路由匹配,这里将固定的 Tabs 值设置为 Tab,否则我只是抛出一个任意常量,例如 Infinity。
const mountTabValueFactory = (location, tabId) => (route) => !!matchPath(location.pathname, { path: route, exact: true }) ? tabId : Infinity;
之后,您只需将信息插入渲染函数即可。
render() {
const {location, match} = this.props;
const tabId = 'myTabId';
const getTabValue = mountTabValueFactory(location, tabId);
return (
<Tabs value={tabId}>
<Tab
value={getTabValue('/route/:id')}
label="tab1"
onClick={() => history.push(`${match.url}`)}/>
<Tab
value={getTabValue('/route/:id/sub-route')}
label="tab2"
onClick={() => history.push(`${match.url}/sub-route`)}
/>
</Tabs>
)
}
有没有一种简单的方法可以使 Material UI 选项卡和 React 路由器保持同步?
基本上,我想在用户单击选项卡 [1] 时更改 URL 并且当用户导航到具有非选项卡的不同页面时选项卡应自动更改 link 或按钮,当然还有直接访问 [2] 和页面刷新。
此外,如果 React 路由器的 非精确 功能也很好,那么 /foo
选项卡应该对 /foo
和 /foo
都有效/foo/bar/1
.
[1] 其他 SO 答案建议直接使用历史 api,这是 react-router 的好习惯吗?
[2] 我不确定它叫什么,我的意思是当用户直接加载 /foo
而不是加载 /
然后导航到 /foo
时选项卡或 link
编辑:
我创建了一个包装器组件来完成这项工作,但有一些问题:
class CustomTabs extends React.PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
setActiveTab(id) {
this.setState({
activeTab: id
});
return null;
}
render() {
return (
<div>
{this.props.children.map((tab,index) => {
return (
<Route
key={index}
path={tab.props.path||"/"}
exact={tab.props.exact||false}
render={() => this.setActiveTab(index)}
/>
);
})}
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
style={{paddingLeft: '10px', paddingRight: '10px', height: '64px'}}
onActive={() => {
this.props.history.push(tab.props.path||"/")
}}
/>
);
})}
</Tabs>
</div>
);
}
}
我是这样使用它的:
<AppBar title="Title" showMenuIconButton={false}>
<CustomTabs history={this.props.history}>
<Tab label="Home" path="/" exact/>
<Tab label="Foo" path="/foo"/>
<Tab label="Bar" path="/bar"/>
</CustomTabs>
</AppBar>
但是:
- 我在我的控制台中收到此警告:
Warning: setState(...): Cannot update during an existing state transition (such as within
render
or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved tocomponentWillMount
.
我认为这是因为我在调用 render()
后立即设置状态 - 因为 Route.render
,但我不知道如何解决这个问题。
编辑#2
我终于解决了所有问题,但方式有点老套。
class CustomTabsImpl extends PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
componentWillMount() {
this.state.activeTab = this.pathToTab(); // eslint-disable-line react/no-direct-mutation-state
}
componentWillUpdate() {
setTimeout(() => {
let newTab = this.pathToTab();
this.setState({
activeTab: newTab
});
}, 1);
}
pathToTab() {
let newTab = 0;
this.props.children.forEach((tab,index) => {
let match = matchPath(this.props.location.pathname, {
path: tab.props.path || "/",
exact: tab.props.exact || false
});
if(match) {
newTab = index;
}
});
return newTab;
}
changeHandler(id, event, tab) {
this.props.history.push(tab.props['data-path'] || "/");
this.setState({
activeTab: id
});
}
render() {
return (
<div>
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
onChange={(id,event,tab) => this.changeHandler(id,event,tab)}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
data-path={tab.props.path||"/"}
style={{height: '64px', width: '100px'}}
/>
);
})}
</Tabs>
</div>
);
}
}
const CustomTabs = withRouter(CustomTabsImpl);
你可以使用 react routers NavLink 组件
import { NavLink } from 'react-router-dom';
<NavLink
activeClassName="active"
to="/foo"
>Tab 1</NavLink>
当 /foo 是路由时,active
class 将添加到此 link。 NavLink
也有一个 isActive
道具,可以传递一个函数来进一步自定义确定 link 是否处于活动状态的功能。
首先,感谢您回答您的问题。 我以不同的方式处理了这个问题,我决定 post 在这里感谢社区的赞赏。
我的推理是:"It would be simpler if I could tell the Tab instead the Tabs component about which one is active."
实现这一点非常简单,可以通过为 Tabs 组件设置一个已知的固定值并将该值分配给任何应该处于活动状态的选项卡来实现。
此解决方案要求托管选项卡的组件可以访问 react-router 中的位置和匹配等道具,如下所示
首先,我们创建一个工厂函数,从渲染方法中删除臃肿的代码。如果所需的路由匹配,这里将固定的 Tabs 值设置为 Tab,否则我只是抛出一个任意常量,例如 Infinity。
const mountTabValueFactory = (location, tabId) => (route) => !!matchPath(location.pathname, { path: route, exact: true }) ? tabId : Infinity;
之后,您只需将信息插入渲染函数即可。
render() {
const {location, match} = this.props;
const tabId = 'myTabId';
const getTabValue = mountTabValueFactory(location, tabId);
return (
<Tabs value={tabId}>
<Tab
value={getTabValue('/route/:id')}
label="tab1"
onClick={() => history.push(`${match.url}`)}/>
<Tab
value={getTabValue('/route/:id/sub-route')}
label="tab2"
onClick={() => history.push(`${match.url}/sub-route`)}
/>
</Tabs>
)
}