前言
從vue轉入到react技術棧有兩月了,兩個月來一直斷斷續續學習react的知識。自己也很久沒有寫過總結了(恐怖的加班),趁元旦假期抽空總結一波(還是要學習地)。習慣了vue簡潔的語法和api,再回過來寫react元件化,不習慣有木有(怪自己太菜)。
文中若有錯誤點,歡迎各位大佬指正
react-router路由的模式選擇
用過react-router的會比較熟悉react路由模式,一般有兩種,分別是hashHistory和history, 使用hashHistory模式,url後面會帶有#號不太美觀,而使用history模式,就是正常的url,但是如果匹配不到這個路由就會出現404請求。這種情況需要在伺服器配置,如果URL匹配不到任何靜態資源,就跳轉到預設的index.html
兩種方式實現原理
1.hashHistory路由
hash值變化不會導致瀏覽器向伺服器發出請求,而且 hash 改變會觸發 hashchange 事件,瀏覽器的進後退也能對其進行控制
如http://localhost:3000/detail#/home,這段url的#號後面的就為hash值
window.location.hash 取到的就是#home
//監聽hash變化
window.addEventListener ('hashchange', (e)=> {
this.setState({
...this.state,
location:{
...location,
hash:window.location.hash
pathname:window.location.hash
},
})
});
複製程式碼
2.history路由
window.history 物件表示視窗的瀏覽歷史,它只有back()、forward() 和 go() 方法可以讓使用者呼叫, 而h5規範中又新增了幾個關於操作history記錄的APi,分別是replaceState,pushState,popstate
在點選瀏覽器前進和後退的時候,都會觸發popstate事件,而採用pushState和replaceState不會觸發此事件,
程式碼示例
/*
state 要跳轉到的URL對應的狀態資訊,可以存一些需要想儲存的值,也可以直接傳{}
title 該條記錄的title,現在大多數瀏覽器不支援或者忽略這個引數
url 這個引數提供了新歷史紀錄的地址,可以是相對路徑,不可跨域
*/
window.history.pushState(state, title, url)
//replaceState和pushState的不同之處在與,replace是替換棧頂上的那個元素,不會影響棧的長度
window.history.replaceState(state, title, url)
//例子
window.addEventListener('popstate',(e)=>{
this.setState({
...this.state,
location:{
...location,
pathname:e.state.path,
},
})
})
複製程式碼
實現路由
有了以上的知識點,就可以動手寫元件了,在動手寫元件之前,先來看看官方路由的具體用法,才能知道如何去設計這些元件
模組匯入和匯出
import { HashRouter as Router, Route,Link, Redirect,Switch,} from 'react-router-dom';
複製程式碼
react-router-dom中引出了很多的元件,模組中向外部匯出介面,常見的做法是資料夾中有一個index.js向外暴露出這個模組的所有介面,所以可以設計為react-router-dom資料夾會下有一堆元件,通過一個index.js,使用export defalut向外部匯出介面對接
路由中的元件使用示例
//router.js 配置路由
export default const BasicRouter = () => {
return (
<div>
<Router>
<div>
<div>
<Link to="/home">首頁</Link>
<Link to="/detail">詳情</Link>
</div>
<Switch>
<Route path="/home" component={Home} />
<Route path="/detail" component={Detail} />
<Redirect to="/home" />
</Switch>
</div>
</Router>
</div>
)
}
複製程式碼
可以看到Router是最外層的父元件,它裡面的每個子元件都可以從props中拿到Router元件中的state,router看作是父元件,而裡面的route、Switch元件等,一般做法是採用porps向下級傳遞的方法,但如父子元件中間跨了多個子元件,採用props傳值就很麻煩,這裡採用元件的context來傳遞共享資料
//使用路由後,在所有子元件中列印this.props,會發現有這一陀東西,這裡只是router元件中的部分state狀態
{
history:{
replace:e=>{},
push:e=>{},
},
match:{
params:'',
isExact:false
},
location:{
pathname:'',
hash:'',
}
}
複製程式碼
熟悉redux的人應該都知道,store中的共享狀態需要通過一個頂層元件作為父元件,一般將頂級元件叫做Provider元件,由它內部建立context來作為資料的提供者
例如redux中的connect方法,它就是一個高階元件,connext方法的引數在函式中通過解構拿到store中的資料,再通過props的方式給到connext傳入的元件中,而在react 16.3版本中新增createContext方法,它返回了Provider, Consumer元件等,
context實現
//context.js
import React from 'react';
let { Provider,Consumer } = React.createContext()
export { Provider, Consumer}
//頂級元件
import { Provider } from './context'
<Provider value={this.state}>
{this.props.children}
</Provider>
//所有的子級元件 Consumer裡面的childer是一個函式,由函式來返回渲染的塊,state就是provider傳入的value
import { Consumer} from './context'
render(){
<Consumer>
{state => {
//這裡的state就是provider傳入的value
if(state.pathname===path){
return this.props.component
}
return null
}}
</Consumer>
}
複製程式碼
Provider元件實現了,其他的就比較好辦了,在hashRouter頂級元件中使用Provider元件,裡面每個子元件中外層採用Consumer包裹,這樣每個元件都能拿到provider的資料
hashRouter.js實現
hashRouter用於提供hisotry的資料以及方法給到子元件,如push,go等方法
//react-router-dom資料夾下hashRouter.js
import React, {Component} from 'react';
import {Provider} from './context';
export default class HashRouter extends Component {
constructor () {
super (...arguments);
this.state = {
location: {
pathname: window.location.hash.slice(1), //去除#號
hash: window.location.hash,
},
history:{
push(to){
window.location.hash = to
}
}
};
}
componentDidMount () {
let location = this.state
window.addEventListener ('hashchange', (e)=> {
this.setState ({
location: {
...location,
hash:window.location.hash,
pathname: window.location.hash.slice (1) || '', //去除#號
},
});
});
}
render () {
return (
<Provider value={this.state}>
{this.props.children}
</Provider>
);
}
}
複製程式碼
hashRouter元件state中的的push方法,直接將 window.location.hash值改變,會觸發haschange時間,而在componentDidMount鉤子函式中,監聽hashchange事件中,在變化後將hash值存入state中
在componentWillUnmount記得要把繫結的事件解綁,remove事件需要將函式抽出來作為一個變數引用才能清除掉
Route.js實現
該元件用來傳入component和path
import React, {Component} from 'react';
import { Consumer} from './context'
const pathToRegexp = require('path-to-regexp');
export default class Route extends Component {
constructor () {
super (...arguments)
}
render () {
let { path, component: Component, exact=false } = this.props;
return (
<Consumer>
{state => {
//pathToRegexp 方法,第一個引數,
let reg= pathToRegexp(path,[],{end:exact })
let pathname = state.location.pathname
if (reg.test(pathname)) {
return <Component {...state} />;
}
return null;
}}
</Consumer>
);
}
}
複製程式碼
正常情況下,url可能會有這幾種情況,如/foo/bar, 或者/foo:123,這種url如果不處理,預設是匹配不到的,而exact引數就是控制是否精確匹配,這裡引入了 pathToRegexp庫來生成正規表示式,來處理 url 中地址查詢引數
//示例程式碼
//如果需要精確匹配,將pathToRegexp的第三個引數end傳為true,pathToRegexp第二個引數是匹配到的值
let ret = []
var re = pathToRegexp('/detail',ret,{
end:true
})
re.test('/foo/1') // true
//生成的正則
/^\/detail(?:\/)?$/i
/^\/detail(?:\/(?=$))?(?=\/|$)/i
複製程式碼
Switch.js實現
用於匹配只渲染一個route元件
import React, {Component} from 'react';
import { Consumer} from './context'
const pathToRegexp = require('path-to-regexp');
export default class Switch extends Component {
constructor () {
super (...arguments);
}
render () {
return (
<Consumer>
{state => {
let pathname =state.location.pathname;
let children = this.props.children
for(let i=0;i<children.length;i++){
let child = children[i]
let path = child.props.path || ''
let reg = pathToRegexp(path,[],{end:false})
if(reg.test(pathname)){
return child
}
}
return null
}}
</Consumer>
);
}
}
//使用Switchs
<Switch>
<Route path="/home" component={Home} />
<Route path="/detail" component={Detail} />
<Redirect to="/home"/>
</Switch>
複製程式碼
Switch元件將傳入的children,遍歷拿到每一個元件傳入的path,並生成正則,如果正則能夠匹配的上,則直接渲染child,否則return null,確保switch中包裹的子元件,只能渲染其中一個,switch元件是用於配合redirect元件來使用的
redirect.js實現
用於重定向
import React, {Component} from 'react';
import { Consumer} from './context'
export default class Redirect extends Component {
constructor () {
super (...arguments);
}
render () {
return (
<Consumer>
{state => {
let { history }= state;
history.push(this.props.to)
return null
}}
</Consumer>
);
}
}
複製程式碼
redirect元件實現非常簡單,如果該元件渲染,直接將window.location.hash = to
browserRouter.js的實現
browserRouter與hashRouter的實現不同點是,在state的push方法中呼叫window.history.pushState,壓入後,瀏覽器的url會直接變化頁面不會重新整理,另外popstate監聽事件,也需要同步一次state裡面的pathname
import React, {Component} from 'react';
import {Provider} from './context';
class browserRouter extends Component {
constructor () {
super (...arguments);
this.state = {
location: {
pathname: window.location.pathname ,
hash: window.location.hash,
},
history:{
push :(to)=>{
this.pushState(null,null,to)
}
},
queue:[]
};
this.pushState = this.pushState.bind(this)
}
pushState = (state="",title="",path="")=>{
let queue = this.state.queue
let {location} = this.state
let historyInfo ={state,title,path}
queue.push( historyInfo)
this.setState({
...this.state,
location:{
...location,
pathname:path,
},
queue,
})
window.history.pushState(historyInfo,title,path)
}
componentDidMount () {
let {location} = this.state
window.addEventListener('popstate',(e)=>{
this.setState({
...this.state,
location:{
...location,
pathname:e.state.path,
},
queue:this.state.queue,
})
})
}
render () {
return (
<Provider value={this.state}>
{this.props.children}
</Provider>
);
}
}
export default browserRouter;
複製程式碼
如何使用?
1.新建一個router.js,用於管理route元件
2.在index.js中匯入使用
import React from 'react';
import {
HashRouter as Router,
// BrowserRouter as Router,
Route,
Link,
Redirect,
Switch,
} from './react-router-dom';
import Home from './pages/home';
import Detail from './pages/detail';
const BasicRoute = () => {
return (
<div>
<Router>
<div>
<div>
<Link to="/home">首頁</Link>
<Link to="/detail">詳情</Link>
</div>
<Switch>
<Route path="/home" component={Home} />
<Route path="/detail" component={Detail} />
<Redirect to="/home" />
</Switch>
</div>
</Router>
</div>
);
};
export default BasicRoute;
// index.js中 使用
import Router from './router'
ReactDOM.render(<Router/>, document.getElementById('root'));
複製程式碼
結尾
簡易版的router元件到這裡就實現的差不多了,但是還是有很多功能沒實現,比如query引數處理,link元件等,有興趣可自行研究
程式碼地址 : github.com/huqc2513/re…