React 路由器 v4 渲染方法重构

React router v4 render method refactor

我有一个运行良好的组件。但我想更改代码,以便 React.createElement(component, { ...props, loggingIn, authenticated }) 将使用 JSX 编写。

import React, { PropTypes } from 'react';
import { Route, Redirect } from 'react-router-dom';

const Public = ({ loggingIn, authenticated, component, ...rest }) => (
  <Route {...rest} render={(props) => {
    if (loggingIn) return <div></div>;
    return !authenticated ?
    (React.createElement(component, { ...props, loggingIn, authenticated })) :
    (<Redirect to="/documents" />);
  }} />
);

Public.propTypes = {
  loggingIn: PropTypes.bool,
  authenticated: PropTypes.bool,
  component: PropTypes.func,
};

export default Public;

您可以通过对 Public 组件的参数进行少量更改来完成此操作。

const Public = ({ component: Component, loggingIn, authenticated, ...rest }) => (

我做了一些实验来了解如何捕获组件参数 以便将其用作 JSX,所以我创建了一个 working example on Codepen,使用下面的代码。 (请注意,我使用 HashRouter 因为 BrowserRouter 在 Codepen 中不起作用。忽略它 - 对解决方案不重要。我还删除了 Public 组件中的一些逻辑以专注于解决方案。在您的最终代码中,您当然可以将 loggingInauthenticated 逻辑添加回 Public 组件)。

const {
  HashRouter,
  Route,
  Switch,
  Link
} = ReactRouterDOM

const Home = () => (
  <span>Home</span>
);

const Another = (props) => (
  <div>
    <span>Another</span>
    <div>Bah: {props.bah}</div>
  </div>
);

const NotHome = (props) => {    
  return (
    <div>
      <div>Not Home</div>
      <div>Logging in: {props.loggingIn}</div>
      <div>Authenticated: {props.authenticated}</div>
      <div>Other prop: {props.otherProp}</div>
      <div>Route props(match.path): {props.match.path}</div>
    </div>
  );
};

const Public = ({ component: Component, loggingIn, authenticated, ...rest }) => (
  <Route {...rest} render={(props) => {
    // return (React.createElement(Component, { ...props, loggingIn, authenticated }));
    return (<Component {...props} {...rest} loggingIn={loggingIn} authenticated={authenticated} />);
  }} />
);

ReactDOM.render((
  <HashRouter>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/public">Public</Link></li>
        <li><Link to="/another">Another</Link></li>
      </ul>

      <p>The rendered route component:{' '}
        <Switch>
          <Route exact path="/" component={Home} />
          <Public path="/public" component={NotHome} loggingIn="could be" authenticated="perhaps" otherProp="bar" />
          <Public path="/another" component={Another} loggingIn="no..." authenticated="not" bah="baz" />
        </Switch>
      </p>
    </div>
  </HashRouter>
), document.getElementById('app'));

我认为理解这一点的关键是将 how Babel transforms the JSX 看成 createElement,您会注意到:

<Public component={MyComponent} />

转换为:

React.createElement(Public, { component: MyComponent });

基本部分是 { component: MyComponent },因此需要更改参数。但是等等,这没有多大意义。因为你的参数{ component }{ component: component }是一样的。见 MDN docs on destructuring assignment syntax with the important sections being Assignment without declaration and Assigning to new variable names.

因此,在我的工作示例中,上面唯一真正发生的事情是变量名现在大写了......Wat?

深入挖掘,using Babel some more,真正的原因是 JSX 处理小写和大写组件名称的方式。比较:

console.log(<component />);

// Which is transformed to:
console.log(React.createElement("component", null));

与:

console.log(<Component />);

// Transformed to:
console.log(React.createElement(Component, null));

您需要使用大写字母来获取变量名而不是字符串。 (这导致了另一种解决方案,但这意味着将 属性 名称大写,这不是标准的,因此我不提供该解决方案)。

事实上,React JSX In Depth docs 确实简要提到了如何处理大写字母(强调我的):

"JSX 标签的第一部分决定了 React 元素的类型。

大写类型表示 JSX 标签指的是 React 组件。 这些标签被编译成对命名变量的直接引用,因此如果您使用 JSX <Foo /> 表达式,Foo 必须在范围内。"

还有 User-Defined Components Must Be Capitalized 部分:

"当元素类型以小写字母开头时,它指的是一个内置组件,如 <div><span> 并导致字符串 'div''span' 传递给 React.createElement。以大写字母开头的类型,如 <Foo /> 编译为 React.createElement(Foo),并对应于 JavaScript 文件中定义或导入的组件。

我们建议用大写字母命名组件。如果您确实有一个以小写字母开头的组件,请在将其用于 JSX 之前将其分配给大写变量。"

上面的 link 继续显示一些有用的示例。