Javascript 基于恒定条件调用react钩子安全吗?

Javascript 基于恒定条件调用react钩子安全吗?,javascript,reactjs,eslint,react-hooks,Javascript,Reactjs,Eslint,React Hooks,需要在每个渲染上以相同的顺序调用相同的钩子。如果你违反了这条规则,会出现什么问题,这是可以解释的。例如,此代码: function App() { console.log('render'); const [flag, setFlag] = useState(true); const [first] = useState('first'); console.log('first is', first); if (flag) { const [second] = use

需要在每个渲染上以相同的顺序调用相同的钩子。如果你违反了这条规则,会出现什么问题,这是可以解释的。例如,此代码:

function App() {
  console.log('render');
  const [flag, setFlag] = useState(true);
  const [first] = useState('first');
  console.log('first is', first);
  if (flag) {
    const [second] = useState('second');
    console.log('second is', second);
  }
  const [third] = useState('third');
  console.log('third is', third);

  useEffect(() => setFlag(false), []);

  return null;
}
控制台输出

render 
first is first 
second is second 
third is third 
render 
first is first 
third is second 
并导致警告或错误

但是,在元素生命周期中不会改变的条件又如何呢

const DEBUG = true;

function TestConst() {
  if (DEBUG) {
    useEffect(() => console.log('rendered'));
  }

  return <span>test</span>;
}
const DEBUG=true;
函数TestConst(){
如果(调试){
useffect(()=>console.log('rendered');
}
回归试验;
}
这段代码并没有真正违反规则,而且似乎工作正常。但它仍然会触发eslint警告

此外,似乎可以基于道具编写类似的代码:

function TestState({id, debug}) {
  const [isDebug] = useState(debug);

  if (isDebug) {
    useEffect(() => console.log('rendered', id));
  }

  return <span>{id}</span>;
}

function App() {
  const [counter, setCounter] = useState(0);
  useEffect(() => setCounter(1), []);
  return (
    <div>
      <TestState id="1" debug={false}/>
      <TestState id="2" debug={true}/>
    </div>
  );
}
函数TestState({id,debug}){
const[isDebug]=使用状态(调试);
if(isDebug){
useffect(()=>console.log('rendered',id));
}
返回{id};
}
函数App(){
const[counter,setCounter]=useState(0);
useffect(()=>setCounter(1),[]);
返回(
);
}
此代码按预期工作

所以,当我确信条件不会改变时,在条件内调用挂钩安全吗?是否可以修改eslint规则以识别此类情况

问题更多的是真正的需求,而不是实现类似行为的方式。据我所知,重要的是

确保每次调用组件时,钩子的调用顺序相同 渲染。这就是允许React正确地保持状态的原因 多个useState和useEffect调用之间的挂钩


这条规则还有一个例外:“不要在循环、条件或嵌套函数中调用钩子”。

尽管您可以像上面提到的那样有条件地编写钩子,而且它目前可能工作,但将来可能会导致意外行为。例如,在当前情况下,您没有修改
isDebug
状态

演示

const{useState,useffect}=React;
函数TestState({id,debug}){
const[isDebug,setDebug]=useState(debug);
if(isDebug){
useffect(()=>console.log('rendered',id));
}
常量切换按钮=()=>{
setDebug(prev=>!prev);
}
返回(
{id}
切换调试
);
}
函数App(){
const[counter,setCounter]=useState(0);
useffect(()=>setCounter(1),[]);
返回(
);
}
ReactDOM.render(,document.getElementById('app'))

对于您的用例,我看不出问题所在,我看不出这在将来会如何中断,您是对的,它按预期工作

然而,我认为警告实际上是合法的,并且应该一直存在,因为这可能是代码中的一个潜在错误(不是在这个特定的代码中)

因此,在您的情况下,我要做的是禁用该行的
react hooks/rules of hooks
rule


参考:

请不要使用此模式。它可能在您的示例中起作用,但不好(或不惯用)

标准模式(出于充分的理由)是在构造函数中声明初始状态,然后根据主体中的某些条件进行更新(setState)。React钩子在无状态组件中镜像此功能-因此它应该可以同样工作

其次,我看不出动态添加这段状态是多么有用,并且可能会在以后导致渲染问题。在您的示例中,一个简单的常量也可以工作——没有理由使用动态

考虑这一点:

return (<React.Fragment>{second}</React.Fragment>)
返回({second})

当您没有定义
second
时,这会因引用错误而中断。

此钩子规则解决了条件钩子调用可能出现问题的常见情况:

不要在循环、条件或嵌套函数中调用钩子。相反,总是在React函数的顶层使用钩子。通过遵循此规则,可以确保每次呈现组件时都以相同的顺序调用挂钩

如果开发人员没有完全意识到后果,那么这个规则是一个安全的选择,可以作为经验法则使用

但这里的实际规则是:

确保每次渲染组件时都以相同的顺序调用挂钩

可以使用循环、条件和嵌套函数,只要保证在同一个组件实例中以相同的数量和顺序调用挂钩

即使是
process.env.NODE\u env===“development”
在组件寿命期间,如果在运行时重新分配
process.env.NODE\u env
属性,条件也会发生变化

如果条件为常数,可在组件外部定义,以确保:

const isDebug = process.env.NODE_ENV === 'development';

function TestConst() {
  if (isDebug) {
    useEffect(...);
  }
  ...
}
如果条件源自动态值(尤其是初始道具值),则可将其记忆为:

function TestConst({ debug }) {
  const isDebug = useMemo(() => debug, []);

  if (isDebug) {
    useEffect(...);
  }
  ...
}
或者,由于在将来的React版本中,可以使用
useState
(如问题所示)或
useRef
;后者没有额外的开销和合适的语义:

function TestConst({ debug }) {
  const isDebug = useRef(debug).current;

  if (isDebug) {
    useEffect(...);
  }
  ...
}

如果存在
react hooks/rules of hooks
ESLint规则,则可以按行禁用该规则。

谢谢。我理解
TestState
依赖于当前的“每个元素”需求,如果一些内部react更改将其转化为“每个组件”需求,则可能会中断。但我无法想象未来
const DEBUG
会如何崩溃。还
const[isDebug]=useState(debug)是这样编写的,以确保它不会更改。@UjinT34,目前您没有修改isDebug状态,但将来可能会有人添加一个setter函数,并提供一种修改isDebug状态的方法。”。这可能会破坏应用程序。一个问题是,一旦你想要有条件地使用的钩子是一个包含大量use*调用的自定义钩子,这种方法就会产生大量样板代码。