react-router理解
1. Provider和Consumer
Provider和Comsumer是React提供的两个原生组件,Provider的value属性传递数据,Provider包裹内的所有Consumer都可以直接获取到Provider的数据
获取方法
let { Provider, Consumer } = React.createContext();
使用方法
<Provider value={{ name: "aeipyuan" }}>
<div>
<div>
<Consumer>{state => {
console.log(state)
return <div>{state.name}</div>
}}</Consumer>
</div>
</div>
</Provider>
2. HashRouter 和 Route
根据hash改变组件的步骤
- HashRouter绑定hashchange事件,每次发生都会触发setState,并记录新的hash
- 数据改变触发render函数,render一个Provider,并且将新数据(pathname)放到Provider的value属性上
- Router作为Router的子组件,也会重新执行render,此时通过Consumer接受Provider提供的数据,并且比对Route自身传递的path属性和state里面的pathname,如果符合条件则返回Route属性传入的Component进行渲染,否则返回null不渲染
/* index.jsx */
<Router>
<Route path="/home" component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/user" component={User}></Route>
</Router>
/* HashRouter.js */
export default class HashRouter extends React.Component {
constructor() {
super();
this.state = {
location: {/* slice去除# */
pathname: window.location.hash.slice(1) || '/'
}
}
}
/* 挂载组件完成时绑定hashchange事件 */
componentDidMount() {
window.location.hash = window.location.hash || '/'
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/'
}
})
})
}
render() {
/* 要传给Route使用的值 */
let value = {
location: this.state.location
}
return (<Provider value={{ ...value }}>
{this.props.children}
</Provider>)
}
}
/* Route.js */
export default class Route extends React.Component {
render() {
return (<Consumer>
{state => {
/* 浏览器路径 */
let pathname = state.location.pathname;/* /home */
/* 获取Route的路径和组件,以及是否精确匹配 */
//{path: "/home", component: ƒ, exac:true}
let { path, component: Component, exac = false } = this.props;
/* 比对路径 end为true时为严格模式时*/
let reg = pathToRegexp(path, [], { end: exac });
/* 通过属性把Provider的数据继续传给子组件 */
return reg.test(pathname) ? <Component {...state}/> : null;
}}
</Consumer>)
}
}
3. Link
/* index.jsx */
<div>
<Link to="/home"> 主页 </Link>
<Link to="/profile"> 个人中心 </Link>
<Link to="/user"> 用户 </Link>
</div>
实现步骤
- 扩充HashRender通过Provider传递的方法,增加push方法强制改变hash
/* 要传给Consumer使用的数据 */
let value = {
location: this.state.location,
history: {
push(to) {
window.location.hash = to;
}
}
}
- Link组件插入一个Consumer,返回值是一个a标签,点击触发HashRender提供的push方法
return (<Consumer>
{state => {
/* 调用state的push函数,强制跳转 */
return <a onClick={() => {
state.history.push(this.props.to)
}}>{this.props.children}</a>
}}
</Consumer>)
4. Switch
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/user" component={User}></Route>
</Switch>
Switch实现每次只匹配一个组件的效果
- 利用Consumer获取浏览器hash值
- 遍历孩子,遇到符合条件的直接停止遍历,不处理后面的子元素
return (<Consumer>
{state => {
let { pathname } = state.location;
/* 遍历子元素,找出符合条件的进行渲染然后返回 */
for (let child of this.props.children) {
/* 比对 */
let path = child.props.path || '';
let exac = child.props.exac || false;
let reg = pathToRegexp(path, [], { end: exac });
if (reg.test(pathname)) return child;
}
return null;
}}
</Consumer>)
5. Redirect
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/user" component={User}></Route>
<Redirect to="/err"></Redirect>
</Switch>
当所有页面都不匹配时强制跳转
return (<Consumer>
{state => {
// 直接强行改变hash
state.history.push(this.props.to);
return null;
}}
</Consumer>)
6. 根据参数匹配页面
match的作用是使组件可以通过类似/home/:id
的方式传递数据,渲染出id
相关数据
/* user.jsx */
return (<div>
<div>
<Link to="/user/add"> 用户添加 </Link>
<Link to="/user/list"> 用户列表 </Link>
<hr />
</div>
<div>
<Route path="/user/add" component={userAdd}></Route>
<Route path="/user/list" component={userList}></Route>
<Route path="/user/detail/:id/:name" component={userDetail}></Route>
</div>
</div>)
/* userList.jsx */
return (<div>
<Link to="/user/detail/1/Amy"> Amy </Link>
<Link to="/user/detail/2/Mike"> Mike </Link>
<Link to="/user/detail/3/Nancy"> Nancy </Link>
</div>)
/* userDetail.js */
return (
<div>
Detail
<div>id: {this.props.match.params.id}</div>
<div>name:{this.props.match.params.name}</div>
</div>
)
实现步骤:
- 利用pathToRegexp函数解析对比可以得到key和values的映射关系
let { pathToRegexp } = require('path-to-regexp')
let keys = [];
let reg = pathToRegexp('/user/detail/:id/:name', keys, { end: false })
console.log(keys.map(v => v.name));//[ 'id', 'name' ]
/* 测试 */
let pathname = '/user/detail/111/bbb';
let [url, ...values] = path.match(reg);
console.log(values);//['111','bbb'];对应于['id','name']
- 依据pathToRegexp规则修改Route类的render函数,将映射应用到params上,传递给子元素
/* Route.js */
render() {
return (<Consumer>
{state => {
/* 浏览器路径 */
let { pathname } = state.location;/* /home */
/* 获取Route的路径和组件,以及是否精确匹配 */
//{path: "/home", component: ƒ, exac:true}
let { path, component: Component, exac = false } = this.props;
/* 比对路径 end为true时为严格模式时*/
let keys = [];
let reg = pathToRegexp(path, keys, { end: exac });
keys = keys.map(v => v.name);
let [url, ...values] = pathname.match(reg) || [];
// console.log(keys)//['id','name']
// console.log(values);//['1','Amy']
/* 设置传给子节点的数据 */
let props = {
...state,
match: {
params: keys.reduce((data, item, idx) => {
data[item] = values[idx];
return data;
}, {})//结果 {id:'1',name:'Amy'}
}
}
return reg.test(pathname) ? <Component {...props} /> : null;
}}
</Consumer>)
}
过程描述
- 假设
to="/user/detail/1/Amy"
,点击link标签,调用state传入的state.history.push(to)
方法,改变window.location.hash
- 触发HashRouter的hashchange事件,改变HashRouter的state重新render包含的内容
App
组件的Route
匹配到/user
渲染User
组件User
组件的Route
匹配到/user/detail/:id/:name
,将{id:'1',name:'Amy'}
放到props.history.match
,再将整个props
解构传给userDetail
7. 总结
- HashRouter监听hash的改变并提供操作hash的方法,每次改变都会触发包裹的内部元素进行render,并使用Provider传递数据
- Route、Link、Switch、Redirect都是使用Consumer接受HashRouter关于hash的数据和方法,利用这些数据与属性传入数据进行比对,从而确定是否渲染组件或者子组件