您可以使用React钩子早日返回吗?

时间:2019-03-01 05:02:58

标签: javascript reactjs react-hooks

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

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

所以允许这样的事情:

import React from 'react';
import { useRouter, Redirect } from 'react-router';
import { useRedux } from 'react-redux';

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

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


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

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

如果不允许这样做,您可以解释为什么为什么,并提供一些通用的替代方法来提前带钩子返回功能组件吗?

3 个答案:

答案 0 :(得分:2)

React不允许您在其他挂接之前进行提前归还。如果一个组件执行的钩子少于以前的渲染,则将出现以下错误:

  

不变违规:渲染的钩子比预期的少。这可能是由于意外的提前归还声明引起的。

React无法分辨早期返回和条件挂接调用之间的区别。例如,如果您有3次调用useState,而您有时在第二次调用之后返回,则React无法告诉您是否在第二次useState调用之后返回,或者是否在第一个或第二个调用周围放置了条件第二次useState调用,因此它无法可靠地知道是否为发生 did 的两个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);

Edit Early return with hooks

我建议的另一种方法是将早期返回的部分分成自己的部分。提早返回后该部分所需的所有内容都将作为道具传递给新组件。

在我的示例中,它可能类似于以下内容:

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

Edit Early return with hooks

答案 1 :(得分:1)

即使您遵守不能有条件地调用Hook的条件,您仍然违反了必须在函数顶部调用钩子的规则。因此,不能,钩子之间不能有任何组件逻辑。您可以改为在选择器中包含该逻辑并发布挂钩。

import React from 'react';
import { useRouter, Redirect } from 'react-router';
import { useRedux } from 'react-redux';

const stateSelector =(match) => {
    if (!match) {
      return null;
    }
    const { some, thing } = match.params;
    // do what you want and return from here;
}
export default function Component() {
  const { match } = useRouter({ path: '/:some/:thing' });
  const state = useRedux(stateSelector(match));

  if (!match) return <Redirect to="/" />;
  return <Blah {...state} />;
}

答案 2 :(得分:-1)

如前所述,您不能在组件内部有条件地运行钩子。但是您可以传递渲染功能,该功能可能被视为对其他组件的自定义挂钩,并且仍然可以访问当前作用域。因此,与其拆分您的组件,不如使用实用程序组件:

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