React文档明确指出calling hooks conditionally will not work。 From 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
挂钩是有条件调用的,但是在渲染之间它们的调用顺序不会改变(即使有可能会调用更少的挂钩)。
如果不允许这样做,您可以解释为什么为什么,并提供一些通用的替代方法来提前带钩子返回功能组件吗?
答案 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);
我建议的另一种方法是将早期返回的部分分成自己的部分。提早返回后该部分所需的所有内容都将作为道具传递给新组件。
在我的示例中,它可能类似于以下内容:
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);
答案 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);