microCMS

Reactのベストプラクティスとコード削減パターン - パート3

松田 承一

この記事は公開後、1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

本記事は React best practices and patterns to reduce code - Part3 を提供元の事前許可を得たうえで翻訳したものです。

元の記事に従いタイトルに「ベストプラクティス」と含んでいますが、実際にはベストプラクティスは規模や状況によって大きく異なります。
チームの状況にあわせて参考にしていただければと思います。

=====

これは全3パート中の最後である第3パートとなる記事です。前2つの記事を読んでいなければ是非以下のリンクからお読みください。


それではいきましょう。

トークンはlocalStorageよりもCookieに保存する

よくないコード:

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

よいコード:

import Cookies from "js-cookie"; //  use another library if you want

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

よりよいコード:

No Code 😉

注釈:

  • Cookieはサブドメインも含んだサイトで共有されますが、リクエスト毎にトークンを渡す必要はありません。なお、バックエンドがフロントエンドと同じドメインではない場合は2つ目の方法を取る必要があります。
  • HttpOnly属性を使うとJavaScriptからCookieの値にアクセスできなくなります。そこでReactではアクセス可能であることをチェックするためのフラグが必要になることもあるでしょう。


訳者注
トークンの保存についてはCookie / LocalStorageそれぞれについて様々議論されており全てのケースで一概に○○の方法が良い、とは言い切れません。上記はあくまで一つの意見として理解しておく程度で良いと感じました。

認証トークンや共通ヘッダの付与にはinterceptorsを使用する

よくないコード:

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

よいコード:

// only once
axios.interceptors.request.use(
  (config) => {
    // Do something before request is sent
    config.headers["ts"] = new Date().getTime();
    return config;
  },
  (error) => {
    // Do something with request error
    return Promise.reject(error);
  }
);

// Component
axios.get("/api");

propsを子要素に渡すために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>
);


// Inside child component
const { auth } = useContext(AuthContext); // For context
const { auth } = useSelector((state) => state.auth); // For redux

styled-componentのためのヘルパー関数を作成する

悪くはないがピクセルで考えることが非常に難しいコード:

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)};
`;

input要素が変更された際のための共通関数を用意する

よくないコード:

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;
};


// Component
const ref = useRef<any>();
const isIntersecting = useScroll(ref?.current);

useEffect(() => {
  if (isIntersecting) {
    // call an 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
<Route path="/home" component={withAuth(Home)} />;

// Component
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 setupPrettier Setup
😥この記事はシンプルかつ短く保っておきたいため、全ての導入手順は記載していません。

eslintやprettierをフォーマットに利用する pre-commitフックを使ってeslintやprettierを動作させる

npx mrm@2 lint-staged // This will install and configure pre-commit hook

// This script will be created at the root of your project
.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をコメントアウトしてgitにコードをプッシュできるでしょう。


より良い開発のためにVSCodeの拡張を活用する

Auto Close TagAuto Rename TagCodeMetricsCSS PeekES7+ React/Redux/React-Native snippetsEslintGitLensImport CostPrettier
注釈:CodeMetricsなどのコード複雑度のための拡張はぜひ試してみてください。コードの複雑度を表示し、より良いコードを書くためのサポートをしてくれます。

ここまでお読みいただきありがとうございました😊

--

今後もWebフロントエンドにまつわる最新情報を発信予定です。
興味のある方はぜひ 公式Twitter をフォローしてください。

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

松田 承一
株式会社microCMSの代表 / 家族=👨‍👩‍👧 / ヤフー→大学教員など→現職 / 管理画面付きAPIがすぐに作れるmicroCMSというサービス作ってます。