如何避免useState函数参数(用于获取初始值)在每次渲染时都被执行?

6

从下面的代码片段可以看出,getInitialState 在每次渲染时都会被执行,尽管它返回的值仅在第一次渲染期间使用。

我知道这是正常的Javascript行为。但是,在React中是否有避免这种情况的方法?

function App() {

  function getInitialState() {
    console.log('Executing getInitialState...');
    return({
      foo: 'bar'
    });
  }

  const [myState,setMyState] = React.useState(getInitialState());
  const [myBoolean,setMyBoolean] = React.useState(false);
  
  return(
    <React.Fragment>
      <div>I am App</div>
      <div>My state: {JSON.stringify(myState)}</div>
      <div>My boolean: {JSON.stringify(myBoolean)}</div>
      <button onClick={()=>setMyBoolean((prevState) => !prevState)}>Force Update</button>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>


为什么你需要它是一个函数而不是一个常量? - skyboyer
@skyboyer 可能提问者正在从 API 响应或类似的动态来源确定状态。那么一个函数就变得必要了。 - ADTC
1
@ADTC 嗯,对于 useState 的初始化来说,异步调用永远不会起作用。因此,“避免在每次渲染时计算初始值”的唯一原因就是“太重了”。 - skyboyer
4个回答

8
不要直接在useState中调用函数,而是传递匿名函数来触发函数。请参考官方文档

  function getInitialState() {
    console.log('Executing getInitialState...');
    return({
      foo: 'bar'
    });
  }
  
function App() {

  const [myState,setMyState] = React.useState(()=>getInitialState());
  const [myBoolean,setMyBoolean] = React.useState(false);
  
  return(
    <React.Fragment>
      <div>I am App</div>
      <div>My state: {JSON.stringify(myState)}</div>
      <div>My boolean: {JSON.stringify(myBoolean)}</div>
      <button onClick={()=>setMyBoolean((prevState) => !prevState)}>Force Update</button>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>


这是一个很好的答案,但您应该详细说明为什么以及如何发生。 - Dennis Vash
1
getInitialState() 会每次执行该函数,但 () => getInitialState() 则会传递该函数的引用,因此只会被调用一次。 - thuva4
1
React会根据参数不同来处理useState。在您的代码中,getInitialState()将返回一个值。而useState会以不同的方式处理函数引用。如果它是一个函数,它只会在初始渲染时执行。 - thuva4

6
您需要根据文档在函数内计算状态,如文档所述:
const [myState,setMyState] = useState(() => {
  const initialState = getInitialState();
  return initialState;
});

或者更短:

const [myState, setMyState] = useState(getInitialState);

又是一个钩子的坑!


2
尝试使用 React.useMemo 进行记忆化。

function getInitialState() {
  console.log('Executing getInitialState...');
  return {
    foo: 'bar'
  };
}

function App() {
  const getInitial = React.useMemo(() => getInitialState(), []);
  const [myState, setMyState] = React.useState(getInitial);
  const [myBoolean, setMyBoolean] = React.useState(false);

  return (
    <React.Fragment>
      <div> I am App </div>
      <div> My state: {JSON.stringify(myState)} </div>
      <div> My boolean: {JSON.stringify(myBoolean)} </div>
      <button onClick={() => setMyBoolean(prevState => !prevState)}>
        Force Update
      </button>
    </React.Fragment>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root" />


1
所有的好解决方案,我的建议是使用Dominic上面更简短的解决方案。 :)
你正在传递一个计算函数(函数的结果被传递给useState)。如果你传递你的函数,React知道它只需要在第一次调用该函数:

function App() {

  function getInitialState() {
    console.log('Executing getInitialState...');
    return({
      foo: 'bar'
    });
  }

  const [myState,setMyState] = React.useState(getInitialState); // <-- pass the function, not the call to the function (result)
  const [myBoolean,setMyBoolean] = React.useState(false);
  
  return(
    <React.Fragment>
      <div>I am App</div>
      <div>My state: {JSON.stringify(myState)}</div>
      <div>My boolean: {JSON.stringify(myBoolean)}</div>
      <button onClick={()=>setMyBoolean((prevState) => !prevState)}>Force Update</button>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接