我想开始讨论创建回调的推荐方法,这些回调接收来自循环内创建的组件的参数。
例如,如果我正在填充将具有“删除”按钮的项目列表,我希望“onDeleteItem”回调知道要删除的项目的索引。所以像这样:
const onDeleteItem = useCallback(index => () => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<div>
<span>{item}</span>
<button type="button" onClick={onDeleteItem(index)}>Delete</button>
</div>
)}
</div>
);
Run Code Online (Sandbox Code Playgroud)
但问题在于 onDeleteItem 将始终向 onClick 处理程序返回一个新函数,导致按钮重新呈现,即使列表没有更改。所以它违背了useCallback.
我想出了我自己的钩子,我称之为useLoopCallback,它通过记住主回调以及循环参数映射到他们自己的回调来解决这个问题:
import React, {useCallback, useMemo} from "react";
export function useLoopCallback(code, dependencies) {
const callback = useCallback(code, dependencies);
const loopCallbacks = useMemo(() => ({map: new Map(), callback}), [callback]);
return useCallback(loopParam => {
let loopCallback = loopCallbacks.map.get(loopParam);
if (!loopCallback) { …Run Code Online (Sandbox Code Playgroud) 我试图将问题归结为一个尽可能简单的例子:
我们有一个子组件列表,每个子组件都称为NumChoice,每个子组件代表一个数字。NumChoice被包裹在React.memo. 在父组件中,我们有一个布尔数组choices,每个对应一个子组件NumChoice。首先, 的所有元素choices都是false。为了渲染子组件,我们遍历choices,并为每个选择生成相应的子组件NumChoice。我们chooseDivisibles在父组件中定义一个函数,使用useCallback它从每个子组件调用NumChoice。chooseDivisibles获取NumChoice调用它的人的索引并将其对应的元素更改choices为true。每个都是,否则,它的背景颜色是“白色”。NumChoice有一个“红色”的背景色,如果在其对应的元件choicestrue
完整代码可在:https : //codesandbox.io/s/react-rerender-l4e3c?fontsize=14&hidenavigation=1&theme=dark
包装NumChoiceinReact.memo和chooseDivisiblesin useCallback,我们希望只重新渲染NumChoice其相应元素发生choices变化的组件,但 React 会重新渲染它们。chooseDivisibles包含在 中useCallback,除了setChoices. 此外,NumChoice被包裹,React.memo并且只有在指定的道具改变时才应该重新渲染,但它们不会,并且改变choices不应该对重新渲染产生任何影响NumChoice。如果我们排除检查chooseDivisibles前一个和下一个 props的相等性,它会按预期工作,但我认为前一个和下一个的比较chooseDivisibles不应该影响重新渲染, …
TL; 博士
为什么被useCallback定义为(大致)
function useCallback(callback, deps) {
return useMemo((...args) => {
return callback(...args);
}, deps);
}
Run Code Online (Sandbox Code Playgroud)
而不是这样?
function useCallback(callback) {
const fn = useRef(callback);
fn.current = callback;
return useMemo((...args) => {
return fn.current(...args);
}, []);
}
Run Code Online (Sandbox Code Playgroud)
它似乎可以解决不必要的重新渲染,同时始终使用该函数的最新版本。我还听说 Vue 3使用该选项以完全相同的方式进行优化cacheHandlers。
上下文解释版本
在编写 react 组件/钩子/上下文时,您可以直接编写函数:
const bootstrapAuth = async () => {
// ...
};
Run Code Online (Sandbox Code Playgroud)
...或使用useCallback以下方法优化最少的重新渲染:
const bootstrapAuth = useCallback(async () => {
// ...
}, []);
Run Code Online (Sandbox Code Playgroud)
我自己useCallback经常使用,但作为老师,我从一开始就不会教我的学生。这是一个额外的复杂性,并且就这样。我知道官方只考虑了性能问题。只是 …
我有一个谜。考虑以下自定义 React 钩子,它按时间段获取数据并将结果存储在 a 中Map:
export function useDataByPeriod(dateRanges: PeriodFilter[]) {
const isMounted = useMountedState();
const [data, setData] = useState(
new Map(
dateRanges.map(dateRange => [
dateRange,
makeAsyncIsLoading({ isLoading: false }) as AsyncState<MyData[]>
])
)
);
const updateData = useCallback(
(period: PeriodFilter, asyncState: AsyncState<MyData[]>) => {
const isSafeToSetData = isMounted === undefined || (isMounted !== undefined && isMounted());
if (isSafeToSetData) {
setData(new Map(data.set(period, asyncState)));
}
},
[setData, data, isMounted]
);
useEffect(() => {
if (dateRanges.length === 0) {
return;
} …Run Code Online (Sandbox Code Playgroud) 我有一个这样的例子:
我想在回调中修改一个状态值,然后使用新的状态值修改另一个状态。
export default function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("0");
const [added, setAdded] = useState(false);
const aNotWorkingHandler = useCallback(
e => {
console.log("clicked");
setCount(a => ++a);
setText(count.toString());
},
[count, setCount, setText]
);
const btnRef = useRef(null);
useEffect(() => {
if (!added && btnRef.current) {
btnRef.current.addEventListener("click", aNotWorkingHandler);
setAdded(true);
}
}, [added, aNotWorkingHandler]);
return <button ref={btnRef}> + 1 </button>
Run Code Online (Sandbox Code Playgroud)
但是,在调用该处理程序后,count已成功增加,但text没有增加。
你们能帮我理解为什么会发生这种情况吗?以及如何干净地避免它?
谢谢你!
我无法满足所有条件:
useCallback,因为我将它设置为子组件的道具(用于防止重新渲染)debounce,因为我的函数是“终点”,可以调用 ~100 次/秒我对最后一点有问题,我在去抖动(1000 毫秒)后的值已过时。
如何使用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 = …Run Code Online (Sandbox Code Playgroud)我在这里做了一个工作示例: https://codesandbox.io/s/magical-flower-o0gyn ?file=/src/App.js
当我单击隐藏按钮时,我想将更新的数据保存到本地存储:
setValueWithCallback运行,将回调设置为引用并设置状态useEffect启动,使用更新的数据调用回调saveToLocalStorage在设置为依赖项的useCallback情况下被调用data问题出在第三步,保存到本地存储的内容适用{visible: true}于两者。我知道如果我改变这一行:
const saveToLocalStorage = useCallback(() => {
localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
}, [data]);
Run Code Online (Sandbox Code Playgroud)
对此:
const saveToLocalStorage = localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
Run Code Online (Sandbox Code Playgroud)
它有效,但我无法理解,为什么第一个不起作用。我认为这一定是一些关闭的事情,但我没有看到它。
如果data已经更新,并useEffect运行了回调,为什么它没有在依赖项数组中更新?是的,这个例子很奇怪,第二个解决方案非常好,我只是想演示这个问题。谢谢您的帮助!
在 React 中,我们可以使用 useCallback 来记忆函数,这样函数就不会在每次重新渲染时都发生改变
const fn = useCallback(someOtherFn, []);
Run Code Online (Sandbox Code Playgroud)
相反,我们可以someOtherFn在组件外部定义吗?如果它使用 setState,我们可以将其作为参数提供给它吗?
就像是
function someOtherFn(setState) {
setState("ok")
}
function myComponenet(){
......
}
Run Code Online (Sandbox Code Playgroud)
如果这是一个愚蠢的问题,请提前抱歉。
我正在开发一个相对较大的 React 代码库,并且我看到以前的开发人员使用memo并usecallback自由地认为通过使用这些代码可以提高性能。显然不是,这是一个例子
export default function QuestionHelper({ text }: { text: string }) {
const [show, setShow] = useState<boolean>(false);
const open = useCallback(() => setShow(true), [setShow]);
const close = useCallback(() => setShow(false), [setShow]);
return (
<span style={{ marginLeft: 4 }}>
<Tooltip show={show} text={text}>
<QuestionWrapper
onClick={open}
onMouseEnter={open}
onMouseLeave={close}
>
<Question size={16} />
</QuestionWrapper>
</Tooltip>
</span>
);
}
Run Code Online (Sandbox Code Playgroud)
该应用程序现在可以运行。但我拒绝修复它,因为该应用程序按预期工作。我怎样才能证明我们应该删除代码中所有不必要的memo和?usecallback
是否有客观的方法来说明删除这些内容的好处?
我有以下问题。我有一个组件需要在安装时调用 API。我在 useCallback 中进行调用,如下所示:
const sendCode = useCallback(() => {
console.log('InsideSendCode');
}, []);
Run Code Online (Sandbox Code Playgroud)
然后我在 useEffect 中调用这个函数,如下所示:
useEffect(() => {
sendCode();
}, [sendCode]);
Run Code Online (Sandbox Code Playgroud)
问题是,即使使用 useCallback,该消息也会在控制台中显示两次,我发现这将是唯一的选择。
我知道 StrictMode,但我不想禁用它。
如果每个人都能发表意见,那就太好了。
谢谢。
reactjs ×10
usecallback ×10
react-hooks ×4
use-effect ×4
javascript ×2
array-map ×1
memo ×1
react-memo ×1
rerender ×1
web-frontend ×1