React Hooks общие проблемы и решения

React.js
React Hooks общие проблемы и решения

Эта статья была включена вGithub: GitHub.com/Умышленно миром/…, добро пожаловать Звезда!

Общая проблема

Поверьте, прочитав эту статью, вы сможете получить нужные вам ответы.

Во-первых, процесс рендеринга функционального компонента

Давайте посмотрим, как работает функциональный компонент:

Counter.js

function Counter() {
    const [count, setCount] = useState(0);

    return <p onClick={() => setCount(count + 1)}>count: {count}</p>;
}

за кликpЭтикетка,countоба +1,setCountвызовет рендеринг функционального компонента. Повторный рендеринг функционального компонента на самом деле является повторным выполнением текущей функции. При каждом рендеринге функционального компонента внутреннийstate, функция и переданныйpropsявляются независимыми.

Например:

// 第一次渲染
function Counter() {
    // 第一次渲染,count = 0
    const [count, setCount] = useState(0);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}


// 点击 p 标签触发第二次渲染
function Counter() {
    // 第二次渲染,count = 1
    const [count, setCount] = useState(0);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

// 点击 p 标签触发第三次渲染
function Counter() {
    // 第三次渲染,count = 2
    const [count, setCount] = useState(0);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

// ...

Методы, объявленные в функциональных компонентах, аналогичны. Таким образом, каждый кадр, отображаемый в функциональном компоненте, соответствует своему независимомуstate,function,props.

2. useState/useReducer

useState VS setState

  • useStateРаботает только с функциональными компонентами,setStateРаботает только с компонентами класса

  • useStateВы можете объявить более одного в компоненте функции, а значения состояния в компоненте класса должны быть объявлены вthisизstateв объекте

  • В основном,stateПри изменении:

    • useStateИсправлятьstateкогда то же самоеuseStateЗаявленная стоимость будетПереопределить обработку, несколькоuseStateОбъявленное значение сработаетвизуализировать несколько раз

    • setStateИсправлятьstateвремя, много разsetStateобъекты будутОбработка слияния

  • useStateИсправлятьstateПри установке одного и того же значения функциональный компонент не будет перерисовываться, а будет унаследованComponentкомпоненты класса, даже еслиsetStateЭто же значение также активирует рендеринг

useState VS useReducer

Первоначальный значение

  • useStateПри установке начального значения, если начальное значение является значением, вы можете установить его напрямую.Если это значение, возвращаемое функцией, рекомендуется использовать функцию обратного вызова для его установки.
const initCount = c => {
    console.log('initCount 执行');
    return c * 2;
};

function Counter() {
    const [count, setCount] = useState(initCount(0));

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

найдет дажеCounterКогда компонент повторно редит не болееcountпереназначить начальное значение, ноinitCountфункция будет выполняться повторно

Изменено на функцию обратного вызова:

const initCount = c => {
    console.log('initCount 执行');
    return c * 2;
};

function Counter() {
    const [count, setCount] = useState(() => initCount(0));

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

В настоящее время,initCountфункция будет толькоCounterВыполняется при инициализации компонента, а затем независимо от того, как компонент отрисовывается,initCountфункция больше не будет выполняться

  • useReducerКогда начальное значение установлено, начальное значение может быть значением только может быть только значением, и способ функции обратного вызова нельзя использовать.
    • Если это возвращаемое значение функции выполнения, то при повторном рендеринге компонента функция выполнения все равно будет выполняться.

Изменить статус

  • useStateПри изменении состояния то же самоеuseStateЗаявленное состояние будет переопределено
function Counter() {
    const [count, setCount] = useState(0);

    return (
        <p
            onClick={() => {
                setCount(count + 1);
                setCount(count + 2);
            }}
        >
            clicked {count} times
        </p>
    );
}

в текущем интерфейсеcountизstepда 2

useState 效果

  • useReducerПри изменении состояния несколько разdispatchБудет выполняться по порядку, отрисовывая компоненты по очереди
function Counter() {
    const [count, dispatch] = useReducer((x, payload) => x + payload, 0);

    return (
        <p
            onClick={() => {
                dispatch(1);
                dispatch(2);
            }}
        >
            clicked {count} times
        </p>
    );
}

в текущем интерфейсеcountизstepда 3

useReducer 效果

снижениеuseReducerНачальное значение , почему его нельзя восстановить

Например следующий пример:

const initPerson = { name: '小明' };

const reducer = function (state, action) {
    switch (action.type) {
        case 'CHANGE':
            state.name = action.payload;
            return { ...state };
        case 'RESET':
            return initPerson;
        default:
            return state;
    }
};

function Counter() {
    const [person, dispatch] = useReducer(reducer, initPerson);
    const [value, setValue] = useState('小红');

    const handleChange = useCallback(e => setValue(e.target.value), []);

    const handleChangeClick = useCallback(() => dispatch({ type: 'CHANGE', payload: value }), [value]);

    const handleResetClick = useCallback(() => dispatch({ type: 'RESET' }), []);

    return (
        <>
            <p>name: {person.name}</p>
            <input type="text" value={value} onChange={handleChange} />
            <br />
            <br />
            <button onClick={handleChangeClick}>修改</button> |{' '}
            <button onClick={handleResetClick}>重置</button>
        </>
    );
}

Нажмите кнопку «Изменить», чтобы изменить объект.nameИзмените его на Little Red, нажмите кнопку сброса и восстановите исходный объект. Но давайте посмотрим на эффект:

unreset

можно увидетьnameПосле изменения Xiaohong, как бы вы ни нажимали кнопку сброса, его нельзя восстановить.

Это потому, что вinitPersonкогда мы изменилисьstateсвойство, которое приводит к начальному значениюinitPersonизменилось, поэтому послеRESET, даже если вернетсяinitPerson``,但是nameЗначение по-прежнему маленькое красное.

Поэтому, когда мы изменяем данные, мы должны обращать внимание не на выполнение атрибутивных операций с исходными данными, а на воссоздание новых объектов для работы. Например, внесите следующие изменения:

// ...

const reducer = function (state, action) {
    switch (action.type) {
        case 'CHANGE':
            // !修改后的代码
            const newState = { ...state, name: action.payload }
            return newState;
        case 'RESET':
            return initPerson;
        default:
            return state;
    }
};

// ...

Взгляните на измененный эффект, вы можете сбросить его как обычно:

reset

3. использоватьЭффект

useEffectОсновное использование:

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        console.log('count: ', count);
    });

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

за кликpЭтикетка,CounterКомпоненты будут перерендерены, и вы увидите в консоли, что естьlogРаспечатать.

использоватьuseEffectмоделированиеcomponentDidMount

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        console.log('count: ', count);
        // 设置依赖为一个空数组
    }, []);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

БудуuseEffectДля зависимостей задан пустой массив, как видите, консоль выводит вывод только при первом рендеринге компонента. после этого неважноcountКак обновить, больше не будет печатать.

использоватьuseEffectмоделированиеcomponentDidUpdate

  • Используйте условие, чтобы определить, является ли зависимость исходным значением, если нет, перейдите к логике обновления.
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        if (count !== 0) {
          console.log('count: ', count);
        }
    }, [count]);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

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

  • использоватьuseRefУстановите начальное значение для сравнения
function Counter() {
    const [count, setCount] = useState(0);
    const firstRender = useRef(true);

    useEffect(() => {
        if (firstRender.current) {
          firstRender.current = false;
        } else {
          console.log('count: ', count);
        }
    }, [count]);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

использоватьuseEffectмоделированиеcomponentWillUnmount

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        console.log('count: ', count);

        return () => {
          console.log('component will unmount')
        }
    }, []);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

useEffectФункция, возвращаемая в обернутой функции, будет выполняться при повторном отображении функционального компонента и очистке предыдущего кадра данных. Таким образом, эта функция может сделать некоторую очистку. еслиuseEffectДанные зависимости представляют собой пустой массив, тогда при выполнении функции возврата это означает, что компонент фактически выгружен.

ДатьuseEffectнастраиватьзависимости - пустой массив,а такжевернуть функцию, то возвращаемая функция эквивалентнаcomponentWillUnmount

Обратите внимание, что зависимости должны быть установлены в пустой массив. Если это не пустой массив, то эта функция срабатывает не при размонтировании компонента, а при повторном рендеринге компонента для очистки данных предыдущего кадра.

существуетuseEffectправильно дляDOMНастройка прослушивателей событий

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const handleClick = function() {
            console.log('count: ', count);
        }
        window.addEventListener('click', handleClick, false)

        return () => {
          window.removeEventListener('click', handleClick, false)
        };
    }, [count]);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

существуетuseEffectНастройте прослушиватели событий вreturnВ функции убраны побочные эффекты, а события прослушивания отменены

существуетuseEffect、useCallback、useMemoполучен изstate、propsПочему старое значение

Как мы только что сказали, каждый кадр функционального компонента будет иметь свой собственный независимыйstate、function、props. а такжеuseEffect、useCallback、useMemoИмеет функцию кэширования.

Поэтому мы берем переменную под текущую соответствующую область видимости функции. Если зависимости установлены неправильно, тоuseEffect、useCallback、useMemoОн не будет выполняться повторно, а используемые в нем переменные по-прежнему имеют прежние значения.

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
      const handleClick = function() {
        console.log('count: ', count);
      }
        window.addEventListener('click', handleClick, false)

        return () => {
          window.removeEventListener('click', handleClick, false)
        };
    }, []);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

Или предыдущий пример, если в это время даноuseEffectустановите пустой массив в качестве зависимости, тогда что угодноcountСколько раз менять, нажмитеwindow, распечатанныйcountеще 0

useEffectПочему бесконечное выполнение происходит в

  • не дляuseEffectустановить зависимости, а вuseEffectобновление вstate, что приведет к бесконечной перерисовке интерфейса
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
      setCount(count + 1);
    });

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

Эта ситуация приведет к бесконечному повторному рендерингу интерфейса, потому что никакие зависимости не установлены.Если мы хотим предоставить интерфейс при первом рендеринге интерфейсаcountЧтобы установить новое значение, просто установите пустой массив для зависимостей.

После модификации: будет установлено только при инициализацииcountстоимость

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
      setCount(count + 1);
    }, []);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

Приведенный выше пример - когда зависимости отсутствуют, будут проблемы, тогда будут проблемы, когда зависимости установлены нормально.

  • На данный момент есть требование: каждый разcountПри добавлении нам нужно перевернуть страницу (page+1), посмотрите, как написать:

Поскольку мы зависим отcount, чтобы включить в зависимостиcount, при измененииpageнужно зависеть отpage, поэтому зависимости также должны включатьpage

function Counter() {
    const [count, setCount] = useState(0);
    const [page, setPage] = useState(0);

    useEffect(() => {
        setPage(page + 1);
    }, [count, page]);

    return (
        <>
            <p onClick={() => setCount(count + 1)}>clicked {count} times</p>
            <p>page: {page}</p>
        </>
    );
}

В это время это также приведет к тому, что интерфейс будет бесконечно повторяться, поэтому измените его в это время.pageизмените его на функцию и удалите из зависимостиpageТолько что

После модификации: можно добиться эффекта и избежать повторного рендеринга

function Counter() {
    const [count, setCount] = useState(0);
    const [page, setPage] = useState(0);

    useEffect(() => {
        setPage(p => p + 1);
    }, [count]);

    return (
        <>
            <p onClick={() => setCount(count + 1)}>clicked {count} times</p>
            <p>page: {page}</p>
        </>
    );
}

4. Конкуренция

Выполнение раньше, но возврат позже приведет к неправильной перезаписи значения состояния.

существуетuseEffect, могут быть сценарии, в которых делаются сетевые запросы, и мы будем проходить в соответствии с родительским компонентомid, чтобы инициировать сетевой запрос,idПри внесении изменений запрос делается снова.

function App() {
    const [id, setId] = useState(0);

    useEffect(() => {
        setId(10);
    }, []);

    // 传递 id 属性
    return <Counter id={id} />;
}


// 模拟网络请求
const fetchData = id =>
    new Promise(resolve => {
        setTimeout(() => {
            const result = `id 为${id} 的请求结果`;
            resolve(result);
        }, Math.random() * 1000 + 1000);
    });


function Counter({ id }) {
    const [data, setData] = useState('请求中。。。');

    useEffect(() => {
        // 发送网络请求,修改界面展示信息
        const getData = async () => {
            const result = await fetchData(id);
            setData(result);
        };
        getData();
    }, [id]);

    return <p>result: {data}</p>;
}

Показать результаты:

竞态问题

В приведенном выше примере, после нескольких обновлений страницы, вы можете увидеть, что конечный результат иногда отображаетсяid 为 0 的请求结果, иногдаid 为 10 的结果. Правильным результатом должен быть «результат запроса с идентификатором 10». Это проблема конкуренции.

Решение:

  • Отменить асинхронную операцию
// 存储网络请求的 Map
const fetchMap = new Map();

// 模拟网络请求
const fetchData = id =>
    new Promise(resolve => {
        const timer = setTimeout(() => {
            const result = `id 为${id} 的请求结果`;
            // 请求结束移除对应的 id
            fetchMap.delete(id);
            resolve(result);
        }, Math.random() * 1000 + 1000);

        // 设置 id 到 fetchMap
        fetchMap.set(id, timer);
    });

// 取消 id 对应网络请求
const removeFetch = (id) => {
  clearTimeout(fetchMap.get(id));
}

function Counter({ id }) {
    const [data, setData] = useState('请求中。。。');

    useEffect(() => {
        const getData = async () => {
            const result = await fetchData(id);
            setData(result);
        };
        getData();
        return () => {
            // 取消对应网络请求
            removeFetch(id)
        }
    }, [id]);

    return <p>result: {data}</p>;
}

Показать результаты:

解决竞态问题

На этом этапе, независимо от того, как вы обновляете страницу, она будет отображать толькоid 为 10 的请求结果.

  • Установите логическую переменную для отслеживания
// 模拟网络请求
const fetchData = id =>
    new Promise(resolve => {
        setTimeout(() => {
            const result = `id 为${id} 的请求结果`;
            resolve(result);
        }, Math.random() * 1000 + 1000);
    });

function Counter({ id }) {
    const [data, setData] = useState('请求中。。。');

    useEffect(() => {
        let didCancel = false;
        const getData = async () => {
            const result = await fetchData(id);
            if (!didCancel) {
                setData(result);
            }
        };
        getData();
        return () => {
            didCancel = true;
        };
    }, [id]);

    return <p>result: {data}</p>;
}

Можно обнаружить, что независимо от того, как страница обновляется в это время, она будет отображать толькоid 为 10 的请求结果.

5. Как сохранить ненужное в функциональных компонентахstate,propsзначение

функциональный компонент неthisуказал, поэтому, чтобы сохранитьэкземпляр компонентасвойства, вы можете использоватьuseRefработать

функциональный компонентrefиметь возможностьзакрытие проникновенияСпособность. Путем преобразования значения обычного типа в значение сcurrentСсылка на объект свойства для обеспечения актуальности значения свойства при каждом доступе к нему.

Гарантированный доступ в каждом кадре функционального компонентаstateзначение такое же

  • Посмотрите, не используете ли выuseRefВ случае каждого кадраstateкак печатается значение
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const handleClick = function() {
            console.log('count: ', count);
        }
        window.addEventListener('click', handleClick, false)
    });

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

нажмите сначалаpTab 5 раз, затем нажмитеwindowобъект, вы можете увидеть результат печати:

不使用 ref 时

  • использоватьuseRefПосле этого в каждом кадреrefкак печатается значение
function Counter() {
    const [count, setCount] = useState(0);
    const countRef = useRef(count);

    useEffect(() => {
        // 将最新 state 设置给 countRef.current
        countRef.current = count;
        const handleClick = function () {
            console.log('count: ', countRef.current);
        };
        window.addEventListener('click', handleClick, false);
    });

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

Та же операция, что и раньше, сначала нажмитеpTab 5 раз, затем нажмитеwindowинтерфейс, вы можете увидеть результат печати

使用 ref 时

использоватьuseRefТо есть можно гарантировать, что функции, к которым осуществляется доступ в каждом кадре функционального компонентаstateЦенности те же.

Как сохранить свойства экземпляра функционального компонента

Функциональные компоненты не имеют экземпляров, поэтому свойства не могут быть смонтированы вthisначальство. Тогда, если мы хотим создать не-state,propsПеременные можно создавать и уничтожать с помощью функциональных компонентов, как это сделать?

Так же можно пройтиuseRef,useRefне только можетDOM, вы также можете преобразовать обычные переменные вcurrentобъект недвижимости

Например, мы хотим установитьModelЭкземпляр , когда компонент создается, генерируетсяmodelНапример, после того, как компонент будет уничтожен и воссоздан, будет автоматически сгенерирован новый.modelпример

class Model {
    constructor() {
        console.log('创建 Model');
        this.data = [];
    }
}

function Counter() {
    const [count, setCount] = useState(0);
    const countRef = useRef(new Model());

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

Согласно этому методу записи, когда функциональный компонент создается, сгенерированныйModelэкземпляр, смонтированный наcountRefизcurrentхарактеристики. При повторном рендеринге больше не выдаетcountRefпереназначить.

Это означает, что один и тот же используется до тех пор, пока компонент не будет удален.Modelнапример, после удаления текущийmodelЭкземпляр также будет уничтожен.

Внимательно наблюдайте за выводом консоли, вы обнаружите, что хотяcountRefне переназначается, но при повторном рендеринге компонентаModelКонструктор по-прежнему выполняется несколько раз

Итак, на данный момент мы можем заимствоватьuseStateхарактеристики, перепишите его.

class Model {
    constructor() {
        console.log('创建 Model');
        this.data = [];
    }
}

function Counter() {
    const [count, setCount] = useState(0);
    const [model] = useState(() => new Model());
    const countRef = useRef(model);

    return <p onClick={() => setCount(count + 1)}>clicked {count} times</p>;
}

При таком использовании его можно использовать без модификацииstateслучае, использоватьmodelНекоторые свойства экземпляра могут сделатьflag, который может быть источником данных или дажеMobxизstoreиспользовать.

Шесть, используйте обратный вызов

Как и в вопросе, как избежать частого изменения зависимостейuseCallbackКак часто?

function Counter() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(count + 1);
    }, [count]);

    return <p onClick={handleClick}>clicked {count} times</p>;
}

Здесь мы положилиclickСобытие извлечено, используяuseCallbackпакет, но на самом деле он не очень хорошо работал.

потому чтоCounterПовторный рендеринг компонента в настоящее время зависит только отcountменяется, так что вотuseCallbackНет никакой разницы между использованием и не использованием.

использоватьuseReducerзаменятьuseState

можно использоватьuseReducerзаменить.

function Counter() {
    const [count, dispatch] = useReducer(x => x + 1, 0);

    const handleClick = useCallback(() => {
        dispatch();
    }, []);

    return <p onClick={handleClick}>clicked {count} times</p>;
}

useReducerвернутьdispatchфункция поставляется сmemoize, не меняется при нескольких рендерингах. Таким образом, вuseCallbackне обязательноdispatchкак зависимость.

КsetStateпередаточная функция в

function Counter() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(c => c + 1);
    }, []);

    return <p onClick={handleClick}>clicked {count} times</p>;
}

существуетsetCountПри использовании функции в качестве параметра в , полученное значение является последнимstateзначение, поэтому операции могут выполняться с этим значением.

пройти черезuseRefВыполнение закрытия закрытия

function Counter() {
    const [count, setCount] = useState(0);
    const countRef = useRef(count);

    useEffect(() => {
        countRef.current = count;
    }, [count]);

    const handleClick = useCallback(() => {
        setCount(countRef.current + 1);
    }, []);

    return <p onClick={handleClick}>clicked {count} times</p>;
}

Такого же эффекта можно добиться и таким образом. Но это не рекомендуется, так как вам не только придется писать больше кода, но и могут возникнуть неожиданные проблемы.

Семь, используйте памятку

описано вышеuseCallbackнекоторые проблемы и решения. Посмотрите нижеuseMemo.

useMemoа такжеReact.memoразные:

  • useMemoЭто оптимизация и кеширование некоторых данных внутри компонента и ленивая их обработка.
  • React.memoОн обертывает функциональный компонент и обертывает внутренние компоненты компонента.state,propsПроизводится поверхностное сравнение, чтобы определить, требуется ли рендеринг.

useMemoа такжеuseCallbackразница

  • useMemoВозвращаемое значение является значением, может быть свойством, может быть функцией (включая компоненты)
  • useCallbackВозвращаемое значение может быть только функцией

следовательно,useMemoв какой-то мере заменитьuseCallback, эквивалентное условие:useCallback(fn, deps) => useMemo(() => fn, deps)

Поэтому сказанное выше оuseCallbackНекоторые моменты оптимизации также применимы кuseMemo.

8. Следует ли часто использовать useCallback и useMemo?

Вот мое скромное мнение:Не рекомендуется частое использование

Дамы и господа, не начинайте распылять, позвольте высказать свое мнение

причина:

  • useCallback и useMemo на самом деле вызываются как функции в функциональных компонентах, поэтому первый параметр — это функция обратного вызова, которую мы передаем.Независимо от того, используются ли useCallback и useMemo, эта функция обратного вызова будет создана, поэтому она не снижает стоимость создания функции.
  • Мало того, что это не может снизить стоимость создания, но после использования useCallback и useMemo зависимость второго параметра также должна выполнять поверхностное сравнение каждый раз при рендеринге, что фактически увеличивает стоимость сравнения данных.
  • Таким образом, использование useCallback и useMemo может не только снизить нагрузку, но и увеличить стоимость сравнения, поэтому не рекомендуется использовать его часто.

Причина уже давно объяснена.Являются ли useCallback и useMemo бессмысленными?Конечно нет.Если эффекта вообще нет, то зачем React его предоставлять.

Он все еще используется, но нам нужно судить в зависимости от ситуации и когда его использовать.

Вот несколько сценариев, в которых применимы useCallback и useMemo.

Сценарии использования useCallback

  1. Сценарий 1: Необходимо оптимизировать производительность подкомпонентов

    В этом примере приложение передаст свойство функции onClick дочернему компоненту Foo.

    Код перед оптимизацией с использованием useCallback

    App.js

    import React, { useState } from 'react';
    import Foo from './Foo';
    
    function App() {
        const [count, setCount] = useState(0);
    
        const fooClick = () => {
            console.log('点击了 Foo 组件的按钮');
        };
    
        return (
            <div style={{ padding: 50 }}>
                <Foo onClick={fooClick} />
                <p>{count}</p>
                <button onClick={() => setCount(count + 1)}>count increment</button>
            </div>
        );
    }
    
    export default App;
    

    Foo.js

    import React from 'react';
    
    const Foo = ({ onClick }) => {
    
        console.log('Foo 组件: render');
        return <button onClick={onClick}>Foo 组件中的 button</button>;
    
    };
    
    export default Foo;
    

    Нажмите кнопку в присоединении подсчета приложений, вы можете увидеть каждый раз, когда Foo повторно видно, но фактически, когда подсчет изменяется, родительский компонент для повторного визуализации, и подбаллии не нуждаются в рекретере, ток Ситуация не будет проблем.

    Но если компонент Foo очень сложный и огромный компонент, то необходимо оптимизировать компонент Foo, и useCallback может пригодиться.

    Код оптимизирован с помощью useCallback

    App.jsОберните свойство функции, переданное дочернему компоненту, с помощью useCallback в

    import React, { useCallback, useState } from 'react';
    import Foo from './Foo';
    
    function App() {
        const [count, setCount] = useState(0);
    
        const fooClick = useCallback(() => {
            console.log('点击了 Foo 组件的按钮');
        }, []);
    
        return (
            <div style={{ padding: 50 }}>
                <Foo onClick={fooClick} />
                <p>{count}</p>
                <button onClick={() => setCount(count + 1)}>count increment</button>
            </div>
        );
    }
    
    export default App;
    

    Foo.jsИспользуйте React.memo для переноса компонентов (компоненты класса наследуют PureComponent Тот же эффект)

    import React from 'react';
    
    const Foo = ({ onClick }) => {
    
        console.log('Foo 组件: render');
        return <button onClick={onClick}>Foo 组件中的 button</button>;
    
    };
    
    export default React.memo(Foo);
    

    Нажмите еще разcount incrementкнопка, вы можете видеть, что родительский компонент обновляется, но дочерний компонент не будет сброшенrender

  2. Сценарий 2: Нужен как другойhooksзависимости, здесь используйте толькоuseEffectдать демонстрацию

    В этом примере по состояниюpageизменения для повторного запроса сетевых данных, когдаpageизменения, мы надеемся вызватьuseEffectвызовите сетевой запрос, в то время какuseEffectназывается вgetDetailфункцию, чтобы использовать последнююpage, так что вuseEffectнужно зависеть отgetDetailфункция для вызова последнейgetDetail

    использоватьuseCallbackкод перед обработкой

    App.js

    import React, { useEffect, useState } from 'react';
    
    const request = (p) =>
        new Promise(resolve => setTimeout(() => resolve({ content: `第 ${p} 页数据` }), 300));
    
    function App() {
        const [page, setPage] = useState(1);
        const [detail, setDetail] = useState('');
    
        const getDetail = () => {
            request(page).then(res => setDetail(res));
        };
    
        useEffect(() => {
            getDetail();
        }, [getDetail]);
    
        console.log('App 组件:render');
    
        return (
            <div style={{ padding: 50 }}>
                <p>Detail: {detail.content}</p>
                <p>Current page: {page}</p>
                <button onClick={() => setPage(page + 1)}>page increment</button>
            </div>
        );
    }
    
    export default App;
    

    Но, согласно приведенному выше письму, это приведет кAppКомпонент зацикливается бесконечноrender, то нужно использоватьuseCallbackобрабатывать

    использоватьuseCallbackобработанный код

    App.js

    import React, { useEffect, useState, useCallback } from 'react';
    
    const request = (p) =>
        new Promise(resolve => setTimeout(() => resolve({ content: `第 ${p} 页数据` }), 300));
    
    function App() {
        const [page, setPage] = useState(1);
        const [detail, setDetail] = useState('');
    
        const getDetail = useCallback(() => {
            request(page).then(res => setDetail(res));
        }, [page]);
    
        useEffect(() => {
            getDetail();
        }, [getDetail]);
    
        console.log('App 组件:render');
    
        return (
            <div style={{ padding: 50 }}>
                <p>Detail: {detail.content}</p>
                <p>Current page: {page}</p>
                <button onClick={() => setPage(page + 1)}>page increment</button>
            </div>
        );
    }
    
    export default App;
    

    В этот момент вы можете видеть,Appкомпоненты могут быть выполнены нормальноrender. Используйте только здесьuseEffectдемонстрировать, как и другиеhooks, вам также необходимо соответствующим образом оптимизировать

  3. useCallbackКраткое описание сценариев использования:

    1. Передайте свойства функции субкомпонентам, и когда субкомпоненты необходимо оптимизировать, свойства функции должны бытьuseCallbackпакет

    2. функционировать как другиеhooks, функция должна бытьuseCallbackпакет

Сценарии использования useMemo

  1. такой жеuseCallbackСценарий 1: когда требуется оптимизация производительности подкомпонентов, использование в основном такое же

  2. такой жеuseCallbackСценарий 2: Нужен как другойhooksИспользование в основном такое же, когда зависимости

  3. Когда требуется большое количество или сложные операции, для повышения производительности можно использоватьuseMemoкеш данных

    Здесь также используется функция кэширования данных useMemo.До изменения зависимостей функции, завернутые в useMemo, не будут выполняться повторно.

    Посмотрите на пример ниже,AppКомпонент имеет два состояния:countа такжеNumberмножествоdataSource, нажмитеincrementкнопка,countувеличится, нажмитеfreshкнопку, он будет получен сноваdataSource, но интерфейс не нужно отображатьdataSource, но нужно показатьdataSourceсумма всех элементов в , поэтому нам нужна новая переменнаяsumдля переноски и отображения на странице.

    См. код ниже

    использоватьuseMemoкод перед оптимизацией

    App.js

    import React, { useState } from 'react';
    
    const request = () =>
        new Promise(resolve =>
            setTimeout(
                () => resolve(Array.from({ length: 100 }, () => Math.floor(100 * Math.random()))),
                300
            )
        );
    
    function App() {
        const [count, setCount] = useState(1);
        const [dataSource, setDataSource] = useState([]);
    
        const reduceDataSource = () => {
            console.log('reduce');
            return dataSource.reduce((reducer, item) => {
                return reducer + item;
            }, 0);
        };
    
        const sum = reduceDataSource();
    
        const refreshClick = () => {
            request().then(res => setDataSource(res));
        };
    
        return (
            <div style={{ padding: 50 }}>
                <p>DataSource 元素之和: {sum}</p>
                <button onClick={refreshClick}>Refresh</button>
                <p>Current count: {count}</p>
                <button onClick={() => setCount(count + 1)}>increment</button>
            </div>
        );
    }
    
    export default App;
    

    Откройте консоль, вы можете видеть, что независимо от того, нажмитеincrementилиRefreshкнопка,reduceDataSourceфункция будет выполнена один раз, ноdataSourceимеет 100 элементов, поэтому мы определенно хотимdataSourceПересчитывать при измененииsumзначение, на этот разuseMemoЭто удобно.

    использоватьuseMemoоптимизированный код

    App.js

    import React, { useMemo, useState } from 'react';
    
    const request = () =>
        new Promise(resolve =>
            setTimeout(
                () => resolve(Array.from({ length: 100 }, () => Math.floor(100 * Math.random()))),
                300
            )
        );
    
    function App() {
        const [count, setCount] = useState(1);
        const [dataSource, setDataSource] = useState([]);
    
        const sum = useMemo(() => {
            console.log('reduce');
            return dataSource.reduce((reducer, item) => {
                return reducer + item;
            }, 0);
        }, [dataSource]);
    
        const refreshClick = () => {
            request().then(res => setDataSource(res));
        };
    
        return (
            <div style={{ padding: 50 }}>
                <p>DataSource 元素之和: {sum}</p>
                <button onClick={refreshClick}>Refresh</button>
                <p>Current count: {count}</p>
                <button onClick={() => setCount(count + 1)}>increment</button>
            </div>
        );
    }
    
    export default App;
    

    На данный момент вы можете увидеть это, только нажавRefreshкнопка,useMemo中的函数才会重新执行。 нажмитеincrementКогда кнопка нажата, сумма остается предыдущим кэшированным результатом и не будет пересчитана.

  4. useMemoКраткое описание сценариев использования:

    1. перейти к дочерним компонентамтип ссылкисвойств, а когда необходимо оптимизировать подкомпоненты, свойства должны бытьuseMemoпакет

    2. значение ссылочного типа, как и другиеhooksзависимости, вам нужно использоватьuseMemoОбернуть, вернуть значение свойства

    3. Когда требуется большое количество или сложные операции, для повышения производительности можно использоватьuseMemoКэшировать данные для экономии вычислительных затрат

Поэтому во время использования useCallback и useMemo, если в этом нет необходимости, нет необходимости его использовать, а частое использование может увеличить стоимость сравнения зависимостей и снизить производительность.

Девять, как вызвать состояние или метод дочернего компонента в родительском компоненте

В функциональном компоненте нет экземпляра компонента, он не похож на компонент класса, подсборку или вызовы методов в состоянии, связанном с помощью примера подсборки.

Итак, в функциональном компоненте, как вызвать состояние или метод дочернего компонента в родительском компоненте? Ответ заключается в использованииuseImperativeHandle

грамматика

useImperativeHandle(ref, createHandle, [deps])

  • Первый параметр — это значение ref, которое можно передать через атрибуты или использовать с forwardRef.

  • Второй параметр — это функция, которая возвращает объект, и свойства в объекте будут смонтированы на текущем свойстве первого параметра ref.

  • Третий параметр — это набор зависимых элементов, таких же, как useEffect, useCallback, useMemo.При изменении зависимости второй параметр будет повторно выполнен и перемонтирован в текущее свойство первого параметра.

Применение

Уведомление:

  • Третий параметр зависимость должна быть заполнена по мере необходимости, если меньше, то это приведет к тому, что возвращаемое свойство объекта будет ненормальным, а если слишком много, то вызоветcreateHandleПовторить
  • компонент илиhook, для того жеref, можно использовать только один разuseImperativeHandle, если он повторяется много раз, он будет выполнен позжеuseImperativeHandleизcreateHandleВозвращаемое значение заменит ранее выполненноеuseImperativeHandleизcreateHandleвозвращаемое значение

Foo.js

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

const Foo = ({ actionRef }) => {
    const [value, setValue] = useState('');

    /**
     * 随机修改 value 值的函数
     */
    const randomValue = useCallback(() => {
        setValue(Math.round(Math.random() * 100) + '');
    }, []);

    /**
     * 提交函数
     */
    const submit = useCallback(() => {
        if (value) {
            alert(`提交成功,用户名为:${value}`);
        } else {
            alert('请输入用户名!');
        }
    }, [value]);

    useImperativeHandle(
        actionRef,
        () => {
            return {
                randomValue,
                submit,
            };
        },
        [randomValue, submit]
    );

    /* !! 返回多个属性要按照上面这种写法,不能像下面这样使用多个 useImperativeHandle
      useImperativeHandle(actionRef, () => {
          return {
              submit,
          }
      }, [submit])

      useImperativeHandle(actionRef, () => {
          return {
              randomValue
          }
      }, [randomValue])
  */

    return (
        <div className="box">
            <h2>函数组件</h2>
            <section>
                <label>用户名:</label>
                <input
                    value={value}
                    placeholder="请输入用户名"
                    onChange={e => setValue(e.target.value)}
                />
            </section>
            <br />
        </div>
    );
};

export default Foo;

App.js

import React, { useRef } from 'react';
import Foo from './Foo'

const App = () => {
    const childRef = useRef();

    return (
        <div>
            <Foo actionRef={childRef} />
            <button onClick={() => childRef.current.submit()}>调用子组件的提交函数</button>
            <br />
            <br />
            <button onClick={() => childRef.current.randomValue()}>
                随机修改子组件的 input 值
            </button>
        </div>
    );
};


10. Справочные документы

написать на обороте

Если в письме есть что-то неправильное или неточное, вы можете высказать свое ценное мнение, большое спасибо.

Если вам нравится или помогаете, добро пожаловать в Star, что также является поощрением и поддержкой для автора.