Привет! Это третья и заключительная часть серии статей “Лучшие подходы и решения для уменьшения кода на React” автора Rahul Sharma. Предыдущие статьи вы можете найти по ссылкам ниже:

Храните токен в куки, вместо localStorage

Плохой код:

const token = localStorage.getItem("token");
if (token) {
  axios.defaults.headers.common["Authorization"] = token;
}

Хороший код:

import Cookies from "js-cookie"; //  Вы можете использовать другую библиотеку

const token = Cookies.get("token");
if (token) {
  axios.defaults.headers.common["Authorization"] = token;
}

Лучший код:

Без кода ????

Примечание:

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

  • Используйте HttpOnly атрибут для избегания получения доступа к куки через JavaScript.


Используйте перехватчики запросов (interceptors) для токена авторизации или других стандартных заголовков

Плохой код:

axios.get("/api", {
  headers: {
    ts: new Date().getTime(),
  },
});

Хороший код:

axios.interceptors.request.use(
  (config) => {
    // Код, необходимый до отправки запроса
    config.headers["ts"] = new Date().getTime();
    return config;
  },
  (error) => {
    // Обработка ошибки из запроса
    return Promise.reject(error);
  }
);

// Компонент
axios.get("/api");

Используйте react context/redux для передачи пропов в дочерние элементы

Плохой код:

const auth = { name: "John", age: 30 };
return (
  <Router>
    <Route path="/" element={<App auth={auth} />} />
    <Route path="/home" element={<Home auth={auth} />} />
  </Router>
);

Хороший код:

return (
  <Provider store={store}>
    <Router>
      <Route
        path="/"
        element={<App />}
      />
      <Route
        path="/home"
        element={<Home />}
      />
    </Router>
);


// Внутри дочернего компонента
const { auth } = useContext(AuthContext); // При использовании контекста
const { auth } = useSelector((state) => state.auth); // При использовании redux

Используйте вспомогательные функции для styled-components

Не плохой код, но сложный для чтения, так как все привыкли думать в пикселях:

const Button = styled.button`
  margin: 1.31rem 1.43rem;
  padding: 1.25rem 1.5rem;
`;

Создайте вспомогательную функцию для перевода пикселей в rem:

const toRem = (value) => `${value / 16}rem`;
const Button = styled.button`
  margin: ${toRem(21)} ${toRem(23)};
  padding: ${toRem(20)} ${toRem(24)};
`;

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

Плохой код:

const onNameChange = (e) => setName(e.target.value);
const onEmailChange = (e) => setEmail(e.target.value);

return (
  <form>
    <input type="text" name="name" onChange={onNameChange} />
    <input type="text" name="email" onChange={onEmailChange} />
  </form>
);

Хороший код:

const onInputChange = (e) => {
  const { name, value } = e.target;
  setFormData((prevState) => ({
    ...prevState,
    [name]: value,
  }));
};

return (
  <form>
    <input type="text" name="name" onChange={onInputChange} />
    <input type="text" name="email" onChange={onInputChange} />
  </form>
);

Используйте intersection observer для реализации отложенной загрузки

Плохой код:

element.addEventListener("scroll", function (e) {
  // do something
});

Хороший код:

const useScroll = (ele, options = {}): boolean => {
  const [isIntersecting, setIsIntersecting] = useState(false);
  useEffect(() => {
    const cb = (entry) => setIsIntersecting(() => entry.isIntersecting);
    const callback: IntersectionObserverCallback = (entries) => entries.forEach(cb);
    const observer = new IntersectionObserver(callback, options);
    if (ele) observer.observe(ele);
    return (): void => ele && observer.unobserve(ele);
  }, [ele]);
  return isIntersecting;
};


// Компонент
const ref = useRef<any>();
const isIntersecting = useScroll(ref?.current);

useEffect(() => {
  if (isIntersecting) {
    // вызов API
  }
}, [isIntersecting]);

Используйте HOC для авторизации и приватной навигации

Плохой код:

const Component = () => {
  if (!isAuthenticated()) {
    return <Redirect to="/login" />;
  }
  return <div></div>;
};

Хороший код:

const withAuth = (Component) => {
  return (props) => {
    if (!isAuthenticated()) {
      return <Redirect to="/login" />;
    }
    return <Component {...props} />;
  };
};

// Навигация
<Route path="/home" component={withAuth(Home)} />;

// Компонент
const Component = (props) => <div></div>;
export default withAuth(Component);

Используйте массив с объектами данных навигации при ее создании

Стандартное решение:

return (
  <Router>
    <Route path="/" element={<App />} />
    <Route path="/about" element={<About />} />
    <Route path="/topics" element={<Topics />} />
  </Router>
);

Хороший код:

const routes = [
  {
    path: "/",
    role: ["ADMIN"],
    element: React.lazy(() => import("../pages/App")),
    children: [
      {
        path: "/child",
        element: React.lazy(() => import("../pages/Child")),
      },
    ],
  },
  {
    path: "/about",
    role: [],
    element: React.lazy(() => import("../pages/About")),
  },
  {
    path: "/topics",
    role: ["User"],
    element: React.lazy(() => import("../pages/Topics")),
  },
];

const createRoute = ({ element, children, role, ...route }) => {
  const Component = role.length > 0 ? withAuth(element) : element;
  return (
    <Route key={route.path} {...route} element={<Component />}>
      {children && children.map(createRoute)}
    </Route>
  );
};

return <Routes>{routes.map(createRoute)}</Routes>;

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


Используйте Typescript

Ничего плохого, если вы не используете Typescript ????, попробуйте он поможет сделать ваш код лучше.

npx create-react-app my-app --template typescript

Используйте eslint, prettier для форматирования

npm install -D eslint prettier
npx eslint --init

Ссылки для помощи в настройке: Eslint, Prettier


Используйте pre-commit хук для запуска eslint и prettier

Плохой код:

npx mrm@2 lint-staged // Установка и конфигурация pre-commit хука

// Скрипт будет создан в корне вашего проекта
.husky/pre-commit

// Package.json
"lint-staged": {
  "src/**/*.{js,ts,jsx,tsx}": [
    "npm run lint",
    "npm run prettier",
    "npm run unit-test",
    "git add"
  ]
}

Примечание:

  • Вы можете обновить конфиг для запуска prettier и eslint при коммите. Вы также можете добавлять или удалять команды в package.json.

  • Лучше, если в проекте настроен CI & CD для этого. Кто-то может закомментировать pre-commit хук и запушить его в гит.


Несколько VSCode расширений для более легкой разработки

Auto Close TagAuto Rename TagCodeMetricsCSS PeekES7+ React/Redux/React-Native snippetsEslintGitLensImport CostPrettier

Примечание: попробуйте расширения для оценки сложности функций (CodeMetrics). Это поможет в написании более хорошего кода, так как вы будете видеть сложность выполнения кода.


Спасибо за прочтение ????

Комментарии (1)


  1. superman_cherry
    01.04.2022 10:41

    Используйте react context/redux для передачи пропов в дочерние элементы
    Добрый день, проверяли каждый из подходов на предмет производительности?
    Что будет менее затратно: передавать в пропсах или в каждом children дёргать hook?