Я пишу React на работе, чему я научился?

внешний интерфейс React.js
Я пишу React на работе, чему я научился?

предисловие

Технический стек на моей работе в основномReact + TypeScript, В этой статье я хочу обобщить, как использовать некоторые навыки React в проекте для решения некоторых практических задач.Код, используемый в этой статье, упрощен и не представляет производственную среду. Код в производственной среде определенно намного сложнее, чем примеры в этой статье, но упрощенные идеи должны быть такими же.

отменить запрос

Компонент, который в данный момент делает запрос в React, выгружается со страницы, и в идеале запрос тоже должен быть отменен, так как же связать отмену запроса с выгрузкой страницы?

Здесь мы должны рассмотреть возможность использования возвращаемого значения useEffect для передачи в функцию:

useEffect(() => {
  return () => {
    // 页面卸载时执行
  };
}, []);

Предполагая, что наш запрос использует выборку, необходимо применить еще один момент знания:AbortController, краткий обзор его использования:

const abortController = new AbortController();

fetch(url, {
  // 这里传入 signal 进行关联
  signal: abortController.signal,
});

// 这里调用 abort 即可取消请求
abortController.abort();

Затем объедините React, чтобы инкапсулироватьuseFetchкрюк:

export function useFetch = (config, deps) => {
  const abortController = new AbortController()
  const [loading, setLoading] = useState(false)
  const [result, setResult] = useState()

  useEffect(() => {
    setLoading(true)
    fetch({
      ...config,
      signal: abortController.signal
    })
      .then((res) => setResult(res))
      .finally(() => setLoading(false))
  }, deps)

  useEffect(() => {
    return () => abortController.abort()
  }, [])

  return { result, loading }
}

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

глубокая зависимость от сравнения

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

Например:

const getDep = () => {
  return {
    foo: 'bar',
  };
};

useEffect(() => {
  // 无限循环了
}, [getDep()]);

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

Есть хитрое решение, преобразовать зависимость в строку:

const getDep = () => {
  return {
    foo: 'bar',
  };
};

const dep = JSON.stringify(getDeps());

useEffect(() => {
  // ok!
}, [dep]);

Это сравнивает строку"{ foo: 'bar' }"значение, а не ссылку на объект, тогда обновление будет запущено только тогда, когда значение действительно изменится.

Конечно, лучше использовать решение, предоставленное сообществом:useDeepCompareEffect, он выбираетглубокое сравнениеСтратегия для зависимостей объектов сравнивает ключ и значение один за другим, что снижает производительность.

Если одна из ваших зависимостей вызывает несколько бессмысленных запросов к интерфейсу, предпочтите использоватьuseDeepCompareEffect, тратить больше времени на сравнение объектов может быть намного лучше, чем повторно запрашивать интерфейс.

useDeepCompareEffectОсновной принцип:

import { isEqual } from 'lodash';
export function useDeepCompareEffect(fn, deps) {
  const trigger = useRef(0);
  const prevDeps = useRef(deps);
  if (!isEqual(prevDeps.current, deps)) {
    trigger.current++;
  }
  prevDeps.current = deps;
  return useEffect(fn, [trigger.current]);
}

действительно входящийuseEffectобновитьtriggerэто числовое значение. использоватьuseRefСохраните последнюю входящую зависимость и используйте isEqual lodash для выполнения этой зависимости и старой зависимости каждый раз.глубокое сравнение, если изменится, пустьtriggerзначение увеличивается.

Конечно, мы также можем использоватьfast-deep-equalЭта библиотека, согласно официальному сравнению бенчмарков, примерно в 7 раз эффективнее, чем lodash.

Использовать URL как хранилище данных

Во внутренних проектах управления фоном компании, независимо от того, работает ли система, над которой вы работаете, или разрабатывается, будет задействован обмен, поэтому очень важно сохранить «статус страницы». Например, я выполняю операцию А и использую внутреннюю платформу данных. Я должен поделиться второй страницей данных о потреблении приложения с операцией Б и отфильтровать ее как данные пользователя.условиестраницу и обсудить ее. Тогда синхронизация состояния и URL особенно важна.

В традиционной идее управления состоянием нам нужно использовать в кодеredux,recoilПодождите, пока библиотека выполнит серию операций по управлению данными, но что, если строка запросов после URL-адреса представляется хранилищем данных? Возможно ли, попробуйте сотрудничатьreact-routerУпакуйте это.

export function useQuery() {
  const history = useHistory();
  const { search, pathname } = useLocation();
  // 保存query状态
  const queryState = useRef(qs.parse(search));
  // 设置query
  const setQuery = handler => {
    const nextQuery = handler(queryState.current);
    queryState.current = nextQuery;
    // replace会使组件重新渲染
    history.replace({
      pathname: pathname,
      search: qs.stringify(nextQuery),
    });
  };
  return [queryState.current, setQuery];
}

В компоненте вы можете использовать его следующим образом:

const [query, setQuery] = useQuery();

// 接口请求依赖 page 和 size
useEffect(() => {
  api.getUsers();
}, [query.page, query, size]);

// 分页改变 触发接口重新请求
const onPageChange = page => {
  setQuery(prevQuery => ({
    ...prevQuery,
    page,
  }));
};

Таким образом, все изменения состояния страницы автоматически синхронизируются с URL-адресом, что очень удобно.

Интернационализация с AST

Самая большая головная боль при интернационализации — вручную заменить текст в коде и преобразовать его вi18n.t(key)Этот вызов метода интернационализации, и этот шаг может быть передан для завершения Babel AST. Отсканируйте место в коде, где текст должен быть заменен, и измените AST, чтобы преобразовать его в вызов метода. Проблема заключается в том, что необходимо учитывать различные граничные условия. Я написал относительно простой пример только для справки. :

GitHub.com/forget 1673495/нет…

Такой кусок исходного кода:

import React from 'react';
import { Button, Toast, Popover } from 'components';
const Comp = props => {
  const tips = () => {
    Toast.info('这是一段提示');
    Toast({
      text: '这是一段提示',
    });
  };
  return (
    <div>
      <Button onClick={tips}>这是按钮</Button>
      <Popover tooltip="气泡提示" />
    </div>
  );
};
export default Comp;

После обработки получается вот так:

import React from 'react';
import { useI18n } from 'react-intl';
import { Button, Toast, Popover } from 'components';
const Comp = props => {
  const { t } = useI18n();
  const tips = () => {
    Toast.info(t('tips'));
    Toast({
      text: t('tips'),
    });
  };
  return (
    <div>
      <Button onClick={tips}>{t('btn')}</Button>
      <Popover tooltip={t('popover')} />
    </div>
  );
};
export default Comp;

поставить скриптtraverseчасть:

// 遍历ast
traverse(ast, {
  Program(path) {
    // i18n的import导入 一般第一项一定是import React 所以直接插入在后面就可以
    path.get('body.0').insertAfter(makeImportDeclaration(I18_HOOK, I18_LIB));
  },
  // 通过找到第一个jsxElement 来向上寻找Component函数并且插入i18n的hook函数
  JSXElement(path) {
    const functionParent = path.getFunctionParent();
    const functionBody = functionParent.node.body.body;
    if (!this.hasInsertUseI18n) {
      functionBody.unshift(
        buildDestructFunction({
          VALUE: t.identifier(I18_FUNC),
          SOURCE: t.callExpression(t.identifier(I18_HOOK), []),
        })
      );
      this.hasInsertUseI18n = true;
    }
  },
  // jsx中的文字 直接替换成{t(key)}的形式
  JSXText(path) {
    const { node } = path;
    const i18nKey = findI18nKey(node.value);
    if (i18nKey) {
      node.value = `{${I18_FUNC}("${i18nKey}")}`;
    }
  },
  // Literal找到的可能是函数中调用参数的文字 也可能是jsx属性中的文字
  Literal(path) {
    const { node } = path;
    const i18nKey = findI18nKey(node.value);
    if (i18nKey) {
      if (path.parent.type === 'JSXAttribute') {
        path.replaceWith(
          t.jsxExpressionContainer(makeCallExpression(I18_FUNC, i18nKey))
        );
      } else {
        if (t.isStringLiteral(node)) {
          path.replaceWith(makeCallExpression(I18_FUNC, i18nKey));
        }
      }
    }
  },
});

Конечно, реальный проект также должен учитывать часть копирайтинга перевода, как построить платформу и как сотрудничать с операторами или специалистами по переводу.

И AST обрабатывает различные пограничные случаи, это должно быть намного сложнее, это просто упрощенная версия идеи.

Суммировать

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

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