【问题标题】:Can you early return with React hooks?你能用 React hooks 提前回来吗?
【发布时间】:2019-07-23 02:36:51
【问题描述】:

React 文档明确指出calling hooks conditionally will not workFrom the original React hooks presentation,原因是因为 React 使用你调用 hooks 的顺序来注入正确的值。

我明白这一点,但现在我的问题是是否可以从带有钩子的函数组件中提前返回。

那么这样的事情是否允许?:

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 钩子是有条件地调用的,但是它们被调用的顺序在渲染之间不会改变(即使可能会调用更少的钩子)。

如果不允许这样做,您能否解释一下为什么不允许这样做并提供一般的替代方法来提前返回带有钩子的函数组件?

【问题讨论】:

  • 您在自己的问题中有答案 - 这是不允许的,因为 React uses the order you call hooks to inject the correct value。它实际上可能适用于您的情况,您将收到的警告只是警告而不是错误。但是,当您实际上忘记了该组件的下落并决定添加更多钩子或重新安排条件时,您可能会在以后陷入错误。

标签: javascript reactjs react-hooks


【解决方案1】:

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

不变违规:渲染的钩子比预期的要少。这可能是由于意外的提前退货声明造成的。

React 无法区分钩子调用之前的提前返回和由于其他原因有条件的钩子调用之间的区别。例如,如果你有 3 次调用 useState 并且有时在第二次调用后返回,React 无法判断你是否在第二次调用 useState 后返回,或者你是否在第一次或第二次 useState 周围设置了条件调用,因此它无法可靠地知道它是否正在为 确实 发生的两个 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);

我建议的替代方法是将提前返回后的部分分离到自己的组件中。提前返回后部分所需的任何东西都作为道具传递给新组件。

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

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);

【讨论】:

  • 这是个好主意,但不够干净,所以我写了this HOC 让它更干净
  • 完全是超级吹毛求疵,但提前返回一个有条件的钩子调用。
  • @Evert 我不确定你在说什么。返回(提前或以其他方式)不是挂钩调用。如果在提前返回之后发生挂钩调用,它可能会导致条件挂钩调用,但是在我的第二个示例中,在提前返回之后没有挂钩调用——两个分支都只返回 React 元素。
  • 抱歉,这是对您的评论的挑剔:React can't tell the difference between an early return and a conditional hook call。如果在钩子调用之前有返回,那么钩子调用有条件的。
【解决方案2】:

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

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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-13
    • 1970-01-01
    • 2011-06-27
    • 2010-09-23
    相关资源
    最近更新 更多