motainzhang

motainzhang

react-router - 实践系列

57
2019-02-20
react-router - 实践系列

简单使用

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

const Home = () => (
  <div>
    <h2>主页</h2>
    <Link to="/article/1">文章1</Link>
  </div>
)

const Mine = () => <h2>我的</h2>

class Article extends Component {
  handleClick = () => {
    // 坑点: this.props.history.push('home') 会跳转到路径 /article/home
    this.props.history.push('/home')
  }

  render() {
    const { match } = this.props
    return (
      <div>
        <h2>文章</h2>
        <ul>
          <li>路由传参: {match.params.topicId}</li>
        </ul>
        <button onClick={this.handleClick}>go Home</button>
      </div>
    )
  }
}

/**
 * 坑点1: You should not use <Link> outside a <Router> @desc Link 组件不要在 Router 组件外使用
 * 坑点2: A <Router> may have only one child element @desc Router 组件只能有一个子元素
 * 坑点3: /article/:topicId 匹配不到 /Article 组件, /article/123 可以
 */
class App extends Component {
  render() {
    return (
      <div>
        <Router>
          <div>
            <NavLink to="/" activeClassName="active">
              首页
            </NavLink>
            <NavLink to="/article" activeClassName="active">
              文章
            </NavLink>
            <NavLink to="/mine" activeClassName="active">
              我的
            </NavLink>
            <hr />
            <Switch>
              <Route exact path="/" component={Home} />
              <Route path="/article/:topicId" component={Article} />
              <Route path="/mine" component={Mine} />
            </Switch>
          </div>
        </Router>
      </div>
    )
  }
}

export default App

url 跳转 + 传参

路由跳转

<Route path="/article/:topicId" component={Article} />

// html
<Link to="/article/1">文章1</Link>

// js 方式
this.props.history.push('/article/1')

// Article 组件 接收参数
this.props.match.params.topicId

通过 query

前提:必须由其他页面跳过来,参数才会被传递过来

// 不需要配置路由表。路由表中的内容照常
<Route path="/article" component={Article} />

// html
<Link to={{ pathname: '/article', query: { topicId: 2 } }}>文章2</Link>

// js 方式
this.props.history.push({ pathname : '/article' ,query : { topicId: 2} })

// Article 组件 接收参数
this.props.location.query.topicId //建议不用 刷新页面时 丢失

通过 state

query 差不多,只是属性不一样,而且 state 传的参数是加密的,query 传的参数是公开的,在地址栏

// 不需要配置路由表。路由表中的内容照常
<Route path="/article" component={Article} />

// html
<Link to={{ pathname: '/article', state: { topicId: 2 } }}>文章2</Link>

// js 方式
this.props.history.push({ pathname: '/article', state: { topicId: 2 } })

// Article 组件 接收参数
this.props.location.state.topicId

重定向

// 通过from匹配路由重定向
<Switch>
  <Redirect from="/users/:id" to="/users/profile/:id" />
  <Route path="/users/profile/:id" component={Profile} />
</Switch>

// 通过render重定向
<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>

Redirect(Auth)

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
  Redirect,
  withRouter
} from 'react-router-dom'

const fakeAuth = {
  isAuthenticated: false,
  authenticate(cb) {
    this.isAuthenticated = true
    setTimeout(cb, 100) // fake async
  },
  signout(cb) {
    this.isAuthenticated = false
    setTimeout(cb, 100)
  }
}

/**
 * @class Login - 登录组件
 * 接受参数 this.props.location.state.from
 * 如果登录成功 redirectToReferrer = true, 则跳转回之前的页面 <Redirect to={from} />
 */
class Login extends Component {
  state = { redirectToReferrer: false }

  login = () => {
    fakeAuth.authenticate(() => {
      this.setState({ redirectToReferrer: true })
    })
  }

  render() {
    let { from } = this.props.location.state || { from: { pathname: '/' } }
    let { redirectToReferrer } = this.state

    if (redirectToReferrer) return <Redirect to={from} />

    return (
      <div>
        <p>You must log in to view the page at {from.pathname}</p>
        <button onClick={this.login}>Log in</button>
      </div>
    )
  }
}

/**
 * @class AuthRoute - 权限高阶路由组件
 * 通过 fakeAuth.isAuthenticated 控制是否重定向到 /login
 * 传递参数 { from: props.location }
 */
const AuthRoute = ({ component: Component, ...rest }) => (
  <Route
    {...rest}
    render={props =>
      fakeAuth.isAuthenticated ? (
        <Component {...props} />
      ) : (
        <Redirect
          to={{
            pathname: '/login',
            state: { from: props.location }
          }}
        />
      )
    }
  />
)

/**
 * @func AuthStatus - 显示登录状态的组件
 * @desc 通过 withRouter 包裹,获得 this.props.history 用于跳转
 *       登录成功,显示 login succeeds,并显示注销按钮
 *       未登录,显示 You are not logged in.
 */
const AuthStatus = withRouter(
  ({ history }) =>
    fakeAuth.isAuthenticated ? (
      <p>
        login succeeds
        <button
          onClick={() => {
            fakeAuth.signout(() => history.push('/'))
          }}>
          Sign out
        </button>
      </p>
    ) : (
      <p>You are not logged in.</p>
    )
)

class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Link to="/auth">Auth Page</Link>
          <AuthStatus />
          <Route path="/login" component={Login} />
          <AuthRoute path="/auth" component={() => <h2>Auth page</h2>} />
        </div>
      </Router>
    )
  }
}

export default App

NotFound

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

class App extends Component {
  render() {
    return (
      <Router>
        <Switch>
          <Route exact path="/" component={() => <h2>Home</h2>} />
          <Route component={() => <h2>404, not found</h2>} />
        </Switch>
      </Router>
    )
  }
}
export default App

何时使用 Switch

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

// 输入地址 /article 可以发现 两个组件同时都被命中,这是我们不希望出现的
// 这个时候可以使用Switch,他只会命中第一个命中的路由
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path="/article" component={() => <p>article</p>} />
          <Route path="/:name" component={() => <p>:name</p>} />
        </div>
      </Router>
    )
  }
}
export default App

实现 sidebar

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

// 路由表
const routes = [
  {
    path: '/',
    exact: true,
    component: () => <h1>Home</h1>
  },
  {
    path: '/bubblegum',
    component: () => <h1>bubblegum</h1>
  },
  {
    path: '/shoelaces',
    component: () => <h1>shoelaces</h1>
  }
]

class App extends Component {
  render() {
    const sideBarStyle = {
      padding: '10px',
      width: '40%',
      background: '#f0f0f0'
    }
    return (
      <Router>
        <div style={{ display: 'flex' }}>
          <div style={sideBarStyle}>
            <ul>
              <li>
                <Link to="/">Home</Link>
              </li>
              <li>
                <Link to="/bubblegum">Bubblegum</Link>
              </li>
              <li>
                <Link to="/shoelaces">Shoelaces</Link>
              </li>
            </ul>
          </div>
          <div style={{ flex: 1, padding: '10px' }}>
            {routes.map((route, index) => (
              <Route key={index} {...route} />
            ))}
          </div>
        </div>
      </Router>
    )
  }
}

export default App

route config - 定义路由表,实现子路由

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

// 路由表
const routes = [
  {
    path: '/sandwiches',
    component: () => <h2>Sandwiches</h2>
  },
  {
    path: '/tacos',
    component: Tacos,
    routes: [
      {
        path: '/tacos/bus',
        component: () => <h3>sub Bus</h3>
      },
      {
        path: '/tacos/cart',
        component: () => <h3>sub cart</h3>
      }
    ]
  }
]

function Tacos({ routes }) {
  return (
    <div>
      <h2>Tacos</h2>
      <ul>
        <li>
          <Link to="/tacos/bus">Bus</Link>
        </li>
        <li>
          <Link to="/tacos/cart">Cart</Link>
        </li>
      </ul>

      {routes.map((route, i) => (
        <RouteWithSubRoutes key={i} {...route} />
      ))}
    </div>
  )
}

const RouteWithSubRoutes = route => (
  <Route
    path={route.path}
    render={props => <route.component {...props} routes={route.routes} />}
  />
)

class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <ul>
            <li>
              <Link to="/tacos">Tacos</Link>
            </li>
            <li>
              <Link to="/sandwiches">Sandwiches</Link>
            </li>
          </ul>
          <hr />
          {routes.map((route, i) => (
            <RouteWithSubRoutes key={i} {...route} />
          ))}
        </div>
      </Router>
    )
  }
}

export default App

render