React-Router 渲染多个组件

使用 Route 配置的一个组件,当在浏览器中访问某一地址时,默认只会渲染该地址对应的组件,可能是在一个地方,也可能是多个地方(如果对该 Route 配置了多次),不仅如此,在同一地址下,我们也可以使用一些 ‘手段’ 渲染两个不同 location 下的组件。

Switch 组件会渲染 children 中的第一个匹配路径的组件,它还有一个 location 属性,该属性是可以动态改变的,利用这一特性,可以在某一地址下,把 Switch 组件的 location 属性手动改变以达到同一路径渲染多个不同组件的目的。比如有的情况下,需要弹出 Modal 对话框,而原来的组件保持不变,仍然渲染在页面中,这种情况下就适合使用 Switch 组件动态改变 location 来实现。

假定有这样一个场景,点击图片列表页面中的小图查看大图,大图使用 Modal 弹出的样式,在大图显示的时候,为了不使图片其它空白地方无填充内容,所以大图弹出的时候,使用半透明色使原有页面仍然可见。

定义样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var styles = {}
styles.modal_root = {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
background: 'rgba(0, 0, 0, 0.15)'
}
styles.modal_panel = {
position: 'absolute',
background: '#fff',
top: 0,
left: '10%',
right: '10%',
bottom: 0,
border: '2px solid #444'
}

上面定义了两个样式:

  • modal_root : 弹出框布局样式
  • modal_panel : 嵌套在弹出框中显示大图的布局样式

定义 Modal 弹出框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const IMAGES = [
{id: 0, title: '深兰花紫', color: 'DarkOrchid'},
{id: 1, title: '石灰绿', color: 'LimeGreen'},
{id: 2, title: '番茄色', color: 'Tomato'},
{id: 3, title: '#七八九', color: '#789'},
{id: 4, title: '赤红色', color: 'Crimson'}
]
const Modal = ({match, history}) => {
const image = IMAGES[parseInt(match.params.id, 10)]
if (!image) {
return null;
}
const back = (e) => {
e.stopPropagation()
history.goBack()
}
return (
<div onClick={back} style={styles.modal_root}>
<div className='modal' style={styles.modal_panel}>
<h1>{image.title}</h1>
<Image color={image.color}/>
<button type='button' onClick={back}>关闭</button>
</div>
</div>
)
}
const Image = ({color}) =>
<div style={{
width: '100%',
height: '80%',
background: color
}}/>

全部图片使用 IMAGES 来存储,在 Modal 弹出的时候,根据 match.params.idIMAGES 数组中查找当前需要显示大图的 IMAGE。每个 IMAGEtitlecolor 属性,使用 Image 组件来显示(背影颜色)图片。点击根布局或者关闭按钮,可以关闭 Modal

定义图片列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Gallery = () => {
return (
<div>
{IMAGES.map(i => (
<Link key={i.id} to={{pathname: `/img/${i.id}`, state: { modal: true }}}>
<Thumbnail color={i.color}/><p>{i.title}</p>
</Link>
))}
</div>
)
}
const Thumbnail = ({color}) =>
<div style={{
width: 50,
height: 50,
background: color
}}/>

使用一个 50 50 大小的 div 表示小图,以及图片颜色。使用 Link 指定小图被点击之后的跳转 location。其中 pathname 传递了图片的 idstate 表示是否是 *Modal 弹出。

定义应用程序组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class GalleryComponent extends React.Component {
constructor(props) {
super(props)
this.previousLocation = this.props.location
}
componentWillUpdate(nextProps) {
const {location} = this.props;
// 非 POP 就把 this.props.location 赋值给 previousLocation。
if (nextProps.history.action !== 'POP') {
this.previousLocation = this.props.location
}
}
render() {
const {location} = this.props
const isModal = !!(
location.state &&
location.state.modal &&
this.previousLocation !== location // 不是首次渲染。
)
return (
<div>
<Switch location={isModal? this.previousLocation: location}>
<Route exact path='/' component={Gallery}/>
</Switch>
{isModal ? <Route path='/img/:id' component={Modal}/> : null}
</div>
)
}
}
var router = (
<Router>
<Route component={GalleryComponent}/>
</Router>
);

componentWillUpdate 方法中,如果 action 不为 POP (不为 POP 就是 PUSH),就把当前的地址给保存到 this.previousLocation。然后在 render 方法中,如果是 isModal 弹出的话,则把 Switchlocation 指定成 this.previousLocation,则 Switch 中的 Route 将被渲染,同时在外部一并渲染 Modal 组件。

整个流程如下:

一开始路径是 localhost:8888/ ,此时渲染的是 Gallery 组件,点击 Gallery 组件中的某一个图片,componentWillUpdate 方法被调用,此时 this.props.location.pathname/,将该值保存到 this.previousLocationrender 方法被调用,此时 isModal 为 true,则 Switch 组件的 location/,这个路径正好匹配 Gallery,同时 Modal 也会被渲染。