React router 模态路由
React router modal-only routes
我的应用程序中确实有一个 public 和一个私有区域,我希望能够在 public 视图中的任何位置显示登录模式对话框。模态应该有自己的路线。第二个用例是私有区域中的配置文件模式。
问题是当模态显示时,背景中的当前视图会消失,因为模态不是当前视图路由的子级。
由于我不想为每个可能的视图路由添加相同的模态,这里有一个问题:是否可以将模态路由与其父路由分离并在没有主要内容渲染的情况下在应用程序中随处显示它们?最好的方法是什么?我发现 this issue 但似乎不是同一个问题。
在模式路径上刷新浏览器不会在后台呈现任何内容,但这是我可以忍受的问题。
我认为您最好的选择可能是使用隐藏的 state
或查询字符串(对于 permalinks),或两者兼而有之,尤其是在模式(例如登录模式)的情况下可以显示在任何页面上。以防万一你不知道,React Router 公开了历史 API 的 state
部分,允许你将数据存储在用户历史中,但实际上在 URL 中不可见。
这是我想到的一组路线;你可以跳转 straight into the working example on JSBin if you want. You can also view the resulting example app in its own window 这样你就可以看到 URL 的变化(它使用散列位置策略来与 JSBin 兼容)并确保刷新如你所期望的那样工作。
const router = (
<Router>
<Route component={LoginRedirect}>
<Route component={LocationDisplay}>
<Route path="/" component={ModalCheck}>
<Route path="/login" component={makeComponent("login")} />
<Route path="/one" component={makeComponent("one")} />
<Route path="/two" component={makeComponent("two")} />
<Route path="/users" component={makeComponent("users")}>
<Route path=":userId" component={UserProfileComponent} />
</Route>
</Route>
</Route>
</Route>
</Router>
);
让我们研究一下这些路由及其组件。
首先,makeComponent
只是一个方法,它接受一个字符串并创建一个 React 组件,该组件将该字符串呈现为 header 然后它的所有 children;这只是一种创建组件的快速方法。
LoginRedirect
是一个具有一个目的的组件:检查路径是否为 /login
或 如果当前路径上有 ?login
查询字符串。如果其中任何一个为真, 和 当前状态不包含 login
键,它将状态上的 login
键设置为 true
.如果 any child 路由匹配(即始终渲染组件),则使用该路由。
class LoginRedirect extends React.Component {
componentWillMount() {
this.handleLoginRedirect(this.props);
}
componentWillReceiveProps(nextProps) {
this.handleLoginRedirect(nextProps);
}
handleLoginRedirect(props) {
const { location } = props;
const state = location.state || {};
const onLoginRoute = location.query.login || location.pathname === "/login";
const needsStateChange = onLoginRoute && !state.login;
if (needsStateChange) {
// we hit a URL with ?login in it
// replace state with the same URL but login modal state
props.history.setState({login: true});
}
}
render() {
return React.Children.only(this.props.children);
}
}
如果您不想使用查询字符串来显示登录模式,您当然可以修改此组件以满足您的需要。
接下来是LocationDisplay
,但它只是我为 JSBin 演示构建的一个组件,显示有关当前路径、状态和查询的信息,并且还显示一组 link演示应用程序的功能。
登录状态对于下一个组件很重要,ModalCheck
。该组件负责检查 login
(或 profile
或可能的任何其他)键的当前状态,并根据需要显示相关联的模式。 (JSBin 演示实现了一个超级简单的模式,你的肯定会更好。:)它还在主页上以文本形式显示模式检查的状态。)
class ModalCheck extends React.Component {
render() {
const location = this.props.location;
const state = location.state || {};
const showingLoginModal = state.login === true;
const showingProfileMoal = state.profile === true;
const loginModal = showingLoginModal && <Modal location={location} stateKey="login"><LoginModal /></Modal>;
const profileModal = showingProfileMoal && <Modal location={location} stateKey="profile"><ProfileModal /></Modal>;
return (
<div style={containerStyle}>
<strong>Modal state:</strong>
<ul>
<li>Login modal: {showingLoginModal ? "Yes" : "No"}</li>
<li>Profile modal: {showingProfileMoal ? "Yes" : "No"}</li>
</ul>
{loginModal}
{profileModal}
{this.props.children}
</div>
)
}
}
其他一切都是相当标准的 React Router 东西。唯一需要注意的是 LocationDisplay
中的 Link
显示了如何 link 到应用程序中的不同位置,在某些情况下显示模式。
首先,您当然可以 link(和 permalink)到任何要求它显示登录模式的页面,方法是使用查询字符串中的 login
键:
<Link to="/one" query={{login: 1}}>/one?login</Link>
<Link to="/two" query={{login: 1}}>/two?login</Link>
当然也可以link直接上/login
URL.
接下来,请注意您可以显式设置状态以便模式显示,这将不会更改URL。但是,它会保留在历史记录中,因此 back/forward 可以按您的预期使用,并且刷新将在同一背景页面的顶部显示模态。
<Link to="/one" state={{login: true}}>/one with login state</Link>
<Link to="/two" state={{login: true}}>/two with login state</Link>
您还可以link到当前页面,添加一个特定的模式。
const path = props.location.pathname;
<Link to={path} state={{login: true}}>current path + login state</Link>
<Link to={path} state={{profile: true}}>current path + profile state</Link>
当然,并非所有这些都适用或有用,具体取决于您希望应用如何工作。例如,除非一个模态是真正全局的(也就是说,它可以显示不管路由),这可能工作正常,但对于模式,比如显示给定用户的个人资料,我可能会把它作为一个单独的路线,将其嵌套在 parent 中,例如:
<Route path="/users/:id" component={UserPage}>
<Route path="/users/:id/profile" component={UserProfile} />
</Route>
UserProfile
,在这种情况下,将是呈现模态的组件。
您可能想要进行更改的另一个示例是在历史记录中存储某些模态;如果您不想,请根据需要使用 replaceState
而不是 setState
。
使用shouldComponentUpdate做这个会很简单
<Router history={newHistory}>
<Route path="/" component={App}>
<Route path="home" component={Container} >
<IndexRedirect to="home"/>
<Route path="login" component={Authentication}/>
<Route path="home" component={Home}/>
<Route path="post/:id" component={Post}/>
</Route>
</Route>
</Router>
<Link to='/post/123' state={{isModal:true}}/>
<Link to='/home' state={{isModal:false}}/>
<Link to='/login' state={{isModal:true}}/>
Container = React.createClass({
render() {
let isModal = (location.state && location.state.modal);
return (
<div id="MeContainer">
<ModalControlOpen isModal={isModal}>
<Modal returnTo={this.props.location.pathname}>
{this.props.children}
</Modal>
</ModalControlOpen>
<ModalControlClose isModal={isModal}>
{this.props.children}
</ModalControlClose>
</div>
)
}
});
ModalControlOpen = React.createClass({
render(){
if (this.props.isModal) {
return (
this.props.children
)
} else return <div></div>
}
});
ModalControlClose = React.createClass({
shouldComponentUpdate(nextProp){
return !nextProp.isModal
},
render(){
return (
this.props.children
)
}
});
我的应用程序中确实有一个 public 和一个私有区域,我希望能够在 public 视图中的任何位置显示登录模式对话框。模态应该有自己的路线。第二个用例是私有区域中的配置文件模式。
问题是当模态显示时,背景中的当前视图会消失,因为模态不是当前视图路由的子级。
由于我不想为每个可能的视图路由添加相同的模态,这里有一个问题:是否可以将模态路由与其父路由分离并在没有主要内容渲染的情况下在应用程序中随处显示它们?最好的方法是什么?我发现 this issue 但似乎不是同一个问题。
在模式路径上刷新浏览器不会在后台呈现任何内容,但这是我可以忍受的问题。
我认为您最好的选择可能是使用隐藏的 state
或查询字符串(对于 permalinks),或两者兼而有之,尤其是在模式(例如登录模式)的情况下可以显示在任何页面上。以防万一你不知道,React Router 公开了历史 API 的 state
部分,允许你将数据存储在用户历史中,但实际上在 URL 中不可见。
这是我想到的一组路线;你可以跳转 straight into the working example on JSBin if you want. You can also view the resulting example app in its own window 这样你就可以看到 URL 的变化(它使用散列位置策略来与 JSBin 兼容)并确保刷新如你所期望的那样工作。
const router = (
<Router>
<Route component={LoginRedirect}>
<Route component={LocationDisplay}>
<Route path="/" component={ModalCheck}>
<Route path="/login" component={makeComponent("login")} />
<Route path="/one" component={makeComponent("one")} />
<Route path="/two" component={makeComponent("two")} />
<Route path="/users" component={makeComponent("users")}>
<Route path=":userId" component={UserProfileComponent} />
</Route>
</Route>
</Route>
</Route>
</Router>
);
让我们研究一下这些路由及其组件。
首先,makeComponent
只是一个方法,它接受一个字符串并创建一个 React 组件,该组件将该字符串呈现为 header 然后它的所有 children;这只是一种创建组件的快速方法。
LoginRedirect
是一个具有一个目的的组件:检查路径是否为 /login
或 如果当前路径上有 ?login
查询字符串。如果其中任何一个为真, 和 当前状态不包含 login
键,它将状态上的 login
键设置为 true
.如果 any child 路由匹配(即始终渲染组件),则使用该路由。
class LoginRedirect extends React.Component {
componentWillMount() {
this.handleLoginRedirect(this.props);
}
componentWillReceiveProps(nextProps) {
this.handleLoginRedirect(nextProps);
}
handleLoginRedirect(props) {
const { location } = props;
const state = location.state || {};
const onLoginRoute = location.query.login || location.pathname === "/login";
const needsStateChange = onLoginRoute && !state.login;
if (needsStateChange) {
// we hit a URL with ?login in it
// replace state with the same URL but login modal state
props.history.setState({login: true});
}
}
render() {
return React.Children.only(this.props.children);
}
}
如果您不想使用查询字符串来显示登录模式,您当然可以修改此组件以满足您的需要。
接下来是LocationDisplay
,但它只是我为 JSBin 演示构建的一个组件,显示有关当前路径、状态和查询的信息,并且还显示一组 link演示应用程序的功能。
登录状态对于下一个组件很重要,ModalCheck
。该组件负责检查 login
(或 profile
或可能的任何其他)键的当前状态,并根据需要显示相关联的模式。 (JSBin 演示实现了一个超级简单的模式,你的肯定会更好。:)它还在主页上以文本形式显示模式检查的状态。)
class ModalCheck extends React.Component {
render() {
const location = this.props.location;
const state = location.state || {};
const showingLoginModal = state.login === true;
const showingProfileMoal = state.profile === true;
const loginModal = showingLoginModal && <Modal location={location} stateKey="login"><LoginModal /></Modal>;
const profileModal = showingProfileMoal && <Modal location={location} stateKey="profile"><ProfileModal /></Modal>;
return (
<div style={containerStyle}>
<strong>Modal state:</strong>
<ul>
<li>Login modal: {showingLoginModal ? "Yes" : "No"}</li>
<li>Profile modal: {showingProfileMoal ? "Yes" : "No"}</li>
</ul>
{loginModal}
{profileModal}
{this.props.children}
</div>
)
}
}
其他一切都是相当标准的 React Router 东西。唯一需要注意的是 LocationDisplay
中的 Link
显示了如何 link 到应用程序中的不同位置,在某些情况下显示模式。
首先,您当然可以 link(和 permalink)到任何要求它显示登录模式的页面,方法是使用查询字符串中的 login
键:
<Link to="/one" query={{login: 1}}>/one?login</Link>
<Link to="/two" query={{login: 1}}>/two?login</Link>
当然也可以link直接上/login
URL.
接下来,请注意您可以显式设置状态以便模式显示,这将不会更改URL。但是,它会保留在历史记录中,因此 back/forward 可以按您的预期使用,并且刷新将在同一背景页面的顶部显示模态。
<Link to="/one" state={{login: true}}>/one with login state</Link>
<Link to="/two" state={{login: true}}>/two with login state</Link>
您还可以link到当前页面,添加一个特定的模式。
const path = props.location.pathname;
<Link to={path} state={{login: true}}>current path + login state</Link>
<Link to={path} state={{profile: true}}>current path + profile state</Link>
当然,并非所有这些都适用或有用,具体取决于您希望应用如何工作。例如,除非一个模态是真正全局的(也就是说,它可以显示不管路由),这可能工作正常,但对于模式,比如显示给定用户的个人资料,我可能会把它作为一个单独的路线,将其嵌套在 parent 中,例如:
<Route path="/users/:id" component={UserPage}>
<Route path="/users/:id/profile" component={UserProfile} />
</Route>
UserProfile
,在这种情况下,将是呈现模态的组件。
您可能想要进行更改的另一个示例是在历史记录中存储某些模态;如果您不想,请根据需要使用 replaceState
而不是 setState
。
使用shouldComponentUpdate做这个会很简单
<Router history={newHistory}>
<Route path="/" component={App}>
<Route path="home" component={Container} >
<IndexRedirect to="home"/>
<Route path="login" component={Authentication}/>
<Route path="home" component={Home}/>
<Route path="post/:id" component={Post}/>
</Route>
</Route>
</Router>
<Link to='/post/123' state={{isModal:true}}/>
<Link to='/home' state={{isModal:false}}/>
<Link to='/login' state={{isModal:true}}/>
Container = React.createClass({
render() {
let isModal = (location.state && location.state.modal);
return (
<div id="MeContainer">
<ModalControlOpen isModal={isModal}>
<Modal returnTo={this.props.location.pathname}>
{this.props.children}
</Modal>
</ModalControlOpen>
<ModalControlClose isModal={isModal}>
{this.props.children}
</ModalControlClose>
</div>
)
}
});
ModalControlOpen = React.createClass({
render(){
if (this.props.isModal) {
return (
this.props.children
)
} else return <div></div>
}
});
ModalControlClose = React.createClass({
shouldComponentUpdate(nextProp){
return !nextProp.isModal
},
render(){
return (
this.props.children
)
}
});