はじめに
主にフロントエンドの領域でお仕事をさせていただく中で、どのようなことを意識して開発を行っているのかを自己理解を含めて記事として残しておきたいと思います。
これからフロントエンド開発に従事する方々の多少なりとも参考になりますと幸いです。
なお、最近の業務ではVue.js(Nuxt.js)を使った開発に携わることが多いのですが、個人的な趣向としてはReact.js、TypeScriptを好みますので記事に出るコードはこちらを用いて表現したいと思います。
UIに対して意識したこと
前提としてUI/UXはデザイナーだけで考えるものでなく、エンジニアでも考えてコーディングをする必要がある箇所だと思っています。
なお私がUIについて調べ、認識をしている定義はユーザーが触れるweb上のパーツ等の当たり前に考慮がされてあり、考慮が出来ていないとユーザーを困らせるもの
であるという認識を心がけています。
ラベル要素とインプット要素のidによる紐付け
とても基本的なことなのですが、基本的なことすぎて、逆にUIへの意識が薄いエンジニアの方すると、少し軽視されていることが多いものになると思っています。
こちらの対応を怠ると直接インプット要素をクリックしないと、いけなくなるのでユーザーとしてはとても使いづらくなります。基本的にこちらの対応は怠らないようにしましょう。
formタグを積極的に使う
pcでフォームなどのsubmitを動作させるときに皆さんはいちいちカーソルにボタンに持ってきたりしますでしょうか?
おそらくenter keyを押すのではないのかなと思っています。
こちらもformタグのsubmitイベントを介して動作をさせることで解消をできるので積極的にformタグを扱いましょう。
focusが当たっている箇所がどこなのかを意識
例としてなのですが、modalを開いたとして、その時のfocusはどこにありますでしょうか?
こちらについてもmodalを開いた時に今ユーザーが関心を持っているのはモーダル内のコンテンツ
だと思います。
focusがモーダル内のボタンにfocusを当ててすぐにサブミットができるようにする等の対応を心がけることによってユーザーのことを考えたUIを提供できると思っています。
disabledの理由がちゃんと理解をできるか
フロントエンドの開発においては沢山のボタンがあり、そのボタンはある条件のもとにdisabledの状態にされることがあると思っています。
その時にユーザーは『なぜdisabledになっているのか?』ということを理解できるようになっていますでしょうか?
ユーザーはこの考えるという動作にとてもストレスを感じてしまうと思っています。
この時に開発者でさえ、この状態が分からないとなるとユーザーも当然分からないと思われるので、デザイナーさん等に相談をして適切に対処をするようにしましょう。
CSSに関するところ
css変数を適切に管理
プロジェクトが始まる段階で必ずプロジェクトで使う変数のベースを作っていきましょう。個人的にはz-index
についても全体的なレイアウトに関わるところは変数化することをおすすめしたいです。下記のようにしておくことでz-indexの上下の位置関係が見えやすくなるので個人的に好きな管理方法の一つです。
$z-header: 100;
$z-header-menu: 101;
$z-overlay: 300;
$z-scroll-button: 90;
React周りの記述に関するところ
冗長なpropsの渡し方の改善
下記はpropsとしてはとてもシンプル例なのですが、propsの数が増えると冗長です。改善後のようにスプレッド構文の展開で表現ができますので出来るだけ後者の使用をお勧めさせていただきます。
改善前
<HeaderMenuButton
handleMenuChange={handleMenuChange}
isMenuOpen={isMenuOpen}
/>
改善後
<HeaderMenuButton {...{ handleMenuChange, isMenuOpen }} />
@Yametaro さんの 5歳娘「パパ、余分なpropsいっぱい書くんだね!」がとても分かりやすく参考になると思います。
コンポーネントに多くの状態を持たせないようにする
コンポーネントでprops等によって様々な状態に変更をさせることがあると思うのですが、コンポーネント内で複数の条件分岐を書き始めたときはコンポーネントの分割を検討してください。
特に子コンポーネントに渡したprops同士で条件を記述すると、後々に修正が困難になりますので注意をしましょう。
コンポーネントを作成する時のディレクトリー構成
Buttonコンポーネントごとにnamed exportをされていると仮定をして、別のコンポーネントで複数のボタンをimportをしたいとなった時に複数のimportをコンポーネントでする必要がある。それを改善する
改善前
button
├── BackButton.tsx
├── CautionButton.tsx
├── NextButton.tsx
├── YellowButton.tsx
// 一つ一つimportをする必要がある。少し手間
import { BackButton } from 'components/Button/BackButton'
import { CautionButton } from 'components/Button/YellowButton'
import { NextButton } from 'components/Button/YellowButton'
import { YellowButton } from 'components/Button/YellowButton'
改善後
index.ts
ファイルを作成してここで先にimportをしておく、その後にコンポーネント側でのimportの記述を減らしシンプルにすることができる
button
├── BackButton.tsx
├── CautionButton.tsx
├── NextButton.tsx
├── YellowButton.tsx
└── index.ts // ここを追加。button階層でまとめるのではなく、もう一つ上の親階層でルートを作ってもいいと思います。
// こちらのファイルを介するようにする。余談ですがアルファベット順が好きです。
export { BackButton } from './BackButton'
export { CautionButton } from './YellowButton'
export { NextButton } from './YellowButton'
export { YellowButton } from './YellowButton'
import { BackButton, CautionButton, NextButton, YellowButton } from 'components/Button'
コンポーネントの命名について
- 以前に書いたこちらの記事でもあるとおり、buttonやinputなどの基本となるコンポーネントには接頭辞を使うようにする。個人的にはよく
Button.tsx
=>BaseButton.tsx
のような形で命名しています。
コンポーネントに直接マージンを持たせない
こちらも基本的なことなのですが、コンポーネントに直接余白をもたせると様々なレイアウトに対応がしづらくなってしまいます。
親コンポーネント方から余白等の操作するように心がけましょう。
※賛否分かれるかもですが親からclassNameのpropsを渡して対応をすることはあります。
親で基本余白を組みますが、タグが増えすぎるのは逆に見づらくなる原因にもなるため
TypeScriptで意識をしていること
今の2021年時点のフロントエンド開発においてはTypeScriptは必須のものになっていると思います。
自分もまだまだ日々学んでいる所ではありますが、現時点の理解を含めて記述したい。
独自Utility型の作成
TypeScriptでは便利な型をUtility Typesとして提供をされています。
そのUtilityの型を用いれば達成したいことはある程度のことは対応が可能です。
ただ自分で拡張をして作成した型をプロジェクトで好きな時に使いたい事があると思っています。
その際に私は下記のようにして対応をしています。
declare namespace Utilty {
// 指定をしたKeysで指定をしたユニオンをnullを合わせたユニオン型へする変更をさせる。
export type Nullable<T, Keys extends keyof T> = {
[P in keyof T]: Keys extends P ? T[P] | null : T[P]
}
// keyofのvalueバージョン
export type valueOf<T> = T[keyof T]
}
上記はdeclare namespaceでグローバルなUtiltyとういう名前空間を作成して、独自のUtiltyの型を作成しています。下記はサンプルです。
type SampleUserType = {
name: string
age: number
birthDay: Date
}
// 出力 => string | number | Date
type ValueOfSample = Utilty.valueOf<SampleUserType>
上記は独自の型を自身で作成したものになりますが,ライブラリーとして提供をされているものをあるので、そちらを使用してみるのもいいかもしれないです。
定数からの型の作成
下記はiconがあるパスを渡してiconを表示するシンプルなコンポーネントです。
import React from 'react'
export const BaseIcon = ({ iconPath }: { iconPath: string }): JSX.Element => {
return <img src={iconPath} />
}
悪いわけではないのですが、この時にiconPathがstringになってしまっています。
これはTypeScriptを導入しているのであれば改善すべき点です。
私であれば下記のようにして改善をします。
const ICON = {
BLACK: {
ARROW_NEXT: '/icon/black/arrow_next.svg',
CONFIGURATION: '/icon/black/configuration.svg',
EDIT: '/icon/black/edit.svg',
HISTORY: '/icon/black/history.svg',
INSTAGRAM: '/icon/black/instagram.svg',
ITEM: '/icon/black/item.svg',
LIVE: '/icon/black/live.svg',
MAIL: '/icon/black/mail.svg',
NEWS: '/icon/black/news.svg',
OPEN: '/icon/black/open.svg',
PAY: '/icon/black/pay.svg',
PRICE_SYSTEM: '/icon/black/price_system.svg',
SEARCH: '/icon/black/search.svg',
STAFF: '/icon/black/staff.svg',
STAR: '/icon/black/star.svg',
STORE: '/icon/black/store.svg',
TIKTOK: '/icon/black/tiktok.svg',
USER: '/icon/black/user.svg',
},
GREEN: {
CHAT: '/icon/green/chat.svg',
},
PURPLE: {
ATTENTION: '/icon/purple/attention.svg',
CHECK: '/icon/purple/check.svg',
HEART: '/icon/purple/heart.svg',
HEART_CIRCLE: '/icon/purple/heart_circle.svg',
ITEM: '/icon/purple/item.svg',
STAR: '/icon/purple/star.svg',
UNREAD: '/icon/purple/unread.svg',
},
WHITE: {
ARROW_NEXT: '/icon/white/arrow_next.svg',
ARROW_PREV: '/icon/white/arrow_prev.svg',
CROWN: '/icon/white/crown.svg',
ITEM: '/icon/white/item.svg',
},
} as const
// 先ほど作成をしたvalueOfを使用
export type IconPathType =
| Utilty.valueOf<typeof ICON['BLACK']>
| Utilty.valueOf<typeof ICON['GREEN']>
| Utilty.valueOf<typeof ICON['PURPLE']>
| Utilty.valueOf<typeof ICON['WHITE']>
import React from 'react'
import { IconPathType } from 'constants/icon'
// string が IconPathType に変更されました。
export const BaseIcon = ({ iconPath }: { iconPath: IconPathType }): JSX.Element => {
return <img src={iconPath} />
}
このようにしてLiteralTypes と UnionsTypesを駆使することによって、iconとしてより厳格なコンポーネントを作成する事ができました。
ここで意識をするべき点としては、型を定義する時に、『より厳格に定義をできないか』というところを一度考える思考を持つ必要があると考えています。『とりあえずstringで~』となんとなく以前書いていた頃もあるのですが、こちらは改善ができてよかったです。
使用側のコンポーネントにはimportした定数をpropsに設定せずに、コンポーネントに型を定義して対応
先ほどのBaseIconでの型定義には上記のタイトルのようなメリットもあると思っています。
先ほどIcon
という定数を作成しているので、コンポーネントの使用側でIconの定数を呼び出したくなるかもしれません。
JavaScriptであればそうするかもしれませんが、上記のTypeScriptで型をコンポーネントにあてた場合であれば、コンポーネントに対してパスを既に定義しているため、定数をわざわざimportしなくてもよくなります。
個人的にとても開発体験が良いです。
localstorageを適切に管理しよう
以前にこちらの記事でも書いたのですが、localstorageは便利である反面、管理をしっかりしないと意図しない挙動になることが多いです。自分はTypeScriptでLocalStorageのクラスを作成して管理するやり方が個人的な好みです。
最後に
まだまだフロントエンドの領域としては勉強不足なところもあるため、下記はこれからのフロントエンド領域に関しての課題として意識していきたいと思います。
- webアクセシビリティ
- パフォーマンスチューニング
- 自動テスト(cypressに興味あり)
自分はこういうところを意識しているなーというのがあればコメントいただけると嬉しいです。こちら最後までお読み頂いた方々ありがとうございました。