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