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的數據和方法,利用這些數據與屬性傳入數據進行比對,從而確定是否渲染組件或者子組件