React useCallback with debounce 使用旧值,如何获得实际状态值?
React useCallback with debounce works with old value, how to get actual state value?
我无法满足所有条件:
- 我在
useCallback
中需要一些函数,因为我将它设置为子组件的属性(用于防止重新渲染)
- 我需要用
debounce
,因为我的函数是"end point",可以调用~100次/秒
- 去抖动后我需要获取电流(实际值)。
我对最后一点有疑问,去抖动 (1000ms) 后我的值已过时。
如何使用 useCallback
+ debounce
获取当前值? (提醒中的值必须与页面相同)
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = (props) => (<button onClick={props.getVal}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild components
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1}\n${someVal2}\n${someVal3}`);
}, 1000), [someVal1, someVal2, someVal3]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
首先你必须注意,debounce 函数在创建时从它的闭包设置状态。现在函数在几秒钟后执行,到那时状态已经改变。每次更新状态时都会创建一个新的去抖动实例,所以如果你在点击时使用去抖动函数,它不会正常工作,因为不同的调用将调用不同的去抖动函数实例而不是同一个
这种情况下的解决方案是将状态值作为参数传递给 debounce 函数,而不是让它依赖闭包。然而,它仍然使用调用 debounce 的值,正如您在下面的代码片段中看到的那样
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = ({getVal, someVal1,someVal2,someVal3}) => (<button onClick={() => getVal(someVal1,someVal2,someVal3)}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild components
const getVal = useCallback(_.debounce((val1, val2, val3) => {
alert(`${val1}\n${val2}\n${val3}`);
}, 1000), []); // create debounce function only once
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent someVal1={someVal1} someVal2={someVal2} someVal3={someVal3} getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
现在另一个解决方案是保留状态引用并在 debounce 函数中使用它们,这正是您想要的
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect, useRef } = React;
const SUBChildComponent = React.memo(({getVal}) => {
console.log('child render');
return <button onClick={() => getVal()}>GetValue with debounce</button>;
});
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
const someVal1Ref = useRef(someVal1);
const someVal2Ref = useRef(someVal2);
const someVal3Ref = useRef(someVal3);
useEffect(() => {
someVal1Ref.current = someVal1;
someVal2Ref.current = someVal2;
someVal3Ref.current = someVal3;
}, [someVal1, someVal2, someVal3])
// some callback witch works with states AND called from subClild components
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1Ref.current}\n${someVal2Ref.current}\n${someVal3Ref.current}`);
}, 1000), []); // create debounce function only once
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
PS. 这种实现在 class 组件中更容易,并且不需要任何解决方法,因为您不依赖于闭包
我无法满足所有条件:
- 我在
useCallback
中需要一些函数,因为我将它设置为子组件的属性(用于防止重新渲染) - 我需要用
debounce
,因为我的函数是"end point",可以调用~100次/秒 - 去抖动后我需要获取电流(实际值)。
我对最后一点有疑问,去抖动 (1000ms) 后我的值已过时。
如何使用 useCallback
+ debounce
获取当前值? (提醒中的值必须与页面相同)
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = (props) => (<button onClick={props.getVal}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild components
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1}\n${someVal2}\n${someVal3}`);
}, 1000), [someVal1, someVal2, someVal3]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
首先你必须注意,debounce 函数在创建时从它的闭包设置状态。现在函数在几秒钟后执行,到那时状态已经改变。每次更新状态时都会创建一个新的去抖动实例,所以如果你在点击时使用去抖动函数,它不会正常工作,因为不同的调用将调用不同的去抖动函数实例而不是同一个
这种情况下的解决方案是将状态值作为参数传递给 debounce 函数,而不是让它依赖闭包。然而,它仍然使用调用 debounce 的值,正如您在下面的代码片段中看到的那样
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = ({getVal, someVal1,someVal2,someVal3}) => (<button onClick={() => getVal(someVal1,someVal2,someVal3)}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild components
const getVal = useCallback(_.debounce((val1, val2, val3) => {
alert(`${val1}\n${val2}\n${val3}`);
}, 1000), []); // create debounce function only once
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent someVal1={someVal1} someVal2={someVal2} someVal3={someVal3} getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
现在另一个解决方案是保留状态引用并在 debounce 函数中使用它们,这正是您想要的
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect, useRef } = React;
const SUBChildComponent = React.memo(({getVal}) => {
console.log('child render');
return <button onClick={() => getVal()}>GetValue with debounce</button>;
});
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
const someVal1Ref = useRef(someVal1);
const someVal2Ref = useRef(someVal2);
const someVal3Ref = useRef(someVal3);
useEffect(() => {
someVal1Ref.current = someVal1;
someVal2Ref.current = someVal2;
someVal3Ref.current = someVal3;
}, [someVal1, someVal2, someVal3])
// some callback witch works with states AND called from subClild components
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1Ref.current}\n${someVal2Ref.current}\n${someVal3Ref.current}`);
}, 1000), []); // create debounce function only once
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
PS. 这种实现在 class 组件中更容易,并且不需要任何解决方法,因为您不依赖于闭包