gpt4 book ai didi

javascript - 使用 useCallback 并使用先前状态作为参数设置新对象状态

转载 作者:行者123 更新时间:2023-12-04 10:11:06 25 4
gpt4 key购买 nike

考虑这个带有自定义表单钩子(Hook)的基本表单字段组件来处理输入更改:

import React, { useState, useCallback } from 'react';

const useFormInputs = (initialState = {})=> {
const [values, setValues] = useState(initialState);
const handleChange = useCallback(({ target: { name, value } }) => {
setValues(prev => ({ ...prev, [name]: value }));
}, []);
const resetFields = useCallback(() =>
setValues(initialState), [initialState]);
return [values, handleChange, resetFields];
};

const formFields = [
{ name: 'text', placeholder: 'Enter text...', type: 'text', text: 'Text' },
{ name: 'amount', placeholder: 'Enter Amount...', type: 'number',
text: 'Amount (negative - expense, positive - income)' }
];

export const AddTransaction = () => {
const [values, handleChange, resetFields] = useFormInputs({
text: '', amount: ''
});
return <>
<h3>Add new transaction</h3>
<form>
{formFields.map(({ text, name, ...attributes }) => {
const inputProps = { ...attributes, name };
return <div key={name} className="form-control">
<label htmlFor={name}>{text}</label>
<input {...inputProps} value={values[name]}
onChange={handleChange} />
</div>;
})}
<button className="btn">Add transaction</button>
</form>
<button className="btn" onClick={resetFields}>Reset fields</button>
</>;
};
  • 我真的有任何理由/优势使用 useCallback 将函数缓存在我的自定义 Hook 中吗?我阅读了文档,但我无法理解这种 useCallback 用法背后的想法。它究竟是如何记住渲染之间的函数的? ti 究竟是如何工作的,我应该使用它吗?
  • 在同一个自定义钩子(Hook)中,您可以看到通过传播先前状态并创建一个新对象来更新新值状态,如下所示:setValues(prev => ({ ...prev, [name]: value }));如果我这样做会有什么不同吗? setValues({ ...prev, [name]: value })据我所知,它看起来没有任何区别,对吧?我只是直接访问状态..我错了吗?
  • 最佳答案

    你的第一个问题:

    在您的情况下,这并不重要,因为所有内容都在同一个组件中呈现。如果你有一个获取事件处理程序的列表,那么 useCallback 可以为你节省一些渲染。

    在下面的示例中,前 2 个项目使用每次 App 重新呈现时重新创建的 onClick 呈现。这不仅会导致 Items 重新渲染,还会导致虚拟 DOM 比较失败,并且 React 将在 DOM 中重新创建 Itms(昂贵的操作)。

    最后 2 个项目获得一个 onClick,它在 App 挂载时创建,而不是在 App 重新渲染时重新创建,因此它们永远不会重新渲染。

    const { useState, useCallback, useRef, memo } = React;
    const Item = memo(function Item({ onClick, id }) {
    const rendered = useRef(0);
    rendered.current++;
    return (
    <button _id={id} onClick={onClick}>
    {id} : rendered {rendered.current} times
    </button>
    );
    });
    const App = () => {
    const [message, setMessage] = useState('');
    const onClick = (e) =>
    setMessage(
    'last clicked' + e.target.getAttribute('_id')
    );
    const memOnClick = useCallback(onClick, []);

    return (
    <div>
    <h3>{message}</h3>
    {[1, 2].map((id) => (
    <Item key={id} id={id} onClick={onClick} />
    ))}
    {[1, 2].map((id) => (
    <Item key={id} id={id} onClick={memOnClick} />
    ))}
    </div>
    );
    };

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


    <div id="root"></div>


    另一个例子是当你想在一个效果中调用一个函数,这个函数也需要在效果之外调用,所以你不能把这个函数放在效果里面。你只想在某个值发生变化时运行效果,这样你就可以做这样的事情。
    //fetchById is (re) created when ID changes
    const fetchById = useCallback(
    () => console.log('id is', ID),
    [ID]
    );
    //effect is run when fetchById changes so basically
    // when ID changes
    useEffect(() => fetchById(), [fetchById]);

    你的第二个问题:
    setValues({ ...prev, [name]: value })会给你一个错误,因为你从来没有定义过 pref 但如果你的意思是: setValues({ ...values, [name]: value })并将处理程序包装在 useCallback 中,那么现在您的回调依赖于 values并且每当值发生变化时都会不必要地重新创建。

    如果你不提供依赖,那么 linter 会警告你,你最终会得到一个 stale closure .这是一个过时闭包的示例,因为 counter.count 永远不会增加,因为您在第一次渲染后永远不会重新创建 onClick,因此计数器闭包将始终为 {count:1} .

    const { useState, useCallback, useRef } = React;
    const App = () => {
    const [counts, setCounts] = useState({ count: 1 });
    const rendered = useRef(0);
    rendered.current++;
    const onClick = useCallback(
    //this function is never re created so counts.count is always 1
    // every time it'll do setCount(1+1) so after the first
    // click this "stops working"
    () => setCounts({ count: counts.count + 1 }),
    [] //linter warns of missing dependency count
    );
    return (
    <button onClick={onClick}>
    count: {counts.count} rendered:{rendered.current}
    </button>
    );
    };

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


    <div id="root"></div>

    关于javascript - 使用 useCallback 并使用先前状态作为参数设置新对象状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61330628/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com