你能早 return 使用 React hooks 吗?

Can you early return with React hooks?

React 文档明确指出 calling hooks conditionally will not work. From the original React hooks presentation,原因是 React 使用您调用钩子的顺序来注入正确的值。

我理解这一点,但现在我的问题是是否可以在带有钩子的函数组件中提前 return。

那么允许这样的事情吗?:

import React from 'react';
import { useRouteMatch, Redirect } from 'react-router';
import { useSelector } from 'react-redux';

export default function Component() {
  const { match } = useRouteMatch({ path: '/:some/:thing' });
  if (!match) return <Redirect to="/" />;

  const { some, thing } = match.params;
  const state = useSelector(stateSelector(some, thing));

  return <Blah {...state} />;
}

从技术上讲,useSelector 挂钩是有条件地调用的,但是调用它们的顺序在渲染之间不会改变(即使可能会少调用一个挂钩)。

如果不允许这样做,您能否解释一下为什么不允许这样做,并提供一般的替代方法来早期 returning 在带有挂钩的函数组件中?

React 不允许您在其他钩子之前提前 return。如果一个组件执行的 hooks 比之前的 render 少,你会得到以下错误:

Invariant Violation: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

React 无法区分钩子调用之前的早期 return 和出于其他原因有条件的钩子调用之间的区别。例如,如果你有 3 次调用 useState 并且你有时在第二次 return 之后,React 无法判断你是否在第二次 useState 调用之后 return 或者如果您在第一个或第二个 useState 调用周围设置条件,那么它无法可靠地知道 return 两个 useState 调用的正确状态是否 确实发生了。

这是一个示例,您可以使用它来查看此错误的实际效果(单击“增量状态 1”按钮两次以获取错误):

import React from "react";
import ReactDOM from "react-dom";

function App() {
  const [state1, setState1] = React.useState(1);
  if (state1 === 3) {
    return <div>State 1 is 3</div>;
  }
  const [state2, setState2] = React.useState(2);
  return (
    <div className="App">
      <div>State 1: {state1}</div>
      <div>State 2: {state2}</div>
      <button onClick={() => setState1(state1 + 1)}>Increment State 1</button>
      <button onClick={() => setState2(state2 + 1)}>Increment State 2</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

我建议的替代方法是将早期 return 之后的部分分离到它自己的组件中。早期 return 之后的部分所需的任何内容都作为 props 传递给新组件。

在我的示例中,它可能如下所示:

import React from "react";
import ReactDOM from "react-dom";

const AfterEarlyReturn = ({ state1, setState1 }) => {
  const [state2, setState2] = React.useState(2);
  return (
    <div className="App">
      <div>State 1: {state1}</div>
      <div>State 2: {state2}</div>
      <button onClick={() => setState1(state1 + 1)}>Increment State 1</button>
      <button onClick={() => setState2(state2 + 1)}>Increment State 2</button>
    </div>
  );
};
function App() {
  const [state1, setState1] = React.useState(1);
  if (state1 === 3) {
    return <div>State 1 is 3</div>;
  }
  return <AfterEarlyReturn state1={state1} setState1={setState1} />;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

如前所述,您不能 运行 在组件内部有条件地挂钩。但是您可以将可能被视为自定义挂钩的渲染函数传递给其他组件,并且仍然可以访问当前范围。因此,不要像这样拆分组件,而是使用实用程序组件:

import React from "react";
import ReactDOM from "react-dom";

const HooksHost = ({ children }) => children();

function App() {
  const [state1, setState1] = React.useState(1);

  if (state1 === 3) {
    return <div>State 1 is 3</div>;
  }

  return (
    <HooksHost>
      {/* should be named to pass lint rule that checks if it starts from 'use' then it is custom hook */}
      {function useHooks() {
        const [state2, setState2] = React.useState(2);
        return (
          <div className="App">
            <div>State 1: {state1}</div>
            <div>State 2: {state2}</div>
            <button onClick={() => setState1(state1 + 1)}>
              Increment State 1
            </button>
            <button onClick={() => setState2(state2 + 1)}>
              Increment State 2
            </button>
          </div>
        );
      }}
    </HooksHost>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);