react-router4-training

前言

如今搭建项目通常都是使用脚本手架,其次项目完成搭建以后一些基础的配置信息以及配置如何生效的,对于普通开发人员来讲不太接触的到,相似react-router的配置,长时间不配置的容易健忘html

因此写一篇文章记录下react-router的各类功能使用,主要是根据reacttraning网站的菜单目录来开发的各个demo,以便于全面的理解react-router使用的知识点前端

先提几个问题

文章开始以前,先留几个问题,能够边看demo边理解:react

  • react/react-router同构场景下的实现?
  • 路由代码拆分如何实现?
  • 如何实现自定义React lazy以及Suspense?
  • 如何实现一个切换路由以后滚动到顶部的组件?
  • react-router如何跟react-redux整合?
  • 路由hooks的使用?

全部的DEMO我集中放在了一个文件夹下,戳DEMO地址查看git

DEMO演示及讲解

DEMO1.搭建简单的react-router

DEMO地址github

import { BrowserRouter, Route, Link, Switch } from 'react-router-dom'

<BrowserRouter>
    <ul style={{listStyle: 'none'}}>
      <li>
        <Link to="/dashboard">dashboard</Link>
      </li>
    </ul>
    <Switch>
      <Route path="/dashboard">
        <Dashboard />
      </Route>
      <Route path="/">
        <Dashboard />
      </Route>
    </Switch>
</BrowserRouter>
复制代码

简单的react-router-demo就搭建完成了。那么有几个问题?web

  • Switch是干吗用的?
  • Route没有匹配到的话,如何配置404页面?
  • 根路径的配置须要注意什么?

下面来解答下这些疑问redux

Switch是干吗用的?

问题场景:bash

咱们通常都须要定义一个根路径的路由,可是根路径的路由若是只是这样写的话,会致使输入路由 /dashboard 的时候 渲染了两个Dashboardreact-router

⚠️⚠️⚠️ Switch的做用就来了,Switch的做用是只渲染第一个匹配到的路由的项,解决了以上重复渲染的问题dom

那其实还有一种解决方案,放在下面【根路径的配置须要注意什么?】去解释

根路径的配置须要注意什么?

Route下面有个exact属性,exact属性为true时,表示精确匹配到/才会进根路径的Component,否则/dashboard路由也会同时匹配到/路由

<Route path="/" exact>
复制代码
Route没有匹配到的话,如何配置404页面?

不要配置path,当找不到路由配置时就会走这个

<Route>
    <div>404</div>
</Route>
复制代码

DEMO2.同构场景下的react-router使用

DEMO地址

原理

这个涉及到同构的概念,客户端和服务端的结构不同

App:能够理解这是个Route集合,服务端和客户端通用

服务端: ReactServerDom + StaticRouter + App

  • react服务端渲染须要用到 react-dom/server 的 renderToString 转成字符串传给nunjucks模版引擎去渲染
  • 服务端没有dom,因此BrowserRouter会报错,因此须要换成StaticRouter

客户端:ReactDom + BrowserRouter + App

  • 客户端须要注意的是,同构
  • 其次服务端和客户端渲染的时候StaticRouter和BrowserRouter的区别,我这里是采用不一样入口的方式解决的

涉及react-router使用的源码实现以下:

服务端的实现
import ReactServerDom from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from '../client/App'

const markup = ReactServerDom.renderToString(
    <StaticRouter location={ctx.req.url}>
        <App />
    </StaticRouter>
)
复制代码
客户端的实现
import React from 'react'
import ReactDom from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDom.render(<BrowserRouter>
    <App />
</BrowserRouter>, document.getElementById('root'))
复制代码
App.js的实现
import React from 'react';
import { Switch, Route, Link } from 'react-router-dom'
import Dashboard from './router/dashboard'
import Home from './router/home/index'

function App() {
  return (
    <React.Fragment>
        <div style={{width: 200, float: 'left', listStyle: 'none'}}>
            <h3>菜单</h3>
            <ul style={{listStyle: 'none'}}>
              <li>
                <Link to="/dashboard">dashboard</Link>
              </li>
              <li>
                <Link to="/home">home</Link>
              </li>
            </ul>
          </div>
          <div style={{paddingLeft: 200}}>
            <Switch>
              <Route path="/dashboard">
                <Dashboard />
              </Route>
              <Route path="/home">
                <Home />
              </Route>
              <Route path="/">
                <Dashboard />
              </Route>
            </Switch>
        </div>
    </React.Fragment>
  );
}

export default App;
复制代码
实现的过程当中碰到了几个问题

koa-nunjucks-2 html被渲染成了字符串

解决方案 autoescape: false

同构作完以后发现路由切换仍是发起了请求,而且是404,如何解决?

这实际上是由于客户端的同构没有生效,检查客户端代码,保证注入的client.js可以正常运行,路由切换就不会再发起请求了

同构实现正确,页面出现以后,其实页面的控制权已经交到前端js即BrowserRouter的手中,因此后续的history切换跟服务端没有关系了

DEMO3.路由代码拆分

DEMO地址

路由代码拆分有三种方式:

  • @loadable/component
  • react-loadable
  • react lazy Suspense
@loadable/component的实现
import loadable from '@loadable/component'

const Home = loadable(() => import('./router/home'))
const Dashboard = loadable(() => import('./router/dashboard'))
复制代码
react-loadable的实现
import loadable from 'react-loadable'

const Loading = () => <div>loading</div>
const Dashboard = loadable({
  loader: () => import('./router/dashboard'),
  loading: Loading,
});
const Home = loadable({
  loader: () => import('./router/home'),
  loading: Loading,
});
复制代码
react lazy Suspense的实现
import { Switch, Route, Link } from 'react-router-dom'

const Home = lazy(() => import('./router/home'))
const Dashboard = lazy(() => import('./router/dashboard'))
复制代码
扩展实现myReactLazy

实现一个Suspense.js和lazy.js,功能相似react原生的suspense,lazy

使用以下

<!--注意:这是伪代码,要执行的话参考demo地址,或者本身修改下️-->
import Suspense from './suspense'
import lazy from './lazy'

const Dashboard = lazy(() => import('./router/dashboard'))
const Home = lazy(() => import('./router/home'))

<Suspense fallback={<div>loading</div>}>
    <div style={{paddingLeft: 200}}>
        <Switch>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
    </div>
</Suspense>
复制代码

两个js的实现以下

Suspense.js

import React from 'react'
import PropTypes from 'prop-types'

export default class Suspense extends React.Component{
    getChildContext(){
        return {
            fallback: this.props.fallback || <div>loading...</div>
        }
    }

    render(){
        return this.props.children
    }
}

Suspense.childContextTypes = {
    fallback: PropTypes.object
}
复制代码

lazy.js

import React from 'react'
import PropTypes from 'prop-types'

export default load => {
    class Lazy extends React.Component{
        state = { Comp: null }

        componentDidMount(){
            // 加个定时器,看下fallback的效果
            setTimeout(() => {
                load().then(module => {
                    this.setState({
                        Comp: module.default
                    })
                })
            }, 3000)

            // load().then(module => {
            //     this.setState({
            //         Comp: module.default
            //     })
            // })
        }
        
        render(){
            let { Comp } = this.state
            return Comp ? <Comp /> : this.context.fallback
        }
    }

    Lazy.contextTypes = {
        fallback: PropTypes.object
    }

    return Lazy
}
复制代码

⚠️这里有个注意点是:Suspense和lazy加载的组件非直接父子组件关系,因此这里采用context实现

DEMO4.当有修改时如何阻止路由跳转

DEMO地址

import { Prompt } from 'react-router-dom'
<Prompt
    when={isBlocking}
    message={location => `Are you sure you want to go to ${location.pathname}`
} />
复制代码

其实就是利用了Prompt组件,when为true时拦截,为false时放行

DEMO5.路由切换滚动到页面顶部

DEMO地址

这个DEMO解决的问题

有些时候在一个路由下操做,而后滚动到下面,致使顶部内容看不到了,此时切换菜单,但是页面没有自动滚动到顶部

第一种实现方案

使用withRouter把history,location,match塞入ScrollToTop组件的props中,当路由切换以后进入ScrollToTop的componentDidUpdate事件,在这个事件中window.scrollTo(0, 0)

ScrollToTop组件定义

class ScrollToTop extends React.Component{
  componentWillUpdate(prevProps){
    if(this.props.location.pathname != prevProps.location.pathname){
      window.scrollTo(0, 0)
    }
  }

  render(){
    return this.props.children
  }
}
复制代码

ScrollToTop组件的使用

<Router>
  <ScrollToTopComp>
    <!--...其余代码-->
  </ScrollToTopComp>
</Router>
复制代码
第二种实现方案

改进方案是使用hooks的useEffect
ScrollToTop组件定义

import { useEffect } from 'react'
function ScrollToTop({children, location: { pathname }}){
    useEffect(() => {
        window.scrollTo(0, 0)
    }, [pathname])

    return children
}

const ScrollToTopComp = withRouter(ScrollToTop)
复制代码

ScrollToTop组件的使用和第一种方案同样

第三种实现方案

第三种方案是在每一个路由页面加一个ScrollToTop组件,在ScrollToTop的componentDidMount事件中加window.scrollTo(0, 0)

这个方案就不具体演示代码了

DEMO6.redux整合

DEMO地址

⚠️为何会有整合的问题❓❓❓

-> react-router:withRouter(Home)   
-> react-redux:connect(state => {   
    return { name: state.name }   
})(Home)   
复制代码

两个都须要再次封装Home,那么怎么办❓❓❓

解决方案一
connect(state => {
    return { name: state.name } 
})(withRouter(Home))
复制代码
解决方案二
<Route path="/home" component={Home} />

connect(state => {
    return { name: state.name }
})(Home)
复制代码

DEMO7.路由Hooks

DEMO地址

import { useLocation, useHistory, useParams, useRouteMatch } from 'react-router'

const location = useLocation()
const history = useHistory()
const params = useParams()
const match = useRouteMatch()

就能够获取到各个对象了
复制代码

ok,到这里差很少把reacttraining的功能都用demo实现了一遍,可是没有具体解释例如Route的属性的含义等,这篇demo文章就看成是对react-router的复习

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息