Документы React ясно дают понять, что вызов хуков условно не будет работать. Из оригинальной презентации React hooks, причина в том, что React использует порядок, в котором вы называете хуки, для ввода правильного значения.
Я понимаю это, но теперь мой вопрос заключается в том, можно ли досрочно возвращаться из функционального компонента с хуками.
Так что-то вроде этого разрешено?:
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 вызывается условно, однако порядок их вызова не меняется между рендерами (хотя возможно, что будет вызываться на один хук меньше).
Если это не разрешено, можете ли вы объяснить Зачем, что это не разрешено, и предоставить общие альтернативные подходы к раннему возврату в функциональном компоненте с хуками?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


React не позволяет вам делать ранний возврат до других хуков. Если компонент выполняет меньше хуков, чем предыдущий рендер, вы получите следующую ошибку:
Invariant Violation: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
React не может определить разницу между ранним возвратом перед вызовом ловушки и вызовом ловушки, который является условным по какой-то другой причине. Например, если у вас есть 3 вызова useState и вы иногда возвращаетесь после второго, React не может определить, вернулись ли вы после второго вызова useState или вы поставили условие вокруг первого или второго вызова useState, поэтому он может' t надежно знает, возвращает ли он правильное состояние для двух вызовов 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);
это хорошая идея, но она недостаточно чистая, поэтому я написал этот HOC, чтобы сделать ее чище
Совершенно супер-придирка, но ранний возврат является — условный вызов ловушки.
@Evert Я не уверен, к чему ты клонишь. Возврат (ранний или другой) не является вызовом ловушки. Это может привести к условному вызову ловушки, если вызов ловушки происходит после раннего возврата, но в моем втором примере после раннего возврата вызова ловушки нет — обе ветки просто возвращают элементы React.
Извините, это придирка к вашему комментарию: React can't tell the difference between an early return and a conditional hook call. Если перед вызовом ловушки был возврат, этот вызов ловушки является условный.
Как уже упоминалось, вы не можете условно запускать хуки внутри компонента. Но вы можете передать функцию рендеринга, которая может рассматриваться как настраиваемый хук для других компонентов, и при этом иметь доступ к текущей области. Поэтому вместо разделения вашего компонента используйте служебный компонент, например:
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);
У вас есть ответ на свой вопрос - нельзя, потому что
React uses the order you call hooks to inject the correct value. На самом деле это может сработать в вашем случае, и предупреждение, которое вы получите, будет просто предупреждением, а не ошибкой. Но вы можете столкнуться с ошибкой позже, когда фактически забудете о местонахождении этого компонента и решите добавить больше хуков или переставить условное выражение.