深入了解 React Router 原理

说到 React 我们一定离不开和 Router 打交道。不管 Vue Router 和 React Router ,他们的原理都是差不多的。这篇文章会从一个简单的例子一直拓展到真正的 React Router。

什么是路由

路由(routing)是指分组从源到目的地时,决定端到端路径的网络范围的进程

上面就是百度百科对路由的定义。比如我想去某个地方,那这个东西就带我去那个地方,这个东西就叫路由。

一个例子

先来说说需求,假设我们有两个组件 Login 和 Register,和两个对应的按钮。点击 Login 按钮就显示 Login 组件,点击 Register 显示 Register 组件。

我们这里使用 Hooks API 来创建 App 组件的自身状态。UI 代表了当前显示的是哪个组件的名字。

function Login() {
  return <div>Register</div>;
}

function Register() {
  return <div>Login</div>;
}

function App() {
  let [UI, setUI] = useState('Login');
  let onClickLogin = () => {
    setUI('Login')
  }
  let onClickRegister = () => {
    setUI('Register') 
  }
  let showUI = () => {
    switch(UI) {
      case 'Login':
        return <Login/>
      case 'Register':
        return <Register/>
    }
  }
  return (
    <div className="App">
      <button onClick={onClickLogin}>Login</button>
      <button onClick={onClickRegister}>Register</button>
      <div>
          {showUI()}
      </div>
    </div>
  );
}

这个其实就是路由的雏形了,每个页面对应着一个组件,然后在不同状态下去切换 。

使用 hash 来切换

当然我们更希望看到的是

不同 url -> 不同页面 -> 不同组件

我们先用 url 里的 hash 做尝试:

  1. 在进入页面的时候获取当前 url 的 hash 值,根据这个 hash 值去更新 UI 从而通过 showUI() 来切换到对应的组件
  2. 同时添加 onClick 事件点击不同按钮时,就在 url 设置对应的 hash,并切换对应的组件

这时候组件 App 可以写成这样:

function App() {
  // 进入页面时,先初始化当前 url 对应的组件名
  let hash = window.location.hash
  let initUI = hash === '#login' ? 'login' : 'register'

  let [UI, setUI] = useState(initUI);
  let onClickLogin = () => {
    setUI('Login')
    window.location.hash = 'login'
  }
  let onClickRegister = () => {
    setUI('Register') 
    window.location.hash = 'register'
  }
  let showUI = () => {
    switch(UI) {
      case 'Login':
        return <Login/>
      case 'Register':
        return <Register/>
    }
  }
  return (
    <div className="App">
      <button onClick={onClickLogin}>Login</button>
      <button onClick={onClickRegister}>Register</button>
      <div>
          {showUI()}
      </div>
    </div>
  );
}

这样其实已经满足我们的要求了,如果我在地址栏里输入 localhost:8080/#login,就会显示 <Login/>。但是这个 “#” 符号不太好看,如果输入 localhost:8080/login 就完美了。

使用 pathname 切换

如果要做得像上面说的那样,我们只能用 window.location.pathname 去修改 url 了。只要把上面代码里的 hash 改成 pathname 就好了,那么组件 App 可以写成这样:

function App() {
  // 进入页面时,先初始化当前 url 对应的组件名
  let pathname = window.location.pathname
  let initUI = pathname === '/login' ? 'login' : 'register'

  let [UI, setUI] = useState(initUI);
  let onClickLogin = () => {
    setUI('Login')
    window.location.pathname = 'login'
  }
  let onClickRegister = () => {
    setUI('Register') 
    window.location.pathname = 'register'
  }
  let showUI = () => {
    switch(UI) {
      case 'Login':
        return <Login/>
      case 'Register':
        return <Register/>
    }
  }
  return (
    <div className="App">
      <button onClick={onClickLogin}>Login</button>
      <button onClick={onClickRegister}>Register</button>
      <div>
          {showUI()}
      </div>
    </div>
  );
}

但是这里有个问题,每次修改 pathname 的时候页面会刷新,这是完全不符合我们的要求的,还不如用 hash 好。

使用 history 切换

幸运的是 H5 提供了一个好用的 history API,使用 window.history.pushState() 使得我们即可以修改 url 也可以不刷新页面,一举两得。

现在只需要修改点击回调里的 window.location.pathname = 'xxx' 就可以了,用 window.history.pushState() 去代替。

function App() {
  // 进入页面时,先初始化当前 url 对应的组件名
  let pathname = window.location.pathname
  let initUI = pathname === '/login' ? 'login' : 'register'

  let [UI, setUI] = useState(initUI);
  let onClickLogin = () => {
    setUI('Login')
    window.history.pushState(null, '', '/login')
  }
  let onClickRegister = () => {
    setUI('Register') 
    window.history.pushState(null, '', '/register')
  }
  let showUI = () => {
    switch(UI) {
      case 'Login':
        return <Login/>
      case 'Register':
        return <Register/>
    }
  }
  return (
    <div className="App">
      <button onClick={onClickLogin}>Login</button>
      <button onClick={onClickRegister}>Register</button>
      <div>
          {showUI()}
      </div>
    </div>
  );
}

到此,一个 Router 就已经被我们实现了。当然这个 Router 功能不多,不过这就是 Vue Router 和 React Router 的思想,他们是基于此来开发更多的功能而已。

约束

在前端使用路由要有个前提,那就是后端要将全部的路径都指向首页,即 index.html。否则后端会出现 404 错误。

什么叫全部路径都指向首页呢?我们想一下正常的多页网页是怎么样的:如果访问了一个不存在的路径,如 localhost:8080/fuck.html,那么后端会返回一个 error.html,里面内容显示 “找不到网页”,这种情况就是后端处理网页的路由了。因为正是后端根据不同 url 返回不同的 xxx.html 呀。

如果前端使用路由,那么后端将全部路径都指向 index.html当我们访问到一个不存在路径时,如 localhost:8080/fuck,后端不管三七二十一返回 index.html。但是这个 index.html 里有我们写的 JS 代码(React 打包后的)呀,这 JS 代码其中就包含了我们做的路由。所以我们的路由发现不存在这个路径时,就切换到 Error 组件来充当 “找不到网页” 的 HTML 文件。这就叫前端控制路由。

React Router

现在我们使用 React Router 来重写这个代码。看官网的时候一定要看这个官网,别看 Github 的那个,因为那个是 v3.0 的。

react-router 和 react-router-dom

先说说这两个鬼的不同。当我还是先手的时候总是被这两个东西搞蒙。

react-router: 实现了路由的核心功能。
react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能。

说到底就是 react-router-domreact-router 的加强版呗。那为毛不两个合在一起呢?像 Vue Router 那样多好。因为 React Native 也要路由系统呀。所以还有一个库叫 react-router-native,这个库也是基于 react-router 的,它类似 react-router-dom,加入了 React Native 运行环境下的一些功能。关系图如下:

关系图

所以我们写网站的时候一般引入 react-router-dom 就可以了。

重构

使用了 React Router 之后代码就可以精简成下面这样了。

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

function Login() {
  return <div>Register</div>;
}

function Register() {
  return <div>Login</div>;
}

function App() {
  return (
    <Router>
        <div className="App">
            <Link to="/login">Login</Link>
            <Link to="/register">Register</Link>

            <Route path="/login" component={Login}></Route>
            <Route path="/register" component={Register}></Route>
        </div>
    </Router>
  );
}

可以看到 React Router 帮我们做了很多的事。比如正则的匹配,路由的切换等等。更多的用法就看上面的那个官网就可以了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容