周内工作的时候 接手了一个
react老项目,在开发的时候遇见了useCallback与useState的值无法更新问题,导致无法正确获取数据执行方法,现在看来问题比较简单,自己当时还是过于着急,特此记录
问题描述
- 使用
useCallback返回的方法在执行的时候 ,方法内部的state值一直是不变的,就算对state进行了更新,但是依然是不变的
先看一个简化问题dmeo 进行详细的描述
App.jsx
import { useCallback, useState } from "react";
import "./styles.css";
import Test from "./test";
export default function App() {
const [num, setNum] = useState(1);
const showNum = useCallback(() => {
console.log(num);
}, [num]);
return (
<div className="App">
<h1>{num}</h1>
<button onClick={() => setNum(num + 1)}>增加</button>
<Test onShow={showNum} />
</div>
);
}
test.jsx 因为老项目是class组件,所以demo也是class组件
import React from "react";
import "./styles.css";
class Test extends React.Component {
scrollFn = (onShow) => {
const scorllDom = document.getElementById("scorll");
scorllDom.addEventListener("scroll", onShow);
};
componentDidMount() {
const { onShow } = this.props;
this.scrollFn(onShow);
}
render() {
return (
<div className="test" id="scorll">
<h1>滚动</h1>
<div className="big"></div>
</div>
);
}
}
export default Test;
style.css
.App {
font-family: sans-serif;
text-align: center;
}
.test {
width: 100%;
height: 200px;
overflow: auto;
}
.big {
height: 600px;
background: blue;
}
得到界面为
滚动区域打印出
app.jsx中的num值,为1
点击增加
app.jsx中得num 值变为 2,然后对滚动区域进行滚动,结果打印出来还是1
问题原因分析
-
先看
app.jsx是否有问题showNum为useCallback返回方法,第二参数依赖 为num,没有问题
-
再看
test.jsx是否有问题onShow为props传递参数,在componentDidMount处作为参数传递给了 监听事件- 监听时事件只监听了
mount时的onShow,更新的onShow方法无法传递给监听事件 看来问题就出在这里了
解决方案
-
主要思路
- 利用 class 组件的
shouldComponentUpdate取到最新的onShow方法 - 然后每次更新的时候 重新监听新的事件具体代码如下
- 利用 class 组件的
test.jsx
import React from "react";
import "./styles.css";
class Test extends React.Component {
cachFn = null;
scrollFn = (onShow) => {
const scorllDom = document.getElementById("scorll");
scorllDom.removeEventListener("scroll", this.cachFn);
this.cachFn = onShow; // 缓存方法方便卸载
scorllDom.addEventListener("scroll", onShow);
};
shouldComponentUpdate(nextProps) {
if (nextProps.onShow !== this.props.onShow) {
this.scrollFn(nextProps.onShow);
}
return true;
}
componentDidMount() {
const { onShow } = this.props;
this.scrollFn(onShow);
}
render() {
return (
<div className="test" id="scorll">
<h1>滚动</h1>
<div className="big"></div>
</div>
);
}
}
export default Test;
测试如下
如愿以偿打印出了最新的num
-
其他解决思路
-
不修改
test.jsx问价的情况下 可以把num值改为闭包引用,监听的时候 虽然onShow方法没变,但是值因为是引用的,所以每次可以拿到最新的值,我最后采用了这个修改方式,因为不用对老文件造成修改 避免了新的问题的产生,需要修改app.jsx文件如下import { useCallback, useRef, useState } from "react"; import "./styles.css"; import Test from "./test"; export default function App() { const [num, setNum] = useState(1); const numRef = useRef(1); const showNum = useCallback(() => { console.log(numRef.current); // 使用引用值 }, [numRef]); return ( <div className="App"> <h1>{num}</h1> <button onClick={() => { const newNum = num + 1; setNum(newNum); numRef.current = newNum; }} > 增加 </button> <Test onShow={showNum} num={num} /> </div> ); } -
修改
test.jsx为hooks组,利用useEffect来监听onShow方法来更新事件监听中的方法,这个对原文件造成了破坏性修改,不建议,仅仅作为这次讨论,代码如下import React, { useEffect } from "react"; import "./styles.css"; const Test = ({ onShow }) => { useEffect(() => { const scorllDom = document.getElementById("scorll"); scorllDom.addEventListener("scroll", onShow); return () => { const scorllDom = document.getElementById("scorll"); scorllDom.removeEventListener("scroll", onShow); }; }, [onShow]); return ( <div className="test" id="scorll"> <h1>滚动</h1> <div className="big"></div> </div> ); }; export default Test;
-