在看啦一遍原始碼後,發現不管出什麼問題都可以輕鬆找到原因啦,哇咔咔
Router
安裝http-server
,react-router-dom
,path-to-regexp
將路徑轉換為正則
目的當你點選不同路徑,就渲染不同元件
實現方式
- hashrouter
- BrowserRouter
hashrouter
最早客戶端實現路由靠錨點,實現切換頁面不重新整理,
應用方法,對hashchange
進行監聽,這個是瀏覽器自帶的哦~!
//瀏覽器自帶
window.addEventListener(`hashchange`,(event)=>{
console.log(event);
})
複製程式碼
BrowserRouter
利用h5 APi 實現 history物件,它提供啦操作瀏覽器繪畫歷史的介面,追蹤一組location,並且儲存索引周,指向當前作用的location
跑通路由
從react-router-dom
引入Route(路由規則)和Router(容器)
src/index.js
import React from `react`;
import ReactDOM from `react-dom`;
import {HashRouter as Router,Route,Link,Switch,Redirect,Forward,Back} from `./react-router-dom`;
import Home from `./components/Home`;
import User from `./components/User`;
import Protected from `./components/Protected`;
import Profile from `./components/Profile`;
import Login from `./components/Login`;
import MenuLink from `./components/MenuLink`;
import NavHeader from `./components/NavHeader`;
import `bootstrap/dist/css/bootstrap.css`;
import `./components/MenuLink.css`;
//一個路徑對應一個元件
ReactDOM.render(
<Router>
<div>
<nav>
<div className="navbar navbar-inverse">
<div className="container-fluid">
<NavHeader />
<div>
<ul className="nav navbar-nav">
<MenuLink to=`/` exact={true} lable="首頁" />
<MenuLink to=`/user` lable="使用者管理" />
<MenuLink to=`/profile` lable="個人設定" />
</ul>
</div>
</div>
</div>
</nav>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/user" component={User}/>
<Route path="/login" component={Login}/>
<Protected path="/profile" component={Profile}/>
<Redirect to="/"/>
</Switch>
</div>
</Router>
, document.getElementById(`root`));
複製程式碼
Route兩個屬性,path對應路徑, component對應元件 exact 是否絕對匹配, Router只能擁有一個子元件
component對應元件的props屬性有
- history
- location {hash,pathname,search,state}
- match {path,url,isExact,params }
這些屬性是Router傳過來的,靠contxt中的(provider提供者,Consumer消費者)傳遞(react.6.3)他們是由 React.creatContext()
建立
我們要實現 Home User Protected Profile Login MenuLink NavHeader 和 react-router-dom裡的 HashRouter, Route,Link,Switch,Redirect,Forward,Back的功能
Components元件
components/Home主頁
import React , {Component} from `react`;
export default class Home extends Component{
render(){
return (
<div>Home</div>
)
}
}
複製程式碼
components/Profile受許可權頁面
import React,{Component} from `react`;
export default class Profile extends Component{
render() {
return (
<div>Profile</div>
)
}
}
複製程式碼
Protected受保護路由
import React,{Component} from `react`
import {Route,Redirect} from `../react-router-dom`;
// rest = {path,exact,,xxxxxx}
//返回路由,執行render,返回元件,或者重定向 to裡面放物件或者路徑
export default function ({component: Component,...rest}) {
return (
<Route {...rest} render={props => (
localStorage.getItem(`logined`)? <Component {...props} />:<Redirect to={{pathname: `/login`,state: {from:props.location.pathname}}}/>
)}/>
)
}
複製程式碼
components/User使用者介面
import React,{Component} from `react`;
import {Link,Route,Back,Forward} from `../react-router-dom`;
import UserAdd from `./UserAdd`;
import UserList from `./UserList`;
import UserDetail from `./UserDetail`;
export default class User extends Component{
componentWillMount() {
console.log(`User componentWillMount`);
}
componentDidMount() {
console.log(`User componentDidMount`);
}
render() {
return (
<div className="row">
<div className="col-md-2">
<ul className="nav nav-stacked">
<li><Link to="/user/add">新增使用者</Link></li>
<li><Link to="/user/list">使用者列表</Link></li>
<li><Back /></li>
<li><Forward /></li>
</ul>
</div>
<div className="col-md-10">
<Route path="/user/add" component={UserAdd} />
<Route path="/user/list" component={UserList} />
<Route path="/user/detail/:id" component={UserDetail}/>
</div>
</div>
)
}
}
複製程式碼
components/UserAdd新增使用者介面
有跳轉功能
import React , {Component} from `react`;
import api from `./api`;
import {Prompt} from `../react-router-dom`
export default class UserAdd extends Component{
state={
isBlocking: false
}
componentWillUpdate(nextProps, nextState) {
console.log(`Component WILL UPDATE!`);
console.log(nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
console.log(`Component DID UPDATE!`);
console.log(prevProps, prevState);
}
handleSubmit=(event) => {
this.setState({isBlocking: false},()=>{
event.preventDefault();
let username=this.username.value;
let email=this.email.value;
let user={username,email};
api.createUser(user);
this.props.history.push(`/user/list`);
})
}
render(){
// 提交表單,提交到跳頁面
return (
<form onSubmit={this.handleSubmit}>
<Prompt
when = {this.state.isBlocking}
message={
loc =>`請問你是否要切換到${loc.pathname}`
}
/>
<div className="form-group">
<label htmlFor="username" className="control-label">使用者名稱</label>
<input
onChange={()=> this.setState({isBlocking:true})}
ref={input=>this.username = input} type="text" className="form-control"/>
</div>
<div className="form-group">
<label htmlFor="email" className="control-label">郵箱</label>
<input
onChange={()=> this.setState({isBlocking:true})}
ref={input=>this.email = input} type="text" className="form-control"/>
</div>
<div className="form-group">
<input type="submit" className="btn btn-primary"/>
</div>
</form>
)
}
}
複製程式碼
components/UserList使用者列表展示頁
import React , {Component} from `react`;
import api from `./api`;
import {Link} from `../react-router-dom`;
import `bootstrap/dist/css/bootstrap.css`;
export default class UserList extends Component{
state={
users:[]
}
componentWillMount(){
//讀取使用者
let users = api.getUsers();
this.setState({users});
}
handleDelete =(id)=>{
let users = api.delUser(id);
this.setState({users});
}
render(){
return (
<table className="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>使用者名稱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
this.state.users.map(user => (
<tr key={user.id}>
{/* <td><Link to={`/user/detail/${user.id}`}>{user.id}</Link></td> */}
<td>
<Link to={{pathname:`/user/detail/${user.id}`,state:user}}>
{user.id}
</Link>
</td>
<td>{user.username}</td>
<td>
<button
onClick={()=>this.handleDelete(user.id)}
className="btn btn-danger">刪除</button>
</td>
</tr>
))
}
</tbody>
</table>
)
}
}
複製程式碼
components/menuLink選單
import React,{Component} from `react`
import {Route,Link} from `../react-router-dom`;
import `./MenuLink.css`
//渲染Route有三種方式 component render children
//什麼時候用函式元件,如果不需要state儘量用函式元件,簡潔,可控
export default ({to,exact=false,label}) => (
<Route path={to} exact={exact} children={
({match}) => <li className={match? `active`:``}><Link to={to}>{label}</Link></li>
}/>
)
複製程式碼
components/navHeader導航
import React,{Component} from `react`
import {withRouter} from `../react-router-dom`;
class NavHeader extends Component{
render() {
return (
<div className="navbar-header">
<a onClick={()=>this.props.history.push(`/`)} className="navbar-brand">管理系統</a>
</div>
)
}
}
//NavHeader本來是一個普通的元件,跟Route沒有關係
export default withRouter(NavHeader);
複製程式碼
components/UserDetail 使用者詳細資訊頁
import React , {Component} from `react`;
import api from `./api`;
export default class UserDetail extends Component{
state={
user:{}
}
componentDidMount(){
let user = this.props.location.state.user;
console.log(user);
if(!user){
let id = this.props.match.params.id;
user = api.getUser(id);
}
this.setState({user});
}
render(){
let user = this.state.user;
return (
<div>
<p>ID:{user.id}</p>
<p>使用者名稱:{user.username}</p>
<p>郵箱:{user.email}</p>
</div>
)
}
}
複製程式碼
components/api.js儲存新增使用者使用的方法
let userApi = {
//獲取所有使用者
getUsers(){
let usersStr = localStorage.getItem(`users`);
return usersStr? JSON.parse(usersStr) :[];
},
//建立使用者
createUser(user){
let users = userApi.getUsers();
user.id = users.length>0? users[users.length-1].id + 1:1;
users.push(user);
localStorage.setItem(`users`,JSON.stringify(users));
},
getUser(id){
return userApi.getUsers().find(user => user.id == id)
},
delUser(id){
let users = userApi.getUsers().filter(user => user.id != id)
localStorage.setItem(`users`,JSON.stringify(users));
return users;
}
}
export default userApi;
複製程式碼
react-router-dom 原始碼
react-router-dom/index.js
import HashRouter from `./HashRouter`;
import Route from `./Route`;
import Link from `./Link`;
import Switch from `./Switch`;
import Redirect from `./Redirect`;
import Forward from `./Forward`;
import Back from `./Back`;
import withRoter from `./withRoter`;
import Prompt from `./Prompt`;
export {HashRouter,Route,Link,Switch,Redirect,Back,Forward,withRoter,Prompt}
複製程式碼
hashrouter.js hashrouter是個返回容器的元件
import React , {Component} from `react`;
import { Provider } from `./context`;
//每當位址列的錨點發生變化的時候,都需要重新配置
export default class HashRouter extends Component{
state={
location:{
pathname:window.location.hash?window.location.hash.slice(1):`/`
}
}
componentDidMount(){
window.addEventListener(`hashchange`,()=>{
this.setState({
location:{
...this.state.location,
pathname:window.location.hash?window.location.hash.slice(1):`/`
}
})
})
}
render(){
let that=this;
let value = {
location:that.state.location,
history:{
//value會用Provider傳遞給Consumer,從而使用用這裡定義方法
push(to){
if(that.block){
//保證to是物件
let ok = window.confirm(that.block(typeof to === `object` ? to:{pathname:to}));
if(!ok){
return;
}
}
// if(that.unblock){
// window.confirm(that.block)
// }
if(typeof to === `object`){
let {pathname,state} = to;
that.setState({
...that.state,
location:{
...that.state.location,
pathname,
state
}
},()=>{
window.location.hash = pathname;
})
}else{
window.location.hash = to;
}
},
goback(){
window.history.go(-1);
},
forward(){
window.history.go(1);
},
//彈窗方法
block(message){
that.block = message;
},
unblock(message){
that.block = null;
}
}
}
return(
// 想傳遞資料用Provider,接收資料用Consumer
<Provider value={value}>
{/* children就是Route */}
{this.props.children}
</Provider>
)
}
}
複製程式碼
react-router-dom/Route.js
import React,{Component} from `react`;
import {Consumer} from `./context`;
import pathToRegexp from `path-to-regexp`;
//每當位址列的錨點發生變化的時候,都需要重新配置
//pathToRegexp(`要轉換的路徑`,引數,是否結束)
export default class Route extends Component{
render() {
return (
<Consumer>
{
// 接收屬性
value => {
let {location: {pathname}}=value;// /user
let {path="/",component: Component,exact=false,render,children}=this.props;
let keys=[];//[id]
//匹配路徑
let regexp=pathToRegexp(path,keys,{end: exact});
let result=pathname.match(regexp);
let props={
location: value.location,
history:value.history
}
if(result){
let [,...values]=result;
keys=keys.map(key => key.name);
let params = keys.reduce((memo,name,index) => {
memo[name]=values[index];
return memo;
},{});
//matchv {path,url,isExact,params }
let match={
url:pathname,
path,
params
}
props.match=match;
//prototype元件,如果render存在,則執行
//渲染Route有三種方式 component render children
if (Component) {
return <Component {...props}/>;
} else if (render) {
return render(props);
} else if (children) {
return children(props);
} else {
return null;
}
} else {
if (children) {
return children(props);
} else {
return null;
}
}
}
}
</Consumer>
)
}
}
複製程式碼
/Link.js
import React , {Component} from `react`;
import {Consumer} from `./context`;
//每當位址列的錨點發生變化的時候,都需要重新配置
export default class Link extends Component{
render(){
return(
<Consumer>
{
value => {
let {history: {push}} = value;
return <a onClick={()=>push(this.props.to)}>{this.props.children}</a>
}
}
</Consumer>
)
}
}
複製程式碼
switch.js匹配優化
import React,{Component} from `react`;
import {Consumer} from `./context`;
import pathToRegexp from `path-to-regexp`;
export default class Switch extends Component{
render(){
return (
<Consumer>
{
value =>{
let {location:{pathname}} = value;
let children = this.props.children;
for(let i=0;i<children.length;i++){
let child=children[i];
//path的預設值為/ exact預設值為false,非精確匹配 /user/1
let {path="/",exact=false}=child.props;//:id
let reg=pathToRegexp(path,[],{end: exact});
if(reg.test(pathname)){
return child;
}
}
return null;
}
}
</Consumer>
)
}
}
複製程式碼
Redirect.js重定向
import React , {Component} from `react`;
import {Consumer} from `./context`;
export default class Redirect extends Component{
render(){
return(
<Consumer>
{
value=>{
value.history.push(this.props.to);
return null;
}
}
</Consumer>
)
}
}
複製程式碼
Back.js
import React , {Component} from `react`;
import {Consumer} from `./context`;
export default class Back extends Component{
render(){
return (
<Consumer>
{
value => {
return <a onClick={() => value.history.goback()}>返回</a>;
}
}
</Consumer>
)
}
}
複製程式碼
Forward.js
import React , {Component} from `react`;
import {Consumer} from `./context`;
export default class Back extends Component{
render(){
return (
<Consumer>
{
value => {
return <a onClick={()=>value.history.forward()}>前進</a>;
}
}
</Consumer>
)
}
}
複製程式碼
withRoter 將Component 和router建立連結
import React from `react`;
import { Route } from `../react-router-dom`;
//<Route component = {Component} />是例項,不能被直接返回
export default (Component) => () => <Route component = {Component} />
複製程式碼
Prompt.js防止條狀彈出框
import React,{Component} from `react`
import {Consumer} from `./context`;
export default class Prompt extends Component{
componentWillUnmount() {
this.history.unblock();
}
render() {
return (
<Consumer>
{
value => {
this.history=value.history;
let {when,message}=this.props;
if (when) {
value.history.block(message);
} else {
value.history.unblock();
}
}
}
</Consumer>
)
}
}
複製程式碼
Consumer,provider原理
元件複用兩大策略
- 高階元件
- 函式組委子元件
import React,{Component} from `react`
import ReactDOM from `react-dom`;
class Panel extends Component{
render() {
return (
<div className="panel panel-default">
<div className="panel-heading">頭部</div>
<div className="panel-body">
{
this.props.children(`皮膚`)
}
</div>
</div>
)
}
}
ReactDOM.render(<div>
<Panel>
{(text) => <div style={{color:`red`}}>{`我是${text}`}</div>}
</Panel>
<Panel>
{(text) => <div style={{color:`green`}}>{`我是${text}`}</div>}
</Panel>
</div>,document.querySelector(`#root`));
複製程式碼
同理
import React,{Component} from `react`
import ReactDOM from `react-dom`;
//let {Provider,Consumer}=React.createContext();
class Provider extends Component{
render() {
let value=this.props.value;
//拿到consumer
let children=this.props.children;
children = children.map(child => {
//判斷consumer型別
if (child.type.toString().includes(`Consumer`))
//cloneElement可以返回就屬性的props,也可以繼承新的props
return React.cloneElement(child,{value});
else
return child;
});
return <div>{children}</div>;
}
}
class Consumer extends Component{
render() {
return this.props.children(this.props.value);
}
}
ReactDOM.render((
<Provider value={1}>
<Consumer>
{
value => <div>{value}</div>
}
</Consumer>
<div>2</div>
</Provider>
),document.querySelector(`#root`));
大功告成長城,不懂得留言哦~複製程式碼