こんにちは、デザインエンジニアのomoteです!
estieでは、一人目のデザインエンジニアのhikaruに加え、先日、きょんしー が入社しました!私、omoteを含め、総勢3人になりました。
HTML, CSS, TypeScript, React で良く指摘するところ・気にしているところをベースにしながら幅広く話すことをテーマに勉強会を開催しました。その名も…
(勉強会のタイトルと討論のテーマはパロディです。)
本記事では前後編に分けて、最近のCSS事情や、Reactコンポーネントの設計の気になることについて話していきたいと思います。
若者のmargin離れが進んでいる件
皆さんは、ul
内のli
間のスペースをどうしてますか?
CSSは、様々な解法があるから、どれが正解かわからず悩んでいませんか?
つい数年前までは、Internet Explorerをはじめとしたクロスブラウザの対応する必要がありました。
そのため、新しいCSSプロパティは使ってはいけないと思い込んでいることもしばしば。
勉強会での声
margin
は左派?右派?ul
にネガティブマージンをつけて、li
にはmargin
を持たせるcalc
使ったなあとか
とはいえ、アプリのコンポーネントの設計において、コンポーネントの外側にmargin
を持たせたくありません。また、margin-bottom
とmargin-top
が重なったりと、marginの制御には隣接するセレクタとのmargin
と打ち消しあったり…。
解答
marginのみでレイアウトを制御すると、li
タグといった子要素にmarginを持たせる必要があり、折り返しをする際など、CSSの設計に複雑さが増します。
親要素であるul
にdisplay:grid;
を使用すると、親要素のみの指定でスペースなどのレイアウトを制御することができます。gap
を積極的に使っていきましょう!
marginでレイアウト
ul { display: flex; flex-wrap: wrap; margin: -8px; } li { width: calc(100% / 3 - 16px); margin: 8px; }
gapでレイアウト
ul { display: grid; gap: 8px; grid-template-columns: 1fr 1fr 1fr; } li { // ここでの指定から解放されます }
@containerが活躍しそうな件
コンテナクエリが全ブラウザで実装されました。
これは従来のメディアクエリのようにブラウザのウインドウではなく、任意の要素(親要素など)を基準にすることで、より柔軟なCSS設計ができるようになりました。
勉強会での声
- 明日からでも使いたい
- コンポーネントの設計がしやすくなりそう
- 右と左で別アプリ出すこともできるかも
estieではデザインシステムを構築するプロジェクトが進んでいます。
その一環で作成する UI Component にも積極的に採用していきます!
organismsは人類には早すぎた件
Atomic Designに基づいた設計を採用したものの、難解だと感じた方は多いのではないのでしょうか。
Atomsは「それ以上機能が分解できない」もの。 Moleculesは「ユーザの動作を促す」もの。 Organismsは「コンテンツが完結している」もの。
引用元:MoleculesとOrganismsのこと|ひらやま
(引用元もhirayamaの記事です!ご覧ください)
Atoms、MoluculesはUIとしての表示が責務なのに対して、OrganismsはAPIの呼び出し、ドメインロジック、stateの管理など、独立したコンテンツとして様々な責務を持たされることが多いです。
勉強会での声
- Organisms、あらゆる責任を押し付けられてそう(バブル期の中間管理職のような偏見)
- Pagesにモーダルを設置するとして、ドメインロジックをどこに置く?
解答
Organismsのようなコンポーネントに、ビジネスロジックとUIを同じファイルに設置し、密結合した状態になると再利用性が著しく下がります。
また、ビジネスロジックがコンポーネントの親子関係を無視し、階層を跨ぐとロジックそのものの見通しが非常に悪くなります。
ここでは、Container(ビジネスロジック) / Presentational(UI・表示ロジック)で、関心を分離するデザインパターンを採用するといいのでは?という結論が出ました。
あくまで、Atomic Designとは別のデザインパターンですが、これらは併用することも可能です。
Container
import useSWR from 'swr' import { useMemo } from 'react' import { DogImages } from '~/components/DogImages' const useDogImagesData = (data) => { const dogImages = useMemo(() => { if (!data) { return undefined; } return data.messages.map(message => { return { src: message, } }) }) return { items } } const DogImagesContainer = () => { // 今回の例では data の fetch を SWR で実施しています const { data } = useSWR('https://dog.ceo/api/breed/labrador/images/random/6', fetch) const { items } = useDogImagesData(data); return ( <DogImages items={items} /> ) }
ContainerではuseDogImages
というロジックを用意しデータの読み出しを行なっています。
ここでは、UIのレンダリングは行いません。
Presentational
const DogImages = ({ items }) => { return !!items && items.length > 0 ? ( <ul> {items.map((item, index) => { return <img src={item.src} alt={`Dog ${index} image`} })} </ul? ) : ( <p>わんちゃんがいませんでした</p> ) }
Presentaitonalでは犬の画像を表示するという責務のみを担っています。
ここではUIの表示をするためのコードを書いています。
引用:Redirecting to: /react/presentational-container-pattern
ちなみに
人類が扱うには適切とのお墨付きをいただきました。
後編に続く!