Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

フロントのディレクトリ構成

 フロントのディレクトリ構成

2022.07.29 LT会 古家さん発表資料

More Decks by PharmaX(旧YOJO Technologies)開発チーム

Other Decks in Technology

Transcript

  1. フロントのディレクトリ構成 1 フロントのディレクトリ構成 ├─ components/ │ ├─ elements/ │ │

    └─ button │ │ └─ Button.tsx │ │ └─ ButtonDisabled.tsx │ └─ layouts/ │ └─ header │ └─ Header.tsx │ └─ sideMenu │ └─ footer ├─ pages/ ├─ features/ │ └─ /messageList │ ├─ api/ │ └─ fetchMessageList.ts │ ├─ states/ │ ├─ constants/ │ ├─ components/ │ ├─ index.tsx │ ├─ MessageListHeader.tsx │ ├─ MessageListTalkBox.tsx │ ├─ MessageListTalkBoxMessage.tsx │ ├─ hooks/ │ └─ types/ │ └─ index.ts ├─ states/ ├─ constants/ ├─ hooks/ ├─ libs/ ├─ styles/ └─ types/ 各ディレクトリの役割 components elements アプリケーション全体で使う共通コンポーネントを置く。 例えばセレクトボックスなど。 具体例
  2. フロントのディレクトリ構成 2 /components/elements/select/SelectWithMultiple.tsx export const SelectWithMultiple: React.FC<Props> = ({ label,

    menuItemList, onChange }) => { const [isOpen, setIsOpen] = useState<boolean>(false); return ( <div> <Select isOpen={isOpen} onClick={() => setIsOpen(!isOpen)}> <div className="select__label"> {label} </div> <img className="select__pulldown-icon" src="/images/pulldown.svg" alt=" プルダウン" /> </Select> <MenuItemList style={{ display: isOpen === false ? 'none' : 'block' }}> {menuItemList.map((menuItem, index) => ( <MenuItem key={menuItem.id} > {menuItem.checked === true ? <MenuItemActiveCheckbox onClick={() => onChange(index, !menuItem.checked)}> <div className="menu-item__check-mark" /> </MenuItemActiveCheckbox> : <MenuItemCheckbox onClick={() => onChange(index, !menuItem.checked)} /> } <div className="menu-item__label" style={{color: menuItem.color ? menuItem.color : '' }}> {menuItem.label} </div> </MenuItem> ))}
  3. フロントのディレクトリ構成 3 </MenuItemList> </div> ); }; layouts アプリケーション全体で使うレイアウトコンポーネントを置く。 例えば、Header やFooter

    など 具体例 components/layouts/header/LayoutHeader.tsx export const LayoutHeader: React.FC = () => ( <Header> <div className="header-left"> <StaffProfile> <div className="staff-profile-left"> <img src="/images/dummyStaffProfileImg.svg" alt=" スタッフ画像" /> </div> <div className="staff-profile-right"> <div className="pharmacy-name">YOJO 薬局四⾕店</div> <div className="staff-name"> 養⽣KANAKO</div> </div> </StaffProfile> </div> <div className="header-right"> <PatientIcon> <div className="header-wrapper header-wrapper__patient-icon"> <img src="/images/patientIcon.svg" alt=" 患者" /> <div className="label"> 患者</div> </div> </PatientIcon> <StaffIcon> <div className="header-wrapper header-wrapper__staff-icon"> <img src="images/staffIcon.svg" alt=" スタッフ" /> <div className="label"> スタッフ</div> </div> </StaffIcon> <HeaderBorder /> <NotificationIcon> <div className="header-wrapper header-wrapper__notification-icon"> <img src="images/bell.svg" alt=" 通知" /> <div className="circle" /> </div> </NotificationIcon> </div> </Header> ); pages Next のページコンポーネントを⼊れる。 全体のページを表すもの 具体例
  4. フロントのディレクトリ構成 4 この場合はチャットが出来る画⾯なのでChatPage コンポーネント https://www.figma.com/file/1DvdJugCrzT5kkTW9FSSe8/UIdesign?node-id=613%3A786 const ChatPage = styled.div` display:

    flex; flex-direction: column; height: 100%; `; const Contents = styled.div` display: flex; justify-content: space-between; height: calc(100% - 101px); `; const Home: NextPage = () => ( <ChatPage> <LayoutHeader /> <Contents> <TalkRoomCardList /> <TalkContainer /> <PatientDetailInfo /> </Contents> </ChatPage> ); export default Home; features
  5. フロントのディレクトリ構成 5 ある特定の機能、ドメインでしか使わないapi へのアクセサや定数、型、hooks 、コンポーネントなど 全てを詰め込む。 状態はstore だとredux と紛らわしく、atom だとrecoil

    を知らない⼈がみた時にひと⽬で状態管理⽤ のディレクトリだと分かりにくいのでstates という命名にした。 features/components index.tsx から同ディレクトリのコンポーネントを読みこむルールで統⼀ index.tsx にstate をもたせる 命名はパスカルケース 機能名+ コンポーネント名にすることでこの機能に依存していることを表して、すぐに使ってい る場所が分かるようにする。 共通化したいUI パーツはルートディレクトリの/components のelement やlayout に置く メソッドを書く場所 onXXX などのイベントハンドラ系 コンポーネント内に書く formatXXX 、filteredXXX などの整形系 コンポーネントの外に書く 具体例
  6. フロントのディレクトリ構成 8 export const userState = atom({ key: 'user', default:

    { id: null, name: '', email: '' } }) constants アプリケーション全体の定数を置く 例:カラーコード、レイアウト系の定数 具体例 const colors = { main: { primary: { light: '#DBEEE3', main: '#7DB894', dark: '#569870', }, secondary: { light: '#838383', main: '#6A6A6A', dark: '#464646', }, }, warning: { positive: { light: '#DBEEE3', main: '#7DB894', dark: '#569870', }, negative: { light: '#FFE1E1', main: '#F39191', dark: '#D36A6A', }, }, sub: { a: { light: '#DFDC92', main: '#B9B552', dark: '#676418', }, b: { light: '#D4DEFF', main: '#839EFF', dark: '#3A51A3', }, }, text: { positive: { light: '#878787', main: '#4D4B4B', dark: '#2B2B2B', }, negative: { light: '#FFE1E1', main: '#F39191', dark: '#D36A6A', }, link: { light: '#DBEEE3', main: '#7DB894',
  7. フロントのディレクトリ構成 9 dark: '#569870', }, onDark: { light: '#E3E3E3', main:

    '#FFFFFF', dark: '#A5A5A5', }, }, background: { primary: { main: '#FFFFFF', dark1: '#F5F5F5', dark2: '#D9D9D9', dark3: '#707070', }, secondary: { main: '#DBEEE3', dark1: '#ACD3BC', dark2: '#7DB894', dark3: '#569870', }, }, }; export default colors; hooks アプリケーション全体で使⽤するcustom Hook を置く場所 ログインの状態を全ページで⾒るhook を全ページのroot で呼びだす 例:useRequireLogin.ts import { useEffect } from 'react' import { useRouter } from 'next/router' import { useFirebaseAuth } from 'util/firebase/client' /** * ログイン状態を確認し、未ログインの場合はログインページへリダイレクトするカスタムフック */ export const useRequireLogin = (): void => { const { user, authInitializing } = useFirebaseAuth() const router = useRouter() useEffect(() => { if (authInitializing) return if (!user) router.push('/') // ログイン画⾯へリダイレクト }, [authInitializing, user, router]) } libs ライブラリのラッパーなど Firebase のクライアントはここに置く
  8. フロントのディレクトリ構成 10 styles アプリケーション全体で反映したいスタイルを置く場所 CSS の初期設定 例:global.css * { box-sizing:

    border-box; } html { margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; max-width: 50rem; padding: 0.5rem; margin: 0 auto; font-size: 100%; } a { color: inherit; text-decoration: none; } types アプリケーション全体で使う型定義ファイル 独⾃の型定義ファイル、@types モジュールに無いライブラリの型定義ファイルなど Swagger で⾃動出⼒される型やfirestore の型を置いている