Давайте сядем на поезд Томаса для Крюков

React.js

В React 16, за исключением архитектуры Fiber, хуки являются самой интересной функцией.По сравнению с компонентами класса, функциональные компоненты, поддерживаемые хуками, сильно отличаются с точки зрения написания и мышления и часто более лаконичны и освежают (более низкая энтропия, ослабляя концепцию жизненного цикла), решая надоедливую проблему этого указателя, он по-прежнему очень благоухает.

Но с рациональной точки зрения, пока что хуки — это все еще этап со множеством ям, а также отсутствует систематическая лучшая практика.Позвольте мне рассказать о некоторых моих поверхностных представлениях о хуках.

Что ж, пора идти.

Общие крючки

useState

Здесь мы можем думать о состоянии как this.state, которое мы используем в компоненте класса.

Каждая из наших операций setState после изменения значения будет запускать операцию повторного рендеринга, которая вызовет обновление страницы (но если изменений нет, повторный рендеринг не будет запущен, мы будем использовать эту функцию, чтобы сделать интересное дело позже) .

При этом setState может принимать в качестве параметра функцию, и в это время мы можем получить последнее значение состояния (в первом параметре обратного вызова).

import React, { useState } from 'react';

function App() {
    const [ state, setState ] = useState(0);
    return (
        <span>{state}</span>
    )
}

useEffect

Можно сказать, что useEffect наиболее похож на хук цикла объявления среди всех API хуков.Легко понять, что если массив зависимостей пуст, то он эквивалентен componentDidMount, но так ли это на самом деле?

Мы можем понять наш функциональный компонент таким образом.Каждый запуск функционального компонента эквивалентен рендерингу в компоненте класса, и его закрытие сохраняется в каждом раунде.Поэтому наш useEffect фактически сохраняет состояние и состояние своего текущего раунда. Состояние реквизита (если зависимость не обновляется, то и состояние не обновляется), которое представляет собой отношение между жизненным циклом useEffect и componentDidMount.

import React, { useEffect } from 'react';
function App() {
    useEffect(() => {
        console.log('I am mount');
        return () => {
            console.log('before next run, I am cleaned');
        }
    }, []);

useLayoutEffect

Разница между useLayoutEffect и useEffect заключается в том, что useLayoutEffect будет выполняться до рендеринга DOM, а useEffect будет выполняться после рендеринга DOM, поэтому мы можем использовать эту функцию, чтобы избежать некоторых проблем с белым экраном, вызванных операциями после рендеринга DOM.

useCallback

useCallback может помочь нам кэшировать функции (useMemo тоже может это делать, но метод записи другой), а за счет ручного управления зависимостями может уменьшить обновление подкомпонентов из-за обновления функций (вызванные проблемы с производительностью очень очевидны )

import React, { useCallback } from 'react';
function App() {
    const cb = useCallback(() => { console.log('callback') }, []);
    return (
        <button onClick={cb}></button>
    )
}

useMemo

useMemo может предоставить возможности кэширования для наших функциональных компонентов, что в некоторых сценариях пересчета может уменьшить количество повторных вычислений и значительно повысить производительность. Конечно, для кэширования компонентов можно использовать и useMemo, который играет роль, аналогичную shouldComponentUpdate в компоненте класса.Давайте вручную будем контролировать обновление подкомпонентов, управляя зависимостями (конечно, стоимость ручного управления очень высока)

useRef

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

useRef можно использовать не только для хранения ссылки на элемент Dom (так и задумано), но и для хранения изменяемой переменной (вероятно, очень распространенной), которую нам нужно использовать между раундами рендеринга.

import React, { useRef } from 'react';
function App() {
  const td = useRef(1);
  console.log(td.current); // 1
  ...

useReducer

UseReducer в текущей версии на самом деле является слоем инкапсуляции useState, который реализует набор принципов редукции (предыдущая версия заключалась в том, что useState был слоем инкапсуляции useReducer)

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}

useContext

Предполагая, что у нас уже есть Context, а наши дочерние компоненты заключены в Provider, мы можем использовать useContext, чтобы получить значение напрямую, вместо использования обратного вызова для получения значения. В то же время мы также можем инкапсулировать некоторый контекст с помощью useContext, чтобы мы могли легко использовать данные в контексте в разных компонентах.

// 假定我们已经有 Context
function Child(props) {
    const { value } = useContext(Context);
    return (
      <div>
        {value}
      </div>
    )
}

Мы можем комбинировать Context, useReducer и useContext для создания собственного Redux.

const CTX = React.createContext(null);
const reducer = (state, action) => {
    switch(action.type) {
        default:
            reutrn state;
    }
}
const Context = function({ children }) {
    const [state, dispatch] = useReducer(reducer, {});
    return (
        <CTX.Provider value={ state, dispatch }>
            {children}
        </CTX.Provider>
    )
}

Как понять состояние в хуках

Следует установить представление о том, что в компоненте функции все состояния принадлежат ее замыканию, что приводит к Каждый раунд нашего рендера будет иметь свое закрытие, и все useEffect и useLayoutEffect находятся в закрытии своего последнего обновления. Хуки обрабатывают запросы

правильно обрабатывать зависимости

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

Запрос на размещение зависимостей в useEffect

Чтобы правильно обработать запрос, есть идея — поместить запрошенную функцию в useEffect, чтобы мы могли быть уверены, что всегда правильно обработаем проблему зависимости. обрабатывать состояние гонки Мы знаем, что в хуках каждое выполнение Render и каждое useEffect находится в закрытии своего собственного раунда, поэтому одна из наших идей по работе с условиями гонки исходит отсюда.

Наши изменения зависимостей вызовут нашу операцию ajax, поэтому, когда происходит второй запрос, последний эффект фактически достиг периода очистки побочных эффектов, поэтому функция в ответ выполняется, и наш флаг установлен в значение true, так что, когда Когда наш запрос возвращается, замыкание, где находится его эффект, может воспринимать состояние конца выполнения, тем самым отбрасывая старое значение и добиваясь правильной обработки состояния гонки.

useEffect(() => {
    let flag = false;
    ajax().then(res => {
        if (!flag) {
            //...do something
        }
    })
    return () => {
        flag = true;
    }
}, [deps])

запросить и инициировать разделение

просить

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

function App() {
    const [flag, setFlag] = useState(0);

    const ajax = () => {
       _ajax(props)
    };

    useEffect(() => {
        ajax();
    }, [flag]);

    return (
        ...
    )
}

вызывать

Одна вещь, на которую мы должны обратить внимание в это время, заключается в том, что наш флаг триггера должен быть изменен в конце (сначала предварительная операция - другие изменения состояния), чтобы гарантировать, что при обновлении нашего эффекта используемый индекс является последней функцией запроса ajax. .

Параметры без рендеринга сохраняются с помощью ref Поскольку мы всегда можем правильно получить значение ref в эффекте, когда наши параметры не участвуют в рендеринге, мы можем использовать ref, сгенерированный useRef, для управления ими, так что нам не нужно беспокоиться о параметрах, на которые ссылается ref. проблема с изменением (в то же время это не вызовет повторную визуализацию страницы)

const name = useRef('小明')
const ajax = useCallback(() => {
    ajax({ name })
}, []);

// 修改 param 直接操作 ref
name.current = '123';

Трюк с экстремальной производительностью

сократить вычисления

Обман массива зависимостей

Используйте обратный вызов setState для решения проблемы получения состояния потому что

const [state, setState] = useState(0);

// 利用 setState 的回调拿到最新的 state,返回原值,可以不触发 rerender(极端情况下可以用于性能优化)
const update = useCallback(() => {
    setState(state => {
        // 做你想做的任何事情
        return state;
    })
}, []);

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

const trigger = useCallback(() => {
    setState1(state1 => {
        setState2(state2 => {
            console.log(state1 + state2);
            return state2;
        })
        return state1;
    })
});

Используйте useReducer и setState для решения проблемы получения состояния и свойств. Из-за вышеописанного метода мы можем только гарантировать, что можем получить состояние без зависимостей, но мы не можем получить пропсы без зависимостей Итак, что мы можем сделать. Мы можем поместить редуктор useReducer в тело функции компонента функции и использовать диспетчеризацию, чтобы, наконец, вызвать редьюсер в последнем закрытии, чтобы гарантировать, что мы можем получить реквизиты в последнем состоянии.

function App({ a, b, c }) {
    const reducer = (state, action) => {
        switch(action.type) {
            case 'init':
                // 这里永远可以拿到最新的 a
                return Object.assign(state, { a: a });
            default:
                return state;
        }
    }
    const [state, dispatch] = useReducer(reducer, {});
    return (
        <div>{ state.a }</div>
    )
}

Уменьшить рендеринг

Мы можем сделать что-то вроде PureComponent в компоненте класса, мы можем обернуть большинство компонентов React.memo (добавится дополнительное сравнение, производительность не обязательно будет лучшей).

Используйте React.memo напрямую

Используя React.memo, мы можем провести поверхностное сравнение наших компонентов с React,

const Child = function({ a, b, c }) {
    return <div>{a}{b}{c}</div>
} 
export default React.memo(Child);

Детальное управление с помощью useMemo

function App({ a, b, c }) {
    const RenderComponent = useMemo(() => {
        return <div>{c}</div>
    }, [c]);
    return (
        <RenderComponent />
    )
}

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

Здесь вы можете использовать трюк, который я представил выше, чтобы уменьшить количество зависимостей и, таким образом, уменьшить количество повторных рендеров. Например: переключатель trigger.jsx

const useTrigger = () => {
    const [state, setState] = useState(false);
    const trigger = useCallback(() => {
        setState(ste => !ste);
    }, []);
    return { state, trigger };
}

// vs

const useTrigger = () => {
    const [state, setState] = useState(false);
    const trigger = useCallback(() => {
        setState(ste => !ste);
    }, [state]);
    return { state, trigger };
}

Практика крючков

Разговор о формах

Напишите формы, такие как двусторонняя привязка данных

const useInput = () => {
  const [value, setValue] = useState('');
  const onChange = val => {
    setValue(val.target.value);
  };
  return {
    value,
    onChange
  };
};

отправка формы

export const useSubmit = submitFunction => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [res, setRes] = useState(null);

  const trigger = useCallback(() => {
    try {
      if (_.isFunction(submitFunction)) {
        (async () => {
          let res = await submitFunction();
          if (res) {
            setRes(res);
          }
        })();
      }
    } catch (e) {
      setError(e);
    } finally {
      setLoading(true);
    }
  }, [submitFunction]);

  return [loading, res, error];
};

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

Много раз мы полагаемся на реквизиты для вычисления нашего состояния, и компонент класса предоставляет нам жизненный цикл getDerivedStateFromProps, чтобы мы могли выполнять аналогичные операции, но в хуках у нас нет такой концепции жизненного цикла, тогда мы Как это должно быть Готово?

Мы можем использовать useMemo для выполнения вычислений над реквизитами.Правильно обрабатывая зависимости, мы можем использовать функцию памяти useMemo для корректного обновления состояния с наименьшими затратами (дорогостоящее решение — присваивать значение каждому вычислению. переменные в замыканиях).

import React, { useMemo } from 'react';

function App({ data }) {
    // 只有 data 更新时重新计算
    const info = useMemo(() => {
        // 对 data 进行一系列的计算操作
        return newData;
    }, [data]);
}

Используйте хуки для возврата компонентов

Большая часть того, что я сказал ранее, касается использования хуков для решения логических задач, поэтому могут ли хуки возвращать для нас компонент, как компонент высокого порядка? Ответ — да, и, используя эту возможность, мы также можем упростить многие ситуации. .

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

export default function useModal() {
  const [show, setShow] = useState<boolean>(false);

  const openModal = useCallback(() => {
    setShow(true);
  }, []);

  const closeModal = useCallback(() => {
    setShow(false);
  }, []);

  const CusModal: React.SFC = ({ children, ...props }) => {
    return (
      <Modal
        visible={show}
        {...props}>
        {children}
      </Modal>
    )
  }

  return {
    show,
    setShow,
    openModal,
    closeModal,
    CusModal
  }
}

Использовать реф-хуки для некоторых неинвазивных операций (реакция официально не рекомендует) Поскольку ref может получить исходный dom, мы можем использовать эту функцию для выполнения некоторых операций, таких как перенос скрытых точек, навязчивых кодом, в ref (уменьшение вторжения в исходный код).

например: используйте ref для записи времени пребывания (можно делать неинвазивные закопанные точки)

export const useHoverTime = eventName => {
  const EV = `${ eventName}`;
  const ref = useRef(null);

  useEffect(() => {
    localStorage.setItem(EV, 0);
    return () => {
      const time = localStorage.getItem(EV);
      // do something 
      localStorage.setItem(EV, null);
    };
  }, []);

  useEffect(() => {
    let startTime = null;
    let endTime = null;
    const overHandler = () => {
      startTime = new Date();
    };
    const outHandler = () => {
      endTime = new Date();
      localStorage.setItem(
        EV,
        parseInt(localStorage.getItem(EV)) +
        parseInt(endTime - startTime)
      );
      startTime = 0;
      endTime = 0;
    };
    if (ref.current) {
      ref.current.addEventListener('mouseover', overHandler);
      ref.current.addEventListener('mouseout', outHandler);
    }
    return () => {
      if (ref.current) {
        ref.current.removeEventListener('mouseover', overHandler);
        ref.current.removeEventListener('mouseout', outHandler);
      }
    };
  }, [ref]);
  return ref;
};

React-hook-form использует ref для регистрации формы и перехвата отправки (лично я тоже думаю, что это очень странная идея)

Hooks with Immer.js

Сложность immutable.js очень высока, но иногда мы хотим, чтобы наше приложение React работало лучше и избавляло от ненужных повторных рендеров, тогда Immer.js — очень хороший выбор (на самом деле, dva также использует immer в качестве базовой библиотеки).

Мы можем использовать Immer для изменения состояния при использовании useReducer, чтобы наше последнее состояние было неизменным.

const reducer = (state, action) => {
  switch (action.type) {
    case 'initData':
      return produce(state, draft => {
        draft.data = action.data;
      });

Разрешить useReducer использовать экосистему промежуточного программного обеспечения Redux

С какой точки зрения комбинация useReducer + useContext + Context делает то же, что и традиционный Redux, так можно ли заставить наши нативные хуки использовать промежуточное ПО Redux (по сути, действие захвата, а API Redux не имеет значения)? ! Да, по сути, это равносильно переносу реализации мидлвара Redux на хуки, конечно, мы можем реализовать его сами, но библиотека react-use помогает нам интегрировать его, и мы можем использовать его напрямую.

// 创建增强了中间件的 reducer , 这里的例子增加了 redux-logger 与 redux-thunk
const useLoggerReducer = createReducer(logger, thunk);

export default function App() {
  const [state, dispatch] = useLoggerReducer(reducer, initState);

Таким образом, мы можем использовать промежуточное программное обеспечение, такое как redux-thunk и redux-saga, для обработки асинхронных задач, а также использовать redux-logger для печати действий и сравнения состояния до и после.

Создайте свой собственный CombineReducer (идея кода взята из Medium)

const combineReducers = (reducers) => {
    const keys = Object.keys(reducers);
    const initObj = {};
    keys.forEach(key => {
        let draftState = reducers[key](undefined, { type: '' });
        if (!draftState) {
            draftState = {};
            console.warn(
                `[Error]: 在combineReducers 中 Reducer 需要初始化!`
            );
        }
        initObj[key] = draftState;
    })
    return (state, action) => {
        keys.forEach(key => {
            const prevState = initObj[key];
            initObj[key] = reducers[key](prevState, action);
        });
        return { ...initObj };
    }
}

Объедините это с нашим улучшенным useReducer, и у нас есть редьюсер, который почти так же хорош, как и редукс.

Коллекция хуков сторонних инструментов, реагирующих на использование

Это может быть проект кастомных хуков, пользующийся наибольшей популярностью и вниманием в сообществе в настоящее время, предоставляющий множество пользовательских хуков (многие из них ароматные).react-use

Инструмент запроса хуков swr

Среди библиотек запросов, которые растут как грибы в хуках React, самой привлекательной является swr. Для получения подробной информации обратитесь к официальному репозиторию github.swr

Хуки сторонняя библиотека форм

Полезная библиотека форм react-hook-form. Для получения подробной информации обратитесь к официальному репозиторию github.react-hook-form