【インターン】初めての実務経験で学んだこと

はじめに

こんにちは!エキサイト株式会社の就業型インターンシップ Booost!!! に参加した吉田です。 本記事では、1 ヶ月のインターンシップで私が行ったことや学んだことについてご紹介します。

自己紹介

私は現在大学院修士 1 年生で、情報学を専攻しています。人工知能分野の研究を行っており、普段は Python を使ってデータ処理・データ分析のためのプログラムを書くことが多いです。

Web 開発にも興味があり、学部生の頃から独学で勉強をしてきました。 フロントエンドは JavaScript や TypeScript、バックエンドは RubyPHP を使って開発を行った経験があります。個人で開発をしたりハッカソンでチーム開発をしたりした経験はありますが、実務での Web 開発は経験したことがありませんでした。そのため、今回のインターンが初めての実務開発となります。

インターンでの業務内容

今回のインターンでは SaaS 事業部に配属され、KUROTEN というサービスの開発に関わりました。KUROTEN は企業向けの会計管理クラウドサービスです。KUROTEN を使用することで経営判断の材料となる会計管理データが一元管理され、経営管理業務を効率化することができます。

私は、TypeScript, React を用いてフロントエンドの開発を担当しました。1 ヶ月のインターン期間を通じて、ユーザー設定ページの機能を一通り実装しました。

実務の観点で学んだこと

インターンの期間中に一番意識したことは、実務の観点で重要な考え方やコーディングの方法を学ぶことです。インターンを開始する以前に TypeScript, React を使って開発を行った経験があり、これらの言語・ライブラリの基本的な文法や仕組みは理解していました。そのため、就業型インターンという今回の貴重な機会では言語やライブラリの使い方にとどまらず、実務で開発を行う上で重要なポイントを吸収しようという心構えで臨みました。

インターン期間中は、メンター社員の方に私の開発業務をサポートしていただきました。開発を行う上でわからないことがあったりつまづいたりしたときはすぐにメンターの方へ質問できる環境であったため、実務が初めての私でもスムーズに開発を進めることができました。メンターの方には私の質問に答えていただいただけでなく、多くのアドバイスもしていただきました。その内容は、実務の観点で重要な考え方やエンジニアとして働く上で必要なこと、そして最新の技術動向などで、個人開発を行っているだけでは得られない有益な情報ばかりでした。そのため、インターンの期間を通じて実務的な観点で多くのことを学ぶことができました。

実務的な観点で学んだこととして、適切に技術選定を行うことの重要性があります。React を利用してフロントエンド開発を行う際にもプロジェクトの中で使用する技術の選択肢は多くあり、そのトレンドの移り変わりも速いです。例えば、プロジェクトを進める上で

  • CSS をどのような方法で記述・適用するか?
  • グローバルな State をどのような方法で管理するか?
  • モジュールバンドラー (複数のファイルをビルドして 1 つにまとめるツール) は何を使うか?

といった点について考える必要があり、使用する技術の選択肢は様々です。私が以前 React を用いてフロントエンドの開発を行った際には、深く考えることなく使用技術を決めていました。個人開発を行う上ではどの技術を選択しても大した問題にはならないことが多いですが、実務の観点では技術の選定が重要です。アプリケーションのパフォーマンスや保守、チーム開発での効率などを考慮し、適切な技術を選ぶ必要があります。このことについてメンターの方に詳しく教えていただき、適切に使用技術を決めることの重要性を学びました。

インターンでは、私が今まで使用したことのない技術や知らなかった技術もいくつか使用しました。 初めて使用した技術に、グローバルな状態を管理するライブラリ Jotai があります。以下では Jotaiについてご紹介します。

Jotai の公式ドキュメント

Jotai を用いたグローバルな State 管理

グローバルな State とは?

React では、各コンポーネントが State を持つことができます。以下に、useState を用いて State を扱う例を示します。

Counter.tsx

import { useState, FC } from "react";

const Counter: FC = () => {
  // 初期値を 0 として count という変数で State を管理
  // setCount は count の値を更新する際に利用する関数
  const [count, setCount] = useState<number>(0);

  const handleCountUp = () => setCount((val) => val + 1);
  const handleCountDown = () => setCount((val) => val - 1);

  return (
    <>
      <button onClick={handleCountUp}>Count Up</button>
      <button onClick={handleCountDown}>Count Down</button>
    </>
  );
};

それぞれのコンポーネントで定義される State はローカルな State と呼ばれ、そのコンポーネントの内部でしか利用することができません。そのため、あるコンポーネントで定義された State を別のコンポーネントで使用したい場合には、Props を利用しコンポーネント間で State の受け渡しを行う必要があります。

Props を用いてコンポーネント間で State を受け渡す方法の場合、コンポーネントの階層が増えるに従い Props のやり取りが多くなりコードが複雑化してしまいます。例えば、あるコンポーネントで定義したローカルな State をそのコンポーネントの 3 層下のコンポーネントで使用したい場合には、Props を用いた State の受け渡しを 3 回行う必要があることになります。Props の受け渡し回数が増えるとコードが複雑化するだけでなく、必要のないレンダリングが行われるという問題があります。コンポーネントは Props の更新時に再レンダリングされるため、State をコンポーネント間でやり取りする回数が増えると不必要なレンダリングが行われ、アプリケーションのパフォーマンスが低下する可能性があります。

以上のような理由により、複数のコンポーネントから利用する State はどのコンポーネントからでもアクセスできるグローバルな State として管理することが一般的です。グローバルな State 管理を行うためのライブラリとして有名なものには、React の Context, Redux, Recoil などがあります。今回のインターンでは、Jotai というライブラリを用いてグローバルな State 管理を行いました。

Jotai の特徴と基本的な使い方

Jotai は上述の Redux などに比べて軽量であり、コードの記述が簡単であるという特徴があります。

Jotai の基本的な概念として、atomuseAtom があります。atom は状態を保持するためのオブジェクト、useAtomatom を利用するために使用する関数です。

下の例では、countAtom.ts で数値型を格納する atom を定義し、0 で初期化しています。UpdateCounter.tsxuseAtom を定義し、関数の 2 つ目の返り値である setCount を使用することで countAtom に格納されている数値を変更することができます。

countAtom.ts

import { atom } from "jotai";
import { FC } from "react";

// グローバルな State を格納する atom を定義し 0 で初期化
export const countAtom = atom<number>(0);

UpdateCounter.tsx

import { useAtom } from "jotai";
import { countAtom } from "./countAtom";

const UpdateCounter: FC = () => {
  // countAtom.ts で定義した atom を呼び出し count という変数に格納
  // setCount は count を更新する際に利用する関数
  const [count, setCount] = useAtom(countAtom);

  const handleCountUp = () => setCount((val) => val + 1);
  const handleCountDown = () => setCount((val) => val - 1);

  return (
    <>
      <button onClick={handleCountUp}>Count Up</button>
      <button onClick={handleCountDown}>Count Down</button>
    </>
  );
};

export default UpdateCounter;

参考記事: React 用状態管理ライブラリ「Jotai」に入門

Jotai を用いた snackbar の作成

今回のインターンでは、snackbar の実装に Jotai を利用しました。ここでは、Jotai を利用した snackbar の実装方法について概要を紹介します。

snackbar は、特定のタイミングで画面端に表示させるバーです。今回は以下の例のように、状況に応じてバーの中の文字が変わったりアイコンが表示されたりすることを想定します。

snackbar_loading
ユーザー情報を更新している際に表示される snackbar

snackbar_completed
ユーザー情報が更新された後に表示される snackbar

snackbar を実装するため、以下の 2 つのファイル snackbar.ts, Snackbar.tsx を作成します。

snackbar.ts

import { atom } from "jotai";

// atom に格納するオブジェクトの型定義
type ActiveSnackbarStateProps = {
  isOpened: boolean;
  variant: "inverse-surface" | "error";
  message: string;
  timeout?: number;
  loading?: boolean;
};

// atom オブジェクトを作成し初期値を定義
export const activeSnackbarState = atom<ActiveSnackbarStateProps>({
  isOpened: false,
  variant: "inverse-surface",
  message: "",
  timeout: 5000,
  loading: false,
});

snackbar.ts では snackbar で必要な State を格納する atom を定義し、変数 activeSnackbarState に格納しています。今回扱う State はオブジェクトであり、snackbar の開閉状態を格納する bool 型のプロパティ isOpened、snackbar の中に表示するメッセージを格納する string 型のプロパティ message などを持ちます。

Snackbar.tsx

import clsx from "clsx";
import { useAtom } from "jotai";
import { FC, useEffect } from "react";

import { Loader, Icon, Text } from "@/components/common";
import { activeSnackbarState } from "@/stores/ui/snackbar";

import s from "./Snackbar.module.css";

const Snackbar: FC = () => {
  // useAtom を用いてグローバルな State である activeSnackbar と
  // State を更新する関数 setActiveSnackbar を取得
  const [activeSnackbar, setActiveSnackbar] = useAtom(activeSnackbarState);

  const rootClassName = clsx(s.root, s[`${activeSnackbar.variant}`]);

  useEffect(() => {
    if (activeSnackbar.isOpened) {
      const timer = setTimeout(
        () => {
          setActiveSnackbar({
            isOpened: false,
            variant: activeSnackbar.variant,
            message: "",
          });
        },
        activeSnackbar.timeout ? activeSnackbar.timeout : 5000
      );

      return () => clearTimeout(timer);
    }
  }, [activeSnackbar.isOpened, setActiveSnackbar, activeSnackbar]);

  return (
    <>
      {/* グローバル State である activeSnackbar の isOpend プロパティ が true だったら snackbar を表示 */}
      {activeSnackbar.isOpened && (
        <div className={rootClassName} role="alert">
          {/* activeSnackbar の loading プロパティ が true だったら loader を表示 */}
          {activeSnackbar.loading && (
            <Loader
              size="small"
              color={
                activeSnackbar.variant === "inverse-surface"
                  ? "inverse-on-surface"
                  : "on-error"
              }
            />
          )}
          {/* activeSnackbar の message プロパティ を表示 */}
          <Text
            variant="body"
            color={
              activeSnackbar.variant === "inverse-surface"
                ? "inverse-on-surface"
                : "on-error"
            }
          >
            {activeSnackbar.message}
          </Text>
          {/* snackbar を閉じるボタン */}
          <button
            onClick={() =>
              setActiveSnackbar({
                isOpened: false,
                variant: activeSnackbar.variant,
                message: "",
              })
            }
          >
            <Icon
              name="close"
              size={16}
              color={
                activeSnackbar.variant === "inverse-surface"
                  ? "inverse-on-surface"
                  : "on-error"
              }
            />
          </button>
        </div>
      )}
    </>
  );
};

export default Snackbar;

Snackbar.tsx は snackbar 本体に対応するコンポーネントです。snackbar.ts で定義した activeSnackbarState に保持されている State に応じて挙動を変えたり、State を変更したりすることによって snackbar を表示したり消したりする動作を実現しています。

Loader, Icon, Text は別ファイルで定義しているコンポーネントです。Loader は読み込み中に表示するアイコン、Icon は snackbar を閉じるボタン、Text は snackbar に表示するメッセージに対応しています。いずれのコンポーネントactiveSnackbar.variant を Props で受け取り、その値に応じたカラーコードが設定されるようになっています。

おわりに

1 ヶ月間の就業型インターンを通して、実務の観点で重要なことを多く学ぶことができました。また、コーディングを行う中で React を用いたフロントエンド開発に関する知識もより深まりました。インターンを通して、実務で通用するエンジニアになるためにはどのレベルまで達する必要があるかというゴールが明確になりました。残りの学生生活で、そのゴールへ向けた過程として経験を積んで技術力をさらに伸ばしていきたいです。

インターン期間中、普段の研究や個人開発ではできない経験をたくさんさせていただきました。業務中サポートや有益なアドバイスをくださったメンターの方々、インターンの機会を提供していただいた人事の方々、インターン期間中関わってくださった社員の皆様、1 ヶ月間ありがとうございました。