動態

詳情 返回 返回

react-Router的使用及原理講解和實現react-Router - 動態 詳情

react-router簡介

  • react-router包含3個庫,react-router、react-router-dom和react-router-native。
  • react-router提供最基本的路路由功能,實際使⽤的時候我們不會直接安裝react-router,⽽是根據應⽤運行的環境選擇安裝 react-router-dom(在瀏覽器器中使⽤)或react-router-native(在rn中使⽤)。
  • react-router-dom和 react-router-native都依賴react-router,所以在安裝時,react-router也會自動安裝,

react-Routerg(中文官網):http://react-router.docschina...

創建web應用的使用:

 yarn add react-router-dom

react-Router的基本使用

import React,{Component} from 'react'
import { BrowserRouter as Router,Route, Link, Switch} from "react-router-dom";

import HomePage from './pages/HomePage'
import LoginPage from './pages/LoginPage'
import UserPage from './pages/UserPage'
import _404Page from './pages/_404Page'

export default function App(){
  return(
    <div className="app">
      <Router>
        <Link to='/'>
          首頁
        </Link>
        <Link to='/user'>
          用户中心
        </Link> 
        <Link to='/login'>
          登陸
        </Link>
        <Link to="/product/123">
          商品
        </Link>

        <Switch>
         {/* 沒有swtich  就會把匹配到的進行現實*/}
            // exact精確的
           <Route exact path="/" 
           children={()=><div>HomePage-children</div>}
           component={HomePage}
           render={()=><div>HomePage-render</div>}
           ></Route>
           {/* 優先級順序 */}
           // 三個都存在,只渲染一個;都可以獲取到`router props`
           //  children 不管location 是否匹配了,都會現實
           {/* children>componentrender */}
           <Route path="/user" component={UserPage}></Route>
           <Route path="/login" component={LoginPage}></Route>
           <Route component={_404Page}></Route>
        </Switch>           
      </Router>
    </div>
  )
}
當 Switch 標籤 沒有包裹需要渲染的Route組建時,如果又一個路由 寫了children 渲染方式,那麼每一個路徑都會渲染這個children;
當然這個404 也會這樣一直存在。
Switch 用於渲染與路徑匹配的第一個<Route> 或 <Redirect>
children 被使用的場景,比如導航、菜單,每次都需要被渲染出來。當然一般導航 我們用組建複合比較多。children 也是一種可以實現的方式。

component: component

合理使用 children、component、render
import React, {Component, useEffect} from "react"; 
import {BrowserRouter as Router, Route} from "react-router-dom"; 

export default class RouteComponentPage extends Component {
 constructor(props) { 
  super(props);
  this.state = { count: 0 };
}  
render() {
    const {count} = this.state;
    return ( 
      <div>
        <h3>RouteComponentPage</h3>
        <button 
          onClick={() => {
            this.setState({count: count + 1}); 
        }}>
          click change count {count}
        </button>
        <Router> 
          {/* 渲染component的時候會調⽤用React.createElement,如果使⽤用下⾯面這種匿匿名函數的 形式,每次都會⽣生成⼀一個新的匿匿名的函數,
導致⽣生成的組件的type總是不不相同,這個時候會產⽣生重複的卸載和掛載 */}
          {/* 錯誤舉例例 觀察下child的didMount和willUnmount函數 */} 
          {/* <Route component={() => <Child count={count} />} />  
          <Route component={() => <FunctionChild count={count} />} /> */}
          {/* 下⾯面才是正確的示範 */}  
          {/* <Route render={() => <Child count={count} />} />  */}
          <Route render={() => <FunctionChild count={count} />} />
          {/* children 呢 */}  
          {/* <Route children={() => <Child count={count} />} /> */}
           <Route children={() => <FunctionChild count={count} />} />
            </Router>
        </div> 
    ); 
    }
}

class Child extends Component {
    componentDidMount(){
      console.log("componentDidMount") //sy-log
    } 
    componentWillUnmount() {
      console.log("componentWillUnmount")//sy-log 
    }
 render() {
    return <div>child-{this.props.count}</div>; 
    } 
  }

function FunctionChild(props) {
    useEffect(() => { 
      return () => {
          console.log("FunctionChild-WillUnmount"); //sy-log
        }; 
    }, [])
    return (<div>child-{props.count}</div>)
}
  • 當我們去執行

    <Route component={() => <Child count={count} >/>} /> 
    <Route component={() => <FunctionChild count={count} />} />
首次加載的時候會執行, Child 裏面的componentDidMount
WX20200713-184447@2x.png
  • 當我們點擊按鈕疊加時

WX20200713-184624@2x.png

無論時函數組建,還是class 組建 都會出現,頻繁加載componentDidMountcomponentWillUnmount
這是十分消耗性能的
所以我們最好時使用 render 和 child來加載組建
我們需要合理選用:

route在沒有swtich的情況下,匹配到才進行渲染,我們選擇render和component,組建選擇 component,匿名函數選擇render

接下來我們看下三種渲染方式時如何執行的

三種渲染方式的執行

Rputer 核心渲染源碼:

return(
  <RouterConetxt.Provider value={props}>
    {props.match // match的情況下
      ? children // 先判斷 children 是否匹配
         // children 的數據類型: fn, 對象, 數組
        ? typeof children === 'function' //  如果是fn 
          ? __DEV__
            ? evalChilderDev(children,props,this.props.path)
            :children(props) // 執行fn 函數
          :children // children 存在,但是不是fn;組建複合的形式存在,就直接渲染children
        : component // 如果沒有children;判斷component, 優先級第二
        ? React.createElement(component, props) // component 存在,使用React.createElement(),渲染當前的組建
        : render // component 也不存在,最後判斷render
        ? render(props) // render 存在,直接執行
        : null // 都不存在 返回null
      : typeof children === 'function' // 不match,不匹配的情況直接看children是不是一個fn
      ? __DEV__
        ? evalChilderDev(children,props, this.props.path)
        :children(props) // 是fn 直接執行
      :null // 不是返回null
    }
  </RouterConetxt.Provider>
)

// 不管是否匹配都會渲染children,但是呢,如果不匹配只去渲染,children是fn的情況
  • 匹配porps.match; 三元表達式,首先匹配是否是children;
  • children 可以是function類型,在組建符合的情況下,children可以是單一的對象,還可以是一個數組;數組也被稱之為對象。

嚴格來説,children有三種結構:

  • 函數
  • 組建複合中的對象
  • 數組的形式

動態路由

<Link to="/product/123">
  商品
</Link>
<Route path="/product/:id" component={Product}></Route>
function Product(props){
  console.log('Product-props:', props)
  const {match} = props
  const {id} =match.params
   return <div>Product- id:{id}</div>
}

嵌套路由

<Link to="/product/123">
  商品
</Link>
// 用 render 或者 children 或者component 都是可以的,都能拿到props,我就是多些幾種方式而已
<Route path="/product/:id" children={(props)=><Product {...props}></Product>}></Route>
function Product(props){
  console.log('Product-props:', props)
  // useEffect(() => {
  //   // effect
  //   return () => {
  //     console.log('cleanup')
  //     // cleanup
  //   }
  // }, [])
  const {match} = props
  const {params,url} =match
  const {id} =params
   return <div>
      <h3>Product- id:{id}</h3>
      <div>
        <Link to={url+'/detail'}>詳情</Link>
        <Route path={url+'/detail'} component={Detail}/>
      </div>
    </div>
}
//Detail 商品詳情
function Detail(){
  return<div>
    <h4>
        詳情來了———————— Detail
    </h4>
   
  </div>
}

實現react-Router來了~~~

my-react-router-dom實現

我們只需要實現 BrowserRouter、Link、Route

1. 初步搭建

BrowserRouter.js

Router主要是分為:

  • BrowserRouter
  • HashRouter
  • MemoryRouter
  • NativeRouter
  • StaticRouter

Router的不同主要是history不同

// BrowserRouter ,組建複合,我們在使用的時候,也是在其中進行children。
import react,{Component} from 'react'
import {createBrowserHistory} from 'history' // 安裝了 react-router-dom ;就不需要在安裝history了;已經涵蓋
import Router from './Router'
// BrowserRouter 是基於Router來進行實現的
export default class BrowserRouter extends Component{
  constructor(props){
    super(props)
    this.history = createBrowserHistory()
  }
  render() {
    return(
      <div>
        <Router children={this.props.children}  history={this.history}/>
      </div>
    )
  }
}
children 是在使用BrowserRouter的時候,我們起了個別名Router;<Router>這裏面就是children內容</Router>
Router.js
import React,{Component} from 'react'

export default class Router extends Component{
  constructor(){
    super()
  }
  render(){
    const{ history, children} =this.props
    // 主要目的是返回children
    return  children
  }
}
Route.js
import React,{Component} from 'react'

export default class Route extends Component{
  constructor(){
    super()
  }
  render(){
    return(
      <div>
        Route
      </div>
    )
  }
}
Link.js
import React,{Component} from 'react'

export default class Link extends Component{
  constructor(){
    super()
  }
  render(){
    return(
      <div>
        Link
      </div>
    )
  }
}
目前頁面的展示
import {
  BrowserRouter as Router,
  Route, 
  Link, 
  // Switch
} from "./my-react-router-dom";

最後實現Switch

WX20200716-145846@2x.png


2. 完善

Router.js
import React,{Component} from 'react'
import {RouterContext} from './Context'
export default class Router extends Component{
  constructor(props){
    super(props)
    // 用於路由變化匹配path用的參數
    this.state={
      location: props.history.location
    }
    // 監聽history
    props.history.listen(location=>{
      // 改變了就修改location
      this.setState(location)
    })
  }
  render(){
    const{ history, children} =this.props
     console.log(history,'history')
    // 主要目的是返回children
    return  <RouterContext.Provider value={{history, location:this.state.location}}>
          {children}
      </RouterContext.Provider>
    ;
  }
}
創建 Context.js,來解決跨層級通訊

這麼沒有什麼要説明的,就是跨組建通訊

import React from 'react'

const RouterConetxt =React.createContext()
export {RouterConetxt}
Link.js

平時我們是如何使用Link

 <Link to='/'>
   首頁
 </Link>

Link組建

  • to
  • 首頁相當於children
import React,{Component} from 'react'

export default class Link extends Component{
  constructor(){
    super()
  }
  render(){
    const {to, children, ...restProps} = this.props
    return(
      <a href={to} {...restProps}>{children}</a>
    )
  }
}

處理link a標籤的默認事件

  • 去除默認事件,可以用點擊事件中添加“e.preventDefalut”
  • 去掉之後,那麼href事件就會被禁止,我們就需要手動去跳轉,命令式修改路由
  • 命令式: history.push(this.props.to)

考慮到兼容問題,不採用window.history; 其實在最開始,BrowserRouter中,我們往下穿了一個history;組建通訊,跨層級使用;這個時候我們可以考慮用context來進行傳遞下來,這樣其他的組建,比如Route也可以使用到history

import React,{Component} from 'react'
import { RouterConetxt } from './Conetxt';

export default class Link extends Component{
  // 引用
  static contextType = RouterConetxt
  constructor(){
    super()
  }
  handleClick=(e)=>{
    e.preventDefault();
    // 跳轉
    this.context.history.push(this.props.to)
  }
  render(){
    const {to, children, ...restProps} = this.props
    return(
      <a href={to} {...restProps} onClick={this.handleClick}>{children}</a>
    )
  }
}
Route.js
根據路由,匹配到對應的path,展示對應的組建內容

首先先渲染component

我們平時的使用
<Route path="/user" component={UserPage}></Route>
參數為:

  • path
  • component
import React,{Component} from 'react'
import { RouterContext } from './Context';

export default class Route extends Component{
  constructor(){
    super()
  }
  render(){
    return (
      <RouterContext.Consumer>
        {
          context=>{
            const {location} = context
            const {path,component} =this.props
            // 目前component 還不是一個組建,所以我們需要用到React.createElement() 來進行創建
            // 用來判斷,篩選到的路由進行展示
            // 如果用 window.location;只會首次渲染,只有state發生改變的時候才會重新render
            const match= location.pathname === path
            //為true 就展示,為false就返回null
            return match?React.createElement(component):null
          }
        }
      </RouterContext.Consumer>
    )
  }
}

WX20200716-153237@2x.png

Route.js 繼續完善,實現children、render的渲染

是我們最開始分析的 router的核心,一串三目表達式

// 將props 進行一個組合為的是更好的傳遞參數
const props={
...context,
location,
match
} 
// match 匹配到:優先級-children>component>render|| null
// match 不匹配到: children是function形式 || null
return match?
 (children?():())
  :
 (typeof children==='function'? children(props) : >null)
// match 匹配到:優先級-children>component>render|| null
            // match 不匹配到: children是function形式 || null
            return match?
              children?
                (typeof children==='function'?
                  // 是函數就直接執行
                  children(props)
                  // 組建複合
                  :children 
                )
                :
                (component?
                  (React.createElement(component,props))
                  :(render?render(props):null)
                )
            :
            (typeof children==='function'? children(props) : null)

404頁面展示現實

  • 在route頁面,我們做的match判斷是必須匹配,才會進行渲染;
  • 但是404頁面,在沒有switch 的情況下,應該是每個路由都會被渲染出來;沒有寫path值默認應該是匹配404頁面狀態;需要加一個默認的match值。
  • 在router中添加默認的match值,不寫path值,返回默認的match;
  • 接下來了我們會將mtch儲存為一個對象

源碼中,有一個matchpath.js的文件,主要是將match處理成一個對象

首先 Router.js 的修改

修改的部分

 // 默認match
 // 不寫match的情況下,默認返回 path:'/' 的對象
 // 這一段是直接從源碼中抄的
 static computeRootMatch(pathname){
   return {path:'/',url:'/',params:{}, isExact: >pathname==='/'}
 }
 render(){
   return  <RouterContext.Provider value={{ >match:Router.computeRootMatch(this.state.location.pathname)
     }}>
       {children}
     </RouterContext.Provider>
   ;
 }

完整版Router.js

import React,{Component} from 'react'
import {RouterContext} from './Context'
export default class Router extends Component{
  // 默認match
  // 不寫match的情況下,默認返回 path:'/' 的對象
  // 這一段是直接從源碼中抄的
  static computeRootMatch(pathname){
    return {path:'/',url:'/',params:{}, isExact: pathname==='/'}
  }
  constructor(props){
    super(props)
    // 用於路由變化匹配path用的參數
    this.state={
      location: props.history.location
    }
    // 監聽history
    props.history.listen(location=>{
      // 改變了就修改location
      this.setState({location})
    })
  }
  render(){
    const{ history, children} =this.props
    // 主要目的是返回children
    return  <RouterContext.Provider value={{
      history, 
      location:this.state.location,
      match:Router.computeRootMatch(this.state.location.pathname)
      }}>
        {children}
      </RouterContext.Provider>
    ;
  }
}
static的解釋
這裏涉及到了ES6的class,我們定義一個組件的時候通常是定義了一個類,而static則是創建了一個屬於這個類的屬性或者方法。
組件則是這個類的一個實例,component的props和state是屬於這個實例的,所以實例還未創建,我們又怎麼可能讀得到props和state呢?
總結來説static並不是react定義的,而加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用
-------百度搜索而來 ---------
matchPath.js
去源碼中找也是一樣的,如果我寫的這個版本不是最新的可以去源碼中找,但這個文件被修改和調整,目測可能性不太大。
import pathToRegexp from "path-to-regexp";

const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;

function compilePath(path, options) {
  const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
  const pathCache = cache[cacheKey] || (cache[cacheKey] = {});

  if (pathCache[path]) return pathCache[path];

  const keys = [];
  const regexp = pathToRegexp(path, keys, options);
  const result = { regexp, keys };

  if (cacheCount < cacheLimit) {
    pathCache[path] = result;
    cacheCount++;
  }

  return result;
}

/**
 * Public API for matching a URL pathname to a path.
 */
function matchPath(pathname, options = {}) {
  if (typeof options === "string" || Array.isArray(options)) {
    options = { path: options };
  }

  const { path, exact = false, strict = false, sensitive = false } = options;

  const paths = [].concat(path);

  return paths.reduce((matched, path) => {
    if (!path && path !== "") return null;
    if (matched) return matched;

    const { regexp, keys } = compilePath(path, {
      end: exact,
      strict,
      sensitive
    });
    const match = regexp.exec(pathname);

    if (!match) return null;

    const [url, ...values] = match;
    const isExact = pathname === url;

    if (exact && !isExact) return null;

    return {
      path, // the path used to match
      url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
      isExact, // whether or not we matched exactly
      params: keys.reduce((memo, key, index) => {
        memo[key.name] = values[index];
        return memo;
      }, {})
    };
  }, null);
}

export default matchPath;
Route.js 的修改

修改的部分

 // 用來判斷,篩選到的路由進行展示
 // match:首先判斷 path是否存在? 
 //   存在使用matchPath來進行正則匹配,兩個參數一個是:location.path、 this.props
 //   不存在 使用頂層傳進來的默認的match,context中的。 
const match= path? matchPath(location.pathname,this.props):context.match

完整版Route.js

import React,{Component} from 'react'
import { RouterContext } from './Context';
import matchPath from './matchPath';

export default class Route extends Component{
  constructor(){
    super()
  }
  render(){
    return (
      <RouterContext.Consumer>
        {
          context=>{
            // 這個是Router 使用contex傳進來的參數
            const {location} = context
            // 這個是組建調用傳進來的參數
            const {path,component,children,render} =this.props
            // 用來判斷,篩選到的路由進行展示
            // match:首先判斷 path是否存在? 
            //   存在使用matchPath來進行正則匹配,兩個參數一個是:location.path、 this.props
            //   不存在 使用頂層傳進來的默認的match,context中的。 
            const match= path? matchPath(location.pathname,this.props):context.match
            console.log('route-match', match)
            // 將props 進行一個組合為的是更好的傳遞參數
            const props={
              ...context,
              location,
              match
            } 
            
            // match 匹配到:優先級-children>component>render|| null
            // match 不匹配到: children是function形式 || null
            return match?
              children?
                (typeof children==='function'?
                  // 是函數就直接執行
                  children(props)
                  // 組建複合
                  :children 
                )
                :
                (component?
                  (React.createElement(component,props))
                  :(render?render(props):null)
                )
            :
            (typeof children==='function'? children(props) : null)
          }
        }
      </RouterContext.Consumer>
    )
  }
}
目前404頁面展示效果

頁面代碼

export default function App(){
  return(
    <div className="app">
      <Router>
        <Link to='/'>
          首頁
        </Link>
        <Link to='/user'>
          用户中心
        </Link> 
        <Link to='/login'>
          登陸
        </Link>
        <Link to="/product/123">
          商品
        </Link>

           <Route exact path="/" 
          //  children={()=><div>HomePage-children</div>}
          //  component={HomePage}
           render={()=><div>HomePage-render</div>}
           ></Route>
           <Route path="/user" component={UserPage}></Route>
           <Route path="/login" component={LoginPage}></Route>
           <Route component={_404Page}></Route>
      </Router>
    </div>
  )
}

WX20200717-175206@2x.png
WX20200717-175531@2x.png
WX20200717-175605@2x.png

現實 Switch 獨佔路由

組建複合
Switch 要做的是將children遍歷一遍,找到第一個匹配項之後展示
children的數據類型,可以有一個{}或者多個[]
初步搭建

import React, { Component } from 'react'

export default class Switch extends Component {
  render() {
    let match; // 找到匹配的元素,match設置為true
    let element; // 匹配的元素,沒有匹配到就沒有初始值

    // 還需要做的是查找到匹配到的元素
    // .........

    // 在Switch這塊,element 這塊已經是一個元素了。
    // 如果找到匹配的元素 ?就顯示elment,克隆一下是待會兒會加屬性 : null
    return match? React.cloneElement(element,{}):null
  }
}
import React, { Component } from 'react'
import matchPath from './matchPath';
import { RouterContext } from './Context';

export default class Switch extends Component {
  render() {
    return (<RouterContext.Consumer>
      {context=> {
        const {location} =context
        console.log('Switch=====location',location)
        let match; // 找到匹配的元素,match設置為true
        let element; // 匹配的元素,沒有匹配到就沒有初始值

        const {children}= this.props
        // 還需要做的是查找到匹配到的元素
        React.Children.forEach(children, child=>{
          // if條件 :match 我們最上面定義是undefined,所以用==;&& 有有效的element元素
          if(match==null &&React.isValidElement(child)){
            element= child
            const {path}= child.props
            // path路徑匹配到 ? matchPath(location, ...),這塊就需要用到Conetxt: 不匹配就用傳下來的默認match
            match = path? matchPath(location.pathname, child.props):context.match
          }
        })
        // 在Switch這塊,element 這塊已經是一個元素了。
        // 如果找到匹配的元素 ?就顯示elment,克隆一下是待會兒會加屬性 : null
        return match? React.cloneElement(element,{}):null
        }
      }
    </RouterContext.Consumer>)
  }
}
React.children.forEarch 介紹: https://zh-hans.reactjs.org/d...
WX20200718-190852@2x.png
調整 Route

src/my-react-router-dom/Route.js

// computedMatch 是從Switch裏面傳進來的,用來match判斷,優先使用
   const {path,component,children,render,computedMatch} =this.props
// 用來判斷,篩選到的路由進行展示
// match:首先判斷computedMatch  在判斷path是否存在?
//   存在使用matchPath來進行正則匹配,兩個參數一個是:location.path、 this.props
//   不存在 使用頂層傳進來的默認的match,context中的。 
    const match= computedMatch
    ?computedMatch
    :path
    ? matchPath(location.pathname,this.props)
    :context.match

my-react-router-domhooks方法的實現

例子
router中

 <Route path="/product/:id" children={(props)=><Product {...props}></Product>}></Route>

如果我們需要在函數組建中,用到props,我們只能這樣去使用,進行參數的傳遞

Product

function Product(props){
 console.log('Product-props:', props)
 const {match} = props
 const {params,url} =match
 const {id} =params
  return <div>
     <h3>Product- id:{id}</h3>
     <div>
       <Link to={url+'/detail'}>詳情</Link>
       <Route path={url+'/detail'} component={Detail}/>
     </div>
   </div>
}

如果我們不進行props參數的傳遞,可以使用hooks的方法來獲取到

<Route path="/product/:id" children={()=><Product></Product>}></Route>
import {
useRouteMatch,
useHistory,
useParams,
useLocation
} from "react-router-dom";
function Product(props){
  const match =useRouteMatch()
  const history =useHistory()
  const location =useLocation()
  const Params = useParams() // 參數
  console.log('match:',match);
  console.log('history:',history);
  console.log('location:',location);
  console.log('Params:',Params);
}

WX20200718-221019@2x.png

hook.js

index.js 調整

src/my-react-router-dom/index.js

import BrowserRouter from './BrowserRouter'
import Route from './Route'
import Link from './Link'
import Switch from './Switch'
import {
  useLocation,
  useRouteMatch,
  useParmas,
  useHistory
} from './hook'
export {BrowserRouter, Route, Link,Switch,useLocation,useRouteMatch,useParmas,useHistory }
src/my-react-router-dom/hook.js
// 就是History對象
export function useHistory(){

}
// useLocation: 
// {pathname: "/product/123", search: "", hash: "", state: undefined, key: "y14yf2"}
// hash: ""
// key: "y14yf2"
// pathname: "/product/123"
// search: ""
// state: undefined
// }
export function useLocation(){

}

// match: 
// {path: "/product/:id", url: "/product/123", isExact: true, params: {…}}
// isExact: true
// params: {id: "123"}
// path: "/product/:id"
// url: "/product/123"
// }
export function useRouteMatch(){

}

// useParams:
// {id: "123"}
export function useParams(){

}
useHistory
// 就是History對象
export function useHistory(){
  // 返回的就是history對象
  // 在BrowserRouter中,我們使用import {createBrowserHistory} from 'history';const history= createBrowserHistory() 
  // 在hook對象中,我們要使用 usecontext;
  return context = useContext(RouterContext).history;
}
useLocation
export function useLocation(){
  return useContext(RouterContext).location;
}
我們現在打印看下:
WX20200718-225903@2x.png
這裏,useParams沒有進行展示,而在match是展示的,我們默認的參數,我們寫的默認值,也就是説,當match進行修改了之後,context中的match沒有進行修改。所以我們需要在修改match的地方進行context中重新賦值。
Route.js 調整

src/my-react-router-dom/Route.js

return(
  <RouterContext.Provider value={props}>
    {
      match?
      children?
        (typeof children==='function'?
          // 是函數就直接執行
          children(props)
          // 組建複合
          :children 
        )
        :
        (component?
          (React.createElement(component,props))
          :(render?render(props):null)
        )
    :
    (typeof children==='function'? children(props) : null)                  
    }
  </RouterContext.Provider>
)
又包了一層RouterContext,在使用useParams時,往上找參數,當找到RouterContext 就會停止在往上查找。
打印內容:

WX20200718-231416@2x.png

到這裏react——router的hook方法就已經就已經實現了。


現在我們思考一個問題,如果是class組建在不傳遞props的情況下如何實現,在class組建內獲取props

class Product extends Component{
  render() {
    // const {id} = this.props
    console.log(this.props,'props')
    return<div>
      {/* <h3>Product- id:{id}</h3> */}
      <h3>Product- id</h3>
    </div>
  }
}

react-router中有一個高階組建,withRouter
接下來我們來實現下withRouter

實現withRouter

src/my-react-router-dom/withRouter.js

// 高階組建
import React from 'react'
import {RouterContext} from './Context'
const withRouter= WrappendComponent=>props=>{ 
  // 需要用到context 可以傳遞location match 等參數;因為在context中有記錄
  return <RouterContext.Consumer>
      {context=><WrappendComponent {...props} {...context}></WrappendComponent>}      
    </RouterContext.Consumer>
}

export default withRouter

實現prompt

首先我們來使用一下

class Product extends Component{
  constructor(){
    super()
    this.state={
      cofirm:true,
    }
  }
  change=()=>{
    this.setState({
      cofirm: !this.state.cofirm
    })
  }
  render(){
    console.log('this.state.cofirm',this.state.cofirm)
    return(
      <div>
        <h3>Product</h3>
        <button onClick={this.change}>change</button>
        <Prompt when={this.state.cofirm} message="確定要離開這個頁面嗎?"></Prompt>
      </div>
    )
  }
}

WX20200720-150510@2x.png

Prompt 是react-router 的方法;
屬性 when:為true,跳轉其他頁面時,會出現彈窗提示
屬性 message:彈窗中的消息

src/my-react-router-dom/Prompt.js

import React from 'react'
import { RouterContext } from './Context';
import LifeCycle from './LifeCycle'
// 接收兩個參數
// when 是一個Boolean
// message 是一個String|| function
export default function Prompt({when=true,message}){
  // 我們需要用到histroy,path來判斷跳轉,所以用到context
  return(
    <RouterContext.Consumer>{
      context=>{
        // 當首次進來時,when是true;history.block方法已經掛載在組建上。
        // 當設置為false的時候,history.block還會執行們因為沒有卸載。所以還需要在LifeCycle中進行卸載
        if(!when){
          return null
        }
        const method = context.history.block
        console.log('method:',method)
        // render返回組件必須是<Component/>,所以不能直接寫 return context.history.block;需要用到LifeCycle
        //在這裏假設可以接收到LifeCycle的this,參數self,
        return <LifeCycle onMount={
          (self)=>{
            //設置一個方法release,
            self.release=method(message)
          }}
          onUnmount={(self)=>{
            self.release()
          }}
        ></LifeCycle>
      }
    }</RouterContext.Consumer>
  )
}

src/my-react-router-dom/LifeCycle.js

import React, { Component } from 'react'

export default class LifeCycle extends Component {
  // 掛載
  componentDidMount(){
    // 當前的方法都定義在this裏面,
    console.log('componentDidMount',this)
    if(this.props.onMount){
      this.props.onMount.call(this,this)
    }
  }
  // 取消掛載
  componentWillUnmount(){
    console.log('componentWillUnmount', this)
    if(this.props.onUnmount){
      this.props.onUnmount.call(this,this)
    }
  }
  render() {
    return null
  }
}

Add a new 評論

Some HTML is okay.