はじめに
こんにちは!エキサイト株式会社の就業型インターンシップ Booost!!! に参加した吉田です。 本記事では、1 ヶ月のインターンシップで私が行ったことや学んだことについてご紹介します。
自己紹介
私は現在大学院修士 1 年生で、情報学を専攻しています。人工知能分野の研究を行っており、普段は Python を使ってデータ処理・データ分析のためのプログラムを書くことが多いです。
Web 開発にも興味があり、学部生の頃から独学で勉強をしてきました。 フロントエンドは JavaScript や TypeScript、バックエンドは Ruby や PHP を使って開発を行った経験があります。個人で開発をしたりハッカソンでチーム開発をしたりした経験はありますが、実務での Web 開発は経験したことがありませんでした。そのため、今回のインターンが初めての実務開発となります。
インターンでの業務内容
今回のインターンでは SaaS 事業部に配属され、KUROTEN というサービスの開発に関わりました。KUROTEN は企業向けの会計管理クラウドサービスです。KUROTEN を使用することで経営判断の材料となる会計管理データが一元管理され、経営管理業務を効率化することができます。
私は、TypeScript, React を用いてフロントエンドの開発を担当しました。1 ヶ月のインターン期間を通じて、ユーザー設定ページの機能を一通り実装しました。
実務の観点で学んだこと
インターンの期間中に一番意識したことは、実務の観点で重要な考え方やコーディングの方法を学ぶことです。インターンを開始する以前に TypeScript, React を使って開発を行った経験があり、これらの言語・ライブラリの基本的な文法や仕組みは理解していました。そのため、就業型インターンという今回の貴重な機会では言語やライブラリの使い方にとどまらず、実務で開発を行う上で重要なポイントを吸収しようという心構えで臨みました。
インターン期間中は、メンター社員の方に私の開発業務をサポートしていただきました。開発を行う上でわからないことがあったりつまづいたりしたときはすぐにメンターの方へ質問できる環境であったため、実務が初めての私でもスムーズに開発を進めることができました。メンターの方には私の質問に答えていただいただけでなく、多くのアドバイスもしていただきました。その内容は、実務の観点で重要な考え方やエンジニアとして働く上で必要なこと、そして最新の技術動向などで、個人開発を行っているだけでは得られない有益な情報ばかりでした。そのため、インターンの期間を通じて実務的な観点で多くのことを学ぶことができました。
実務的な観点で学んだこととして、適切に技術選定を行うことの重要性があります。React を利用してフロントエンド開発を行う際にもプロジェクトの中で使用する技術の選択肢は多くあり、そのトレンドの移り変わりも速いです。例えば、プロジェクトを進める上で
- CSS をどのような方法で記述・適用するか?
- グローバルな State をどのような方法で管理するか?
- モジュールバンドラー (複数のファイルをビルドして 1 つにまとめるツール) は何を使うか?
といった点について考える必要があり、使用する技術の選択肢は様々です。私が以前 React を用いてフロントエンドの開発を行った際には、深く考えることなく使用技術を決めていました。個人開発を行う上ではどの技術を選択しても大した問題にはならないことが多いですが、実務の観点では技術の選定が重要です。アプリケーションのパフォーマンスや保守、チーム開発での効率などを考慮し、適切な技術を選ぶ必要があります。このことについてメンターの方に詳しく教えていただき、適切に使用技術を決めることの重要性を学びました。
インターンでは、私が今まで使用したことのない技術や知らなかった技術もいくつか使用しました。
初めて使用した技術に、グローバルな状態を管理するライブラリ 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
の基本的な概念として、atom
と useAtom
があります。atom
は状態を保持するためのオブジェクト、useAtom
は atom
を利用するために使用する関数です。
下の例では、countAtom.ts
で数値型を格納する atom
を定義し、0
で初期化しています。UpdateCounter.tsx
で useAtom
を定義し、関数の 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 を実装するため、以下の 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 ヶ月間ありがとうございました。