はじめに
ランサーズAdventCalender 20日目の記事です。
前日の記事は
図解!ネットワークの7層を実務に当てはめてみた
でした。(ほんのりいいね数多い!)
今回は業務の中でフロントエンドの開発をする機会があったので、そのために勉強したときのことをまとめようと思います。
自己紹介
まずざっと箇条書きで。
- カッキー Kakki0310
- 渋谷で働く20歳エンジニア
- 業務は主にサーバーサイド(PHPメイン)
- 個人の趣味としてはRuby(rails)を使って開発している
- プログラミング歴は大体1年と半年くらい
- シェアハウスに生息
Reactをメインとしたフロントエンドの技術に関してですが、絶望的にわからなくてhtml、cssが書けるくらいでした(もはやフロントエンドというのかも謎)。
そんな中開発を担当していたプロダクトのフロント部分をReact+Redux+Typescriptで作ろうということになったので、どうにかチャレンジしてみたいということで、超デキる先輩エンジニアの元で粛々と勉強を進めていきました。
何をやったのか
まず僕がフロントエンドの開発をしていた期間でどこまでできるようになったかというと
- Fluxアーキテクチャーの構造にそって実際のプロダクトに対して「あーここだいたいこの辺!」といえるようになった
- コンポーネントの構造を理解する力、「親->子」へとデータを渡していく流れが感覚的につかめるようになった
- 見様見真似は多いものの、大体のコードの内容を理解して書くことができるようになった
の3つくらいです。おそらく。。(レベル感は自信なし)
じゃあどのようにして(どのような順番で)勉強していったかといいますと
- 業務内のフロントのコードレビュー
- Reactの公式チュートリアル(マルバツゲーム)の実施
- 業務でコンポーネント部分のごくごく簡単なissueをこなす
- Reduxの公式チュートリアルを見ながら、ちょっとアレンジして簡素なアプリケーションを作成
- reduxのフローに沿ってサーバーサイドからAPI取得した情報をコンポーネント側で表示するissueをこなす
みたいな感じでした。
1. 業務内のフロントのコードレビュー
文字通り、業務内で先輩の書くコードをひたすらレビューしました。といっても最初はもはや何を書いているのかさっぱりわからないので、逐一説明をもらいながら一緒にレビューをするペアレビューの方式で徐々に徐々に理解していきました。
2. Reactの公式チュートリアル(マルバツゲーム)の実施
それでも実際に書かないと体系的に勉強できない、ということでReactの公式チュートリアルをやってみました。これは自分のローカル環境で立ち上げからやるものではなく、サイト内でコンポーネントの部分のみ勉強ができたのでかなりおすすめです(というかみんな勧めているのではないだろうか、公式だし)。
あとはプロゲートで結構レッスン出してるっぽいので、やってみるといいかもです。
(自分がReactを勉強し始めたときはまだ2セクションくらいだったのが、今確認すると4セクションくらいになってる。。)
3. 業務でコンポーネント部分のごくごく簡単なissueをこなす
例えばこんなコンポーネントがあったとします。
const Post = ({ text, id, onClick }) => {
if (!text) {
return (
<div></div>
);
} else {
return (
<div>
<div>コンテンツ: {content}</div>
<div>名前: {name}</div>
{text}<button onClick={onClick}>削除</button>
</div>
);
}
}
export default Post;
こんなコンポーネントに対して、とある場所にリンクを付けるという簡単なissueをこなしたりしました。
ちなみにこのコンポーネントの「コンテンツ」の部分にリンクをつけると、、、
const Post = ({ text, id, onClick }) => {
if (!text) {
return (
<div></div>
);
} else {
return (
<div>
<a href="hogehoge"><div>コンテンツ: {content}</div></a>
<div>名前: {name}</div>
{text}<button onClick={onClick}>削除</button>
</div>
);
}
}
export default Post;
みたいなかんじです。html書いてるみたいで割と簡単っすね。
ただ気をつけないといけないのは、このコンポーネントが持つ責務?のようなものを崩さないようにすることです。
例えばボタン専用のコンポーネントだった場合、そのボタンの周りに変な余白がついていたら、他のコンポーネントで呼び出しをしたときにちょっと扱いづらいです。
そんな「そのコンポーネントの責務」を意識したりしなかったりして、切り出されたissueに取り組んでいきました。
(このあたりはデザインの内容も絡んでくるので詳細知りたい人は「アトミックデザイン」と検索して調べてみてください。と言っときながら自分は読めていないという。。)
4. Reduxの公式チュートリアルを見ながら、ちょっとアレンジして簡素なアプリケーションを作成
「コンポーネントはちょっとサワれる!!」となったものの、「あれ、じゃあ肝心のデータってどうやって取ってきてんの?」となります。
サーバーサイド側もやっていたため、自分がひたすら書いていたApiの情報を取得していることはわかっていたのですが、そこからどうやってコンポーネントまで情報を持ってくるのかの理解をしていませんでした。
ここでReduxの登場です。ここでReduxか、となりました。
疑問を解決するためreduxの公式チュートリアルに沿って自分でredux+reactのアプリケーションを作ってみることに。ちなみにapiを叩いて情報を取得する部分はこの中ではやっていないです。
またこのreduxを理解するための記事として、以下をかなり参考にしていました。
たぶんこれが一番分かりやすいと思います React + Redux のフロー図解
特にこの中で紹介されているFluxのフロー図はreact + reduxのアプリケーションを実装するにあたってものすごくイメージのしやすい図だったので、何度も見返すことをおすすめします。
5. reduxのフローに沿ってサーバーサイドからAPI取得した情報をコンポーネント側で表示するissueをこなす
いよいよ実務での実装です。
「ここやってみなよ〜」と言われ、現在ログインしているユーザーの情報をフロント側で取得して表示をするまでの実装をやってみることに。
Lancers内で行なっているreduxフローの書き方ですが、
modulesというフォルダを切ってその中にオブジェクトごとのファイルを作成してデータ取得のために必要なすべての定数、関数をその中で定義します。(ActionCreater, reducer, sagaなど)
例文は以下になります。
import { createAction, ActionType, getType } from 'typesafe-actions';
import { call, fork, put, take } from 'redux-saga/effects';
import Api from 'api'; // アプリケーション内で定義しているもの
import { User } from 'models'; // アプリケーション内で定義しているもの
/*
* State
*/
export type StateType = {
data: User | null;
isLoading: boolean;
};
export const initialState: StateType = {
data: null,
isLoading: false,
};
/*
* Action Creator
*/
export const getUserStart = createAction(
'GET_USER_START',
resolve => {
return () => resolve();
}
);
export const getUserOk = createAction(
'GET_USER_OK',
resolve => {
return (data: User, meta: any, statusCode: number) =>
resolve(data, {
...meta,
statusCode,
});
}
);
export const getUserNg = createAction(
'GET_USER_NG',
resolve => {
return (error: string, statusCode: number) =>
resolve({ error }, { statusCode });
}
);
/*
* Action
*/
export const userActions = {
getUserStart,
getUserOk,
getUserNg,
};
export type UserAction = ActionType<typeof userActions>;
/*
* Reducer
*/
export const user = (
state: StateType = initialState,
action: UserAction
) => {
switch (action.type) {
case getType(getUserStart):
return { ...state, isLoading: true };
case getType(getUserOk):
return {
...state,
isLoading: false,
data: action.payload,
};
case getType(getUserNg):
return { ...state, isLoading: false };
default:
return state;
}
};
/*
* Saga
*/
export function* get(action: ReturnType<typeof getUserStart>) {
let response: any;
try {
response = yield call(Api.getUser);
} catch (e) {
yield put(
getUserNg(e.response.data.error.message, e.response.status)
);
return;
}
yield put(
getUserOk(response.data.data, response.data.meta, response.status)
);
}
export function* watchUser() {
while (true) {
const action = yield take(getType(getUserStart));
yield fork(get, action);
}
}
作成した関数をそれぞれrootReducerやrootSagaに加え、storeにセットして,準備完了です。
import { userActions as user } from './modules/user';
export default {
user,
};
import {
watchUser,
} from './modules/user';
export default function* rootSaga() {
yield all([
fork(watchUser),
]);
}
あとは、コンポーネント側で、watchUserを呼び出してやれば(dispatchしてやれば)、User情報を取得してくることができます。(上のコードはtypescriptでごりごり書いているので別途型定義ファイルにActionやReducerの型を定義して型付けを行なっている)
これで、業務の中で(なんちゃってな)フロントエンドの実装ができるようになりました!
最後に
1番から5番までを通してかかった期間は3,4ヶ月ほどでしたが、かなり理解するのが大変で勉強中も戸惑ってしまうことばかりでした。ただ、教えてくださった先輩や実務の中でフロントエンド開発ができたその環境があったからこそ、ここまでできたのかなと思います。
ただまだまだ未熟者ではありますので、再度業務でフロントエンド開発に携われる機会がありましたらフロントエンドの開発を引っ張っていけるような人物になれたらと思っています。
最後まで読んでいただきありがとうございました。
ランサーズではフロントエンドエンジニアを募集しています!
日本の社会課題をテクノロジーでハックする!フロントエンドエンジニア募集