Использование useCallback и установка нового состояния объекта с использованием предыдущего состояния в качестве аргумента - PullRequest
2 голосов
/ 20 апреля 2020

Рассмотрим этот базовый c компонент полей формы с настраиваемым хуком формы для обработки изменений ввода:

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>
    </>;
};
  1. Есть ли у меня какая-либо причина / преимущество для использования useCallback для кэшировать функцию в моем пользовательском хуке? Я читаю документы, но я просто не могу понять идею такого использования useCallback. Как именно он запоминает функцию между рендерами? Как именно это работает, и я должен его использовать?

  2. Внутри того же пользовательского хука, вы можете видеть, как обновляется состояние новых значений, расширяя предыдущее состояние и создавая новый объект, такой как итак: setValues(prev => ({ ...prev, [name]: value })); Будет ли какая-то разница, если я сделаю это вместо этого? setValues({ ...prev, [name]: value }) Насколько я могу судить, разве это не похоже на разницу? Я просто напрямую обращаюсь к государству .. Я не прав?

1 Ответ

2 голосов
/ 20 апреля 2020

Ваш первый вопрос:

В вашем случае это не имеет значения, потому что все отображается в одном компоненте. Если у вас есть список вещей, которые получают обработчик событий, то useCallback может сэкономить вам некоторые рендеры.

В приведенном ниже примере первые 2 элемента отображаются с помощью onClick, который создается каждый раз при повторной визуализации приложения. Это не только вызовет повторную визуализацию элементов, но также приведет к сбою виртуального сравнения DOM, и React создаст Itms в DOM (дорогая операция).

Последние 2 элемента получают созданный onClick когда приложение монтируется и не создается заново при рендеринге приложения, поэтому они никогда не будут рендериться.

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 и будет без необходимости заново создаваться при изменении значений.

Если вы не Если вы не обеспечите зависимость, то линтер предупредит вас, и вы получите устаревшее закрытие . Вот пример устаревшего закрытия, так как counter.count никогда не будет go вверх, потому что вы никогда не создаете 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...