背景
今天在做一个功能的时候,需要用到useState
保存一个函数,并setState
去改变这个函数的状态,初始代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import "./styles.css"; import React from "react";
export default function App() { const [callback, setCallback] = React.useState(() => { return "init"; }); console.log(callback); return ( <div className="App"> <button onClick={() => setCallback(() => { return "update"; }) } > 改变函数 </button> </div> ); }
|
很快,页面崩溃了,控制台报错:

一开始init
就输出了一次,点button
后update
输出,这是为啥呢?我只是想保存函数,并不想让他执行
惰性初始State
为了调查上述问题,当然是去看React官方文档,在hooksAPI,这一节中,我发现了问题所在,惰性初始State:
惰性初始 state
initialState
参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
1 2 3 4
| const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; });
|
也就是说,给useState
传入一个函数并不会保存函数状态,而是立即执行这个函数,并且只在初始渲染时执行,我猜测应该是为了避免一些高开销的运算,因为官方文档给的代码中函数名就是someExpensiveComputation
。
这就解释了之前的问题,一开始我是想用useState
保存一个函数,但这个函数立即就执行了,并且输出了init
。
而在我后面调用setState
去更新函数状态的时候,实际上是React
以为你要更新那个惰性初始的state
,于是就执行了setCallback
,并用返回的update
更新了callback
如何保存函数
那state
该如何保存函数呢
方法1 额外加一个函数
既然useState
中函数作为参数是惰性初始化的意思,那我们再返回一个函数不就好了吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import "./styles.css"; import React from "react";
export default function App() { const [callback, setCallback] = React.useState(() => () => { alert("init"); }); console.log(callback); return ( <div className="App"> <button onClick={() => setCallback(() => () => { alert("update"); }) } > 改变函数 </button> <button onClick={() => callback()}>执行函数</button> </div> ); }
|
当我们点击执行函数的时候,alert
了一个init
,当我们改变函数后再去执行函数,alert
了一个update
,实现了保存函数功能
方法2 useRef
当我们想保存一个东西的时候,想到useRef
准没错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import "./styles.css"; import React from "react";
export default function App() { const callback = React.useRef(() => { console.log("init"); }); console.log(callback); return ( <div className="App"> <button onClick={() => { callback.current = () => { console.log("update"); }; }} > 改变函数 </button> <button onClick={callback}>执行函数</button> </div> ); }
|
但又出现了新的问题,点击改变函数后,输出的还是init
,这是因为useRef
的改变并不会引起页面重新渲染。
callback
仍然是初次渲染被赋予的函数
将代码改为如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import "./styles.css"; import React from "react";
export default function App() { const callback = React.useRef(() => { console.log("init"); }); console.log(callback); return ( <div className="App"> <button onClick={() => { callback.current = () => { console.log("update"); }; }} > 改变函数 </button> // 将callback换成callback.current() <button onClick={() => callback.current()}>执行函数</button> </div> ); }
|
功能正常,完毕