探究 react-router 组件

471 阅读3分钟

本文以 react-router-dom v5.3.0 学习。

模块简介

history 核心,提供两种路由 history 对象上的 api ,监听路由变化,改变路由等;

react-router 处理视图的渲染,提供一些组件;

react-router-dom 提供BrowserRouter,HashRouter,Link,NavLink,转发了 react-router 的组件:

export {
  MemoryRouter,
  Prompt,
  Redirect,
  Route,
  Router,
  StaticRouter,
  Switch,
  generatePath,
  matchPath,
  withRouter,
  useHistory,
  useLocation,
  useParams,
  useRouteMatch
} from "react-router";

export { default as BrowserRouter } from "./BrowserRouter.js";
export { default as HashRouter } from "./HashRouter.js";
export { default as Link } from "./Link.js";
export { default as NavLink } from "./NavLink.js";

两种路由

  • history 路由

pushStatereplaceState 改变路由

popState 监听路由

popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()history.forward()history.go()方法。

  • hash 路由

location.hash 改变路由

hashchange 监听路由

路由组件

Router

function Login(props) {
  console.log('login', props);
  return <div>login</div>;
}
function Register(props) {
  console.log('register', props);

  return <div>register</div>;
}
function Foo(props) {
  console.log('foo', props);

  return <div>foo</div>;
}

function App() {
  return (
    <BrowserRouter>
      <Route exact path="/" component={Login} />
      <Route exact path="/foo" component={Foo} />
      <Route exact path="/register" component={Register}></Route>
    </BrowserRouter>
  );
}

访问http://localhost:3000/,匹配 Login 组件,打印的 props:

image.png

history、location、match 就是 RouterContext 内容

image.png

BrowserRouter/HashRouter 将 props.children 和内部的 history 传给 Router 组件返回;

Router 组件会调用 history.listen 监听 location ,给子组件提供 RouterContext、 HistoryContext上下文;

子组件 Route 会消费 RouterContext,render 时,会给 Route 的子组件提供 RouterContext 上下文,渲染子组件;

// BrowserRouter/HashRouter
history = createHistory(this.props);
render() {
  return <Router history={this.history} children={this.props.children} />;
}

// Router
this.unlisten = props.history.listen(location => {
  if (this._isMounted) {
    this.setState({ location });
  } else {
    this._pendingLocation = location;
  }
});
render() {
  return (
    <RouterContext.Provider
      value={{
        history: this.props.history,
          location: this.state.location,
            match: Router.computeRootMatch(this.state.location.pathname),
              staticContext: this.props.staticContext
      }}
      >
      <HistoryContext.Provider
        children={this.props.children || null}
        value={this.props.history}
        />
    </RouterContext.Provider>
  );
}

// Route
<RouterContext.Consumer>
  // 处理 context
  <RouterContext.Provider value={props}>
    // value 的 props 为处理后的 context ,根据 Route 的 props (children, component, render) 来渲染组件,它们会将 value.props 作为属性传给组件,所以组件里面 props 能拿到 RouterContext
  </RouterContext.Provider>
</RouterContext.Consumer>

Route

  • 说明
<Route exact path="/" component={Login}></Route>
exact 精确匹配
path 用于匹配路由,渲染组件
component 组件
render 函数形式返回元素
children 无论是否匹配成功都会渲染,匹配成功 match 有值
strict /foo -> /foo/ /foo 都能匹配;/foo/ -> /foo/ /foo/xx 匹配;

匹配路由,渲染组件,路由状态是通过 context 传递,Route 通过 Consumer 获取上一级的路由匹配,成功,渲染组件,并利用 context 逐层传递的特点,将自己的路由信息传给子路由,实现路由嵌套。

  • 路由优雅的写法 react-router-config
function Login(props) {
  console.log('login', props);
  return <div>login</div>;
}
function Register(props) {
  console.log('register', props);

  return <div>register</div>;
}
function Foo(props) {
  console.log('foo', props);

  return <div>foo</div>;
}

const routes = [
  {
    path: '/',
    component: Login,
    exact: true
  },
  {
    path: '/foo',
    component: Foo,
    exact: true
  },
  {
    path: '/register',
    component: Register,
    exact: true
  }
];

function App() {
  return (
    <BrowserRouter>
      <div>{renderRoutes(routes)}</div>
    </BrowserRouter>
  );
}

其实就是 renderRoutes(routes) 帮我们生成 Route。源码如下:

function renderRoutes(routes, extraProps = {}, switchProps = {}) {
  return routes ? (
    <Switch {...switchProps}>
      {routes.map((route, i) => (
        <Route
          key={route.key || i}
          path={route.path}
          exact={route.exact}
          strict={route.strict}
          render={props =>
            route.render ? (
              route.render({ ...props, ...extraProps, route: route })
            ) : (
              <route.component {...props} {...extraProps} route={route} />
            )
          }
        />
      ))}
    </Switch>
  ) : null;
}

Switch 包裹 Route ,渲染是通过 render 函数来渲染组件的。

Switch

Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route><Redirect>,它里面不能放其他元素。

function App() {
  return (
    <HashRouter>
      <div>
        <Switch>
          <Route exact path="/" component={Login} />
          <Route exact path="/register" component={Register}></Route>
          <Route
            path="/foo"
            render={props => {
              return <Foo {...props} />;
            }}
          ></Route>
        </Switch>
      </div>
    </HashRouter>
  );
}
没有 Switch exact 时,页面访问 /register 时,LoginRegister 都会渲染。
+ Switch,页面展示 Login,
++ exact 页面展示 Register

Redirect

路由匹配不到时,重定向组件

function Login(props) {
  console.log('login', props);
  return <div>login</div>;
}
function Register(props) {
  console.log('register', props);

  return <div>register</div>;
}
function Foo(props) {
  console.log('foo', props);

  return <div>foo</div>;
}

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Login} />
        <Route exact path="/foo" component={Foo} />
        <Route exact path="/register" component={Register}></Route>
        <Redirect to="/foo" />
      </Switch>
    </BrowserRouter>
  );
}

不用 Switch 包裹时,页面组件都会加载,所以总是显示 foo 页面。

Redirect from 属性:是限制来自 from 下的找不到,才会重定向。

路由状态获取

  • 被 Route 包裹的路由组件混入了路由对象,所以子组件可以通过 props 获取(history,location,match);
  • 距离路由组件比较远,层级比较深的类组件,可以通过 withRouter 获取;
  • 函数组件通过 useLocationuseHistory 获取;
class Son extends React.Component {
  render() {
    console.log(this.props);
    return <div>son</div>;
  }
}

function Sonfn(props) {
  console.log(props);
  const location = useLocation();
  const history = useHistory();
  console.log(location, history);
  return <div>son</div>;
}

const SonH = withRouter(Son);

function Login(props) {
  console.log('login', props);
  return (
    <div>
      <SonH />
      <Sonfn />
    </div>
  );
}
function Register(props) {
  console.log('register', props);

  return <div>register</div>;
}
function Foo(props) {
  console.log('foo', props);

  return <div>foo</div>;
}

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Login} />
        <Route exact path="/foo" component={Foo} />
        <Route exact path="/register" component={Register}></Route>
        <Redirect to="/foo" from="/register" />
      </Switch>
    </BrowserRouter>
  );
}

路由跳转、传参

跳转

  • <NavLink>Link 跳转;
  • history.push 函数跳转;

传参

  • 直接 url 上面拼接;
  • state 上传递,location 上面获取;

动态路由 <Route to="/login/:id"/> 可以用正则如下:

path="/login/:type(login|loginout)" ->/login/login  /login/loginout
path="/login/:id?" -> /loign/1 /login

嵌套路由,子路由要跟随父路由,父路由不能加 exact

function Son(props) {
  console.log(props);
  const location = useLocation();
  const history = useHistory();
  console.log(location, history);
  return <div>son</div>;
}

function Login(props) {
  console.log('login', props);
  return (
    <div>
      <Son />
    </div>
  );
}
function Address(props) {
  const { history } = props;
  const goHome = () => {
    history.push({
      pathname: '/',
      state: {
        info: 'address'
      }
    });
  };
  return (
    <div>
      <p>address</p>
      <button onClick={() => goHome()}>首页</button>
    </div>
  );
}
function Register(props) {
  console.log('register', props);

  return (
    <div>
      <Route exact path="/register/address" component={Address} />
      <NavLink to="/register/address">Address</NavLink>
    </div>
  );
}
function Foo(props) {
  console.log('foo', props);

  return <div>foo</div>;
}

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Login} />
        <Route exact path="/foo" component={Foo} />
        <Route path="/register" component={Register}></Route>
        <Redirect to="/foo" />
      </Switch>
      <NavLink to="/foo">Foo</NavLink>
      <NavLink to="/register">Register</NavLink>
    </BrowserRouter>
  );
}

参考

react-router 官网

React 进阶实践指南