初めに
- Reactの実装ドキュメントやライブラリは充実している
- 要件通り動くだけのコードを作ることは誰でもできるが、保守運用改善は大変である
- 綺麗なコードほどコード修正は早くなり、デバックは効率的になり、開発者間の認識の共有は楽になる
前提
- 基本: 数行レベルで綺麗なコードが書けること
- 中級: コンポーネント単位で綺麗なコードが書けること
1. 無駄なアルゴリズムの適応はするな
- 問題提起されてない箇所に勝手に適応していないか?
- ベンチマークが数段高くても、ユーザ体験的には誤差でないか?
// NG🤔 制御フローの理解,例外ケースの確認,一定時間要してしまう
const sumArray = (numbers: number[]): number => {
let sum = 0;
for (let i = 0, j = numbers.length - 1; i < j; i++, j--) {
sum += numbers[i] + numbers[j];
}
return sum;
}
// Good🥴 シンプルなコードは第三者でも拡張しやすい
const sumArray = (numbers: number[]): number => {
let sum = 0;
for (const number of numbers) {
sum += number;
}
return sum;
}
2. <>JSX部分</>
に集中できる構成になっていること
- JSX部分は、リリース後も変更機会が多い
- 「類似コードを使い回すデータ取得」と「オリジナルが多い重要な部分」は切り離されるべき
// NG🤔 重要な部分が直感的に把握できない
const Area = () => {
const [user, setUser] = useState();
// ここの記述が多いからここが一番重要か?
useEffect(() => {
const fetchUser = async (): Promise<void> => {
const response = await GetUser();
setUser(response);
};
fetchUser();
}, []);
// ここまでのデータの流れを把握するのに時間がかかる
return <>{user.name}</>;
};
// Good😎 「データ取得」と「状態管理/デザイン」の分離
const useUser = (): User | undefined => {
const [user, setUser] = useState<User>();
useEffect(() => {
const fetchUser = async (): Promise<void> => {
const response = await GetUser();
setUser(response);
};
fetchUser();
}, []);
return user;
};
// データの表示のコンポーネント
const Area = () => {
const user = useUser();
// 直感的に重要な部分に集中できる
return <>{user?.name}</>;
};
3. データの入力でuseStateを多用しない
// NG🤔 将来的に入力項目が増えるとどうなるか...
const UserForm = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const changeName = (e) => setName(e.target.value);
const changeEmail = (e) => setEmail(e.target.value);
return (
<form onSubmit={onSubmit}>
<input type='text' value={name} onChange={changeName} />
<input type='text' value={email} onChange={changeEmail} />
<input type='submit' value='Submit' />
</form>
);
};
// Good😎 userオブジェクトでsubmitデータをまとめて管理
const UserForm = () => {
const [user, setUser] = useState({ name: '', email: '' });
const changeName = (e) =>
setUser((state) => ({ ...user, name: e.target.value }));
const changeEmail = (e) =>
setUser((state) => ({ ...user, email: e.target.value }));
return (
<form onSubmit={onSubmit}>
<input type='text' value={name} onChange={changeName} />
<input type='text' value={email} onChange={changeEmail} />
<input type='submit' value='Submit' />
</form>
);
};
// Cool😎: useFormを使う
const UserForm = () => {
const { register} = useForm();
return (
<form onSubmit={onSubmit}>
<input {...register("name")} />
<input {...register("email")} />
<input type='submit' value='Submit' />
</form>
);
};
4. オプショナルに対して、デフォルト引数は使用できているか
type Props = {
name: string;
isDisplayNone?: boolean; // オプショナルのプロップス
};
// NG🤔: undefined=falseで処理
const UserCard: FC<Props> = ({ name, isDisplayNone }) => (
<div style={{ display: isDisplayNone ? 'none' : 'block' }}>{name}</div>
);
// Good😎: undefinedの時はfalseと明示する
const UserCard: FC<Props> = ({ name, isDisplayNone = false }) => (
<div style={{ display: isDisplayNone ? 'none' : 'block' }}>{name}</div>
);
// 表示するとき isDisplayNone undefined -> false
<UserCard name='名前' />;
// 表示しないとき isDisplayNone true
<UserCard name='名前' isDisplayNone />;
5. ファイルImport記述を意識しているか
- コンポーネントの大まかな構成を把握するために、
import
はよく見られる
/* NG🤔
├── Layout
├── Header.tsx
└── Footer.tsx
*/
// page.tsx それぞれimport
import Header from './Layout/Header';
import Footer from './Layout/Footer';
/* Good🥴
├── Layout
├── Header.tsx
├── Footer.tsx
└── index.tsx
*/
// index.tsx
export { default as Header } from './Header';
export { default as Footer } from './Footer';
// page.tsx まとめてimport
import { Footer, Header } from './Layout';
どの階層のファイルからも同じパス名でimportできる
// NG:
import { Footer, Header } from '../../../../Layout';
// Good:😎ファイル移動系の修正の影響範囲を抑えることができる
import { Footer, Header } from '@components/Layout';
設定方法
next.config.js
module.exports = {
webpack: (config, _) => {
config.resolve.alias = {
...config.resolve.alias,
'@components': path.resolve(__dirname, 'components'),
};
return config;
},
};
tsconfig.json
{
"paths": {
"@component/*": ["components/*"]
}
}
6. 配列メソッドを使いこなそう
- 関数を定義する回数が減る
- 以下の5例が使えていれば困らない
const data = [1,2,10]
const isIncludedNumber2 = data.includes(2);
const isIncludedNumber2Or3 = data.some((item) => item === 2 || item === 3);
const number3= data.find((item) => item === 3);
const numbersLargerThan2 = data.filter((item) => item > 2);
const totalNumber = data.reduce((sum, num) => sum + num, 0);
7. サービス独自の値の管理方法は統一する
- サービス独自で定義された値がコードに直で書かれていたら理解するのが難しくなる
- 認識の相違が生まれないように書けているか?
// NG🤔 status:0が何を意味するのか
const CheckStatus = (status: number) => {
if (status === 0) {
return true;
}
return false;
};
// Good🥴
const Status = {
Invalid: 0,
Valid: 1,
} as const;
type StatusLiteral = typeof Status[keyof typeof Status]; // 0 | 1
const CheckStatus = (status: StatusLiteral) => {
if (status === Status.Invalid) {
return true;
}
return false;
};
最後に
「綺麗なコード」よりも「正確に動くコード」が求められがちである。確かに、運用後、正常に動いている既存コードをあえて綺麗なコードに置き換えるのは、リスクが高い、綺麗なコードが書ける能力は重要だが、それと同じくらい、既存のコードと上手く付き合っていくことが求められている。
「綺麗なコード」のメリットは、開発者間の認知負荷を軽減して、重要なポイントを明確にできることだと思う。上記で紹介したような決まった書き方をすれば良いということではなく、自分が書いたコードは「客観的に理解し難いか」を意識して取り組むだけでも,周りにその意識は伝わるものです。