react-router-dom组件的三种渲染方式

2,898 阅读3分钟

react-router-dom 的 组件渲染内容有三种方式,分别是:children、component、render。三者的渲染优先级是 children > component > render。

Route render methods

<Route children>

<Route component>

<Route render>

以上三种渲染方式适用于不同的环境,三种方式是互斥的,也就是每次只能用其中的一种方法,component是最常用的一种渲染方式。

children: func

有时候,无论 location 是否匹配路由,你都需要渲染一些内容,这时候你可以使用 children,其工作方式等同于 render

import React, { Component } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
function ListItemLink({ to, name, ...rest }) {
  return (
    <Route
      path={to}
      // 使用 children 的方式渲染内容
      children={({ match }) => ( // children 是一个函数,其函数参数同route props
        <li className={match ? "active" : ""}>
          <Link to={to} {...rest}>
            {name}
          </Link>
        </li>
      )}
    />
  );
}
export default class RouteChildren extends Component {
  render() {
    return (
      <div>
        <h3>RouteChildren</h3>
        <Router>
          <ul>
            <ListItemLink to="/somewhere" name="链接1" />
            <ListItemLink to="/somewhere-else" name="链接2" />
          </ul>
        </Router>
      </div>
    );
  }
}

ReactDOM.render(<RouteChildren />, node)

render: func

使用 render 时,给 render 传递的是一个函数,它和 component 一样,在函数的参数中可以访问到所有的 route props

使用 render ,可以方便的实现 内联渲染 和 传递 route props

方便的内联渲染

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
// 方便的内联渲染
ReactDOM.render(
  <Router>
    <Route path="/home" render={props => <div>Home</div>} />
  </Router>,
  node
);

传递 route 属性

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

// wrapping/composing
//把route属性传递给你的组件
function FadingRoute({ component: Component, ...rest }) {
  return (
    <Route
      {...rest}
      render={routeProps => (
        <Component {...routeProps} />
      )}
    />
  );
}
ReactDOM.render(
  <Router>
    <FadingRoute path="/cool" component={Something} />
  </Router>,
  node
);

component: component

只有在当 location 匹配时才渲染

当传递component渲染UI时,router将会用React.createElement来将给定的组件创建一个新的React element

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

const User = (props) => <h1>Hello {props.match.params.username}</h1>;

ReactDOM.render(
    <Router>
    // component 传递的是一个 Component,Router 会使用 React.createElement 根据 User 组件创建一个新的 React Element
    <Route path="/user/:username" component={User} />
  </Router>
)

当在 component 中传递一个内联函数时,React 每次 render 都会创安一个新的组件,这将会导致不再更新现有的组件,而是直接卸载掉现有组件然后再去挂载一个新的组件,会导致性能问题。因此,当用到内联函数的内联渲染时,应当使用 render 或 children

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

class Child extends Component {
  componentDidMount() {
    console.log("componentDidMount"); //sy-log
  }

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

ReactDOM.render(
    <Router>
    // component 传递内联函数,会导致卸载掉现有的组件然后再去挂载一个新的组件
    <Route component={() => <Child count={count} />} />
  </Router>
)

因此,在 的三种渲染方式中,使用 component 的时候,应当给 component 传递一个 Component 组件而不是内联函数;需要使用内联函数实现内联渲染时,应当使用 render 或 children 的方式。

Route 源码解析

上面,我们分析了 的三种渲染方式,下面,我们来分析一下 Route 的源码:

// Version v5.x
var Route =
    /*#__PURE__*/
    function (_React$Component) {
        _inheritsLoose(Route, _React$Component);

        function Route() {
            return _React$Component.apply(this, arguments) || this;
        }

        var _proto = Route.prototype;

        _proto.render = function render() {
            var _this = this;

            return React.createElement(context.Consumer, null, function (context$1) {
                !context$1 ? invariant(false, "You should not use <Route> outside a <Router>") : void 0;
                var location = _this.props.location || context$1.location;
                var match = _this.props.computedMatch ? _this.props.computedMatch // <Switch> already computed the match for us
                    : _this.props.path ? matchPath(location.pathname, _this.props) : context$1.match;

                var props = _extends({}, context$1, {
                    location: location,
                    match: match
                });

                var _this$props = _this.props,
                    children = _this$props.children,
                    component = _this$props.component,
                    render = _this$props.render; // Preact uses an empty array as children by
                // default, so use null if that's the case.

                if (Array.isArray(children) && children.length === 0) {
                    children = null;
                }

                return React.createElement(context.Provider, {
                    value: props
                }, props.match
                    ? children
                        ? typeof children === "function"
                            ? evalChildrenDev(children, props, _this.props.path)
                            : children
                        : component
                            ? React.createElement(component, props)
                            : render
                                ? render(props)
                                : null
                    : typeof children === "function"
                        ? evalChildrenDev(children, props, _this.props.path)
                        : null);
            });
        };

        return Route;
    }(React.Component);

我们重点来看这段代码:

return React.createElement(context.Provider, {
    value: props
}, props.match
    ? children
        ? typeof children === "function" // 渲染方式为 children
            ? evalChildrenDev(children, props, _this.props.path) // 传递给 children 的是一个 function,执行该function
            : children // 传递给 children的 是节点元素
        : component // 渲染方式为 component
            ? React.createElement(component, props) // 创建一个新的 React Element
            : render // 渲染方式为 render   
                ? render(props)
                : null // Route 组件的三种渲染方式都没有时,返回 null
    : typeof children === "function"
        ? evalChildrenDev(children, props, _this.props.path) // 路由不匹配时,仅渲染children 为 function的情形
        : null
);

通过props.match 获取到路由匹配对象:

  1. 如果路由匹配,则按照 Route 的三种渲染方式的优先级 children > component > render 来渲染不同的内容。
  2. 如果渲染的方式为 children,则判断传递给 children 的是 function 还是节点元素,若是节点,则执行该 function,若是节点元素,则渲染该节点元素;
  3. 如果渲染方式为 component,则通过 React.createElement 方法创建新的 React Element;
  4. 若果渲染的方式为 render,则执行 render 方法;
  5. 如果三种渲染方式都没有,则返回null;
  6. 如果路由匹配,则渲染 children 方式为 function 的情形:evalChildrenDev(children, props, _this.props.path)