Настройка хуков в React

React.js

предисловие

Чтобы прочитать эту статью, вам нужно освоить базовое использование React Hooks.В этой статье есть общее введение.Полное использование хуков в проектах ReactДобро пожаловать читать.

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

Жизненный цикл компонента класса

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

componentDidMount

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

const useDidMount = callback => {
  useEffect(callback, []);
};

Вышеупомянутый простой пользовательский хук, просто нужно использовать его в необходимых компонентах.importЗаходите и пользуйтесь.

componentDidUpdate

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

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

const useDidUpdate = (callback, inputs) => {
  const initial = useRef(true);

  useEffect(() => {
    if (initial.current) {
      initial.current = false;
      return;
    }
    callback?.();
  }, inputs);
};

использование вышеперечисленногоuseRefподдерживатьinitialПеременная используется, чтобы определить, является ли это исходным рендерингом, а также следовать примеруuseEffectподдержалinputsпараметры, чтобы реализовать, обновить эффект для тех значений, если они не переданыinputsпараметр, то повторный рендеринг компонента, вызванный обновлением Props или State, будет выполнен дляcallbackметод.

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

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

🌰 использовать значения

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

const [state, setState] = useState({
  name: 'jace',
  intro: 'emmm'
});

// 只更新 name 字段
setState({
  ...state,
  name: 'jack'
})

мы снова используемuseStateВремя от времени нужно будет просто обновлятьstateКогда в объекте есть определенное свойство, другие данные, которые не изменяются, должны быть переданы, поэтому сложно писать, когда использование велико, а код выглядит некрасиво. Мы хотим обновить одно или несколько свойств одновременно, а другие свойства останутся неизменными, поэтому мы можем инкапсулировать хуки в соответствии с этим требованием.

const useValues = initialValue => {
  const [values, setValues] = useState(initialValue);

  const updateValues = useCallback(
    _values => {
      if (typeof _values !== 'object') {
        return console.warn('values required type is object!');
      }
      setValues(Object.assign({}, values, _values));
    },
    [values]
  );

  return [values, updateValues];
};

Вышеприведенное реализуетuseStateОчень похожие хуки, разница в том, что метод обновления возвращаемого значения будет обновляться на основе предыдущего значения, здесь используетсяuseCallbackМетод делает кеш для нашей функции обновления, я мало что знаю об этомuseCallbackКонкретные сценарии применения можете посмотреть в другой моей статье:Подробное объяснение useCallback и useMemo.

В дальнейшем, при возникновении вышеуказанных требований, вы можетеuseValuesМетод вводится в компонент для использования.

const [state, setState] = useValues({
  name: 'jace',
  intro: 'emmm'
});

// 只更新 name 字段
setState({
  name: 'jack'
})

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

const useValues = initialValue => {
  const [values, setValues] = useState(initialValue);

  const updateValues = useCallback(
    _values => {
      if (typeof _values !== 'object') {
        return console.warn('values required type is object!');
      }
      setValues(Object.assign({}, values, _values));
    },
    [values]
  );
  
  /**
   * 这里 useCallback 所依赖的外部变量 setValues 不会变;
   * initialValue 我们不需要他会变,只用最初状态就可以,所以 useCallback 不需要传入依赖项。
   */
  const forceValues = useCallback(_values => {
    setValues(_values || initialValue);
  }, []);

  return [values, updateValues, forceValues];
};

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

🌰 использоватьОтменитьТаймер

Должно быть много студентов, которые столкнутся с этим предупреждением при написании проектов React:

Warning: 
Can't perform a React state update on an unmounted component. This is a no-op, 
but it indicates a memory leak in your application. To fix, 
cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

Как видно из отчета об ошибке, мы видим, что это связано с тем, что компонент был выгружен, но асинхронная задача компонента не была отменена.Такое предупреждение будет выдано при вызове метода компонента после асинхронная задача завершена. Если мы храним все данные вне компонента, а данные получаются и хранятся через методы вне компонента, то мы не должны столкнуться с этой проблемой (redux/saga/dva).

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

const Content = ({ onClose }) => {
  const [name, setName] = useState('');

  useEffect(() => {
    request();
  }, []);

  const request = () => {
    return setTimeout(() => {
      setName('jace');
    }, 2000);
  };

  return (
    <div>
      <button onClick={onClose}>Close</button>
      <div>name: {name}</div>
    </div>
  );
};

const UseCancelTimer = () => {
  const [visible, setVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setVisible(true)}>Show</button>
      {visible && <Content onClose={() => setVisible(false)} />}
    </div>
  );
};

существуетContentКогда компонент установлен, вы запрашите асинхронную задачу, а асинхронная задача получит данные иsetState, если мы нажмем кнопку «Закрыть» до завершения выполнения асинхронной задачи, что приведет к выгрузке компонента, в консоли появится указанная выше ошибка.

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

// useCancelTimer.js
const useCancelTimer = () => {
  const requests = useRef([]); // 存储每个异步方法标识

  useEffect(() => {
    return () => { // 组件卸载时清除
      requests.current.forEach(clearTimeout);
    };
  }, []);

  return useCallback((timer) => { // 使用该方法包裹每一个异步请求
    requests.current.push(timer);
  }, []);
};
// Content.jsx
const Content = () => {
  const addTimer = useCancelTimer();
  const [data, setData] = useState({});

  useEffect(() => {
    addTimer(request()); // 使用该方法包裹每一个异步请求
  }, []);

  const request = () => {
    return setTimeout(() => {
      console.log("reslove");
      setData({
        name: "jace"
      });
    }, 2000);
  };
}

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

🌰 использовать запрос

В приведенном выше случае используется таймер, и метод асинхронного запроса, используемый в конкретном бизнесе, также сильно отличается.Та же логика, вот еще один случай, который я реализовал на основе Axios: онлайн-адресuseRequest

const useRequest = () => {
  const requests = useRef({});
  const [loading, setLoading] = useState(false); // 顺单维护一个组件内所需要的 loading 状态

  useEffect(() => {
    return () => { // 组件卸载取消所有正在进行中的异步请求
      const { current } = requests;
      for (let key in current) {
        current.hasOwnProperty(key) && current[key]?.('cancel');
      }
    };
  }, []);

  return [
    async (config, showLoading = true) => {
      // 规定组件内所有请求都通过 此方法来发送以便维护
      if (showLoading) {
        !loading && setLoading(true);
      }
      const _id = getRandomId(); // 随机生成一个字符串ID
      const promise = axios(
        Object.assign({}, config, {
          cancelToken: new axios.CancelToken((cancel) => {
            Object.assign(requests.current, { // 存下取消每个异步请求需要的方法
              [_id]: cancel
            });
          })
        })
      );

      let error = false;
      let res = null;

      try {
        res = await promise;
      } catch (err) {
        // 这里因为组件已经卸载了,就直接返回,不走下面的逻辑了
        if (err instanceof Axios.Cancel) {
          return [err, res];
        }
        error = err;
      }

      delete requests.current[_id];
      if (isEmpty(requests.current)) {
        setLoading(false);
      }

      return [error, res];
    },
    loading
  ];
};

наконец

Эта статья в основном предназначена для бизнеса, перечисляя некоторые моменты, которые могут быть оптимизированы в бизнесе, и мне также нравится делиться вещами, которые предпринимают бизнес. Я планировал построить библиотеку с открытым исходным кодом для пользовательских крючков давным-давно, но я узнал, что Ali, кажется, имеет одно время назадahooks, который должен быть более простой или более универсальной инкапсуляцией хуков. Ищите вдохновение, когда у вас есть время, чтобы создать склад демонстрационных коллекций Hooks для обучения, более ориентированный на бизнес. 😋 Заинтересованные студенты могут обратить внимание.

Наконец, небольшая реклама, добро пожаловать на чтение этой моей статьиФункциональное программирование JS и применение функций высшего порядкаОн очень подходит для новичков. Он также написан на основе бизнес-логики. При написании пользовательской логики компонента пакета Hooks, при написании высокоуровневой логики метода пакета функций качество кода и эффективность разработки удваиваются. 😋 .

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