📦

【ReactHook】Props名すらカプセル化したい時

2022/12/17に公開
今回使用するサンプル(これを読みやすくしていきます)

// component/MyDialog.tsx(コンポーネントの定義)
interface MyDialog{
  open: boolean
  onClose: ()=>void
}
const MyDialog :FC<MyDialog> = ({open,onClose})=>{
  if(open){
    return /* ダイアログ開く状態の時に表示するもの */
  }else{
    return /* ダイアログ閉じる状態の時に表示するもの */
  }
}

// Home.tsx(利用側)
const Home :FC<{}> = ()=>{
  const [open,setOpen] = useState(false)
  const handleOpen = ()=>setOpen(true)
  const handleClose = ()=>setOpen(false)
  return (
    <>
      <button onClick={handleOpen}>ダイアログ開く</button>
      <MyDialog
        open={open}
        onClose={handleClose}
      >
        ダイアログです
      </MyDialog>
    </>
  )
}

結論


// MyDialog.tsx
const useMyDialog = (initialOpen:boolean = false)=>{
  const [open,setOpen] = useState(false)
  const onOpen = ()=>setOpen(true)
  const onClose = ()=>setOpen(false)
  return {
    open,
    onOpen,
    onClose,
    dialogProps,
  }
}

const MyDialog :FC<MyDialog> = ({open,onClose})=>{
  ... // 略
}

// Home.tsx(利用側)
const Home :FC<{}> = ()=>{
  const {onOpen, dialogProps} = useMyDialog()    // 👈👈👈 🈁 ①
  return (
    <>
      <button onClick={onOpen}>ダイアログ開く</button>
      <MyDialog
        {...dialogProps}      // 👈👈👈 🈁 ②
      >
        ダイアログです
      </MyDialog>
    </>
  )
}


カスタムフックでロジックをカプセル化

カスタムフックで実装の詳細をカプセル化

  // MyDialog.tsx
+ const useMyDialog = (initialOpen:boolean = false)=>{
+   const [open,setOpen] = useState(false)
+   const onOpen = ()=>setOpen(true)
+   const onClose = ()=>setOpen(false)
+   return {
+     open,
+     onOpen,
+     onClose,
+   }
+ }

  const MyDialog :FC<MyDialog> = ({open,onClose})=>{
    ... // 略
  }

  // Home.tsx(利用側)
  const Home :FC<{}> = ()=>{
-   const [open,setOpen] = useState(false)
-   const handleOpen = ()=>setOpen(true)
-   const handleClose = ()=>setOpen(false)
+   const {open,onOpen,onClose} = useMyDialog()
    return (
      <>
        <button onClick={onOpen}>ダイアログ開く</button>
        <MyDialog
          open={open}
          onClose={onClose}
        >
          ダイアログです
        </MyDialog>
      </>
    )
  }

よくなった点

  • ダイアログの状態(open)やそれの変更方法(handleOpen,handleClose)がカプセル化されたことで利用側で必要な行が減った。これすなわち「MyDialog コンポーネントを利用する側で考えないといけないことが減った」ことを意味する。

Props すらカプセル化

Propsすらカプセル化

  // MyDialog.tsx
  const useMyDialog = (initialOpen:boolean = false)=>{
    const [open,setOpen] = useState(false)
    const onOpen = ()=>setOpen(true)
    const onClose = ()=>setOpen(false)
    return {
      open,
      onOpen,
      onClose,
+     dialogProps,
    }
  }

  const MyDialog :FC<MyDialog> = ({open,onClose})=>{
    ... // 略
  }

  // Home.tsx(利用側)
  const Home :FC<{}> = ()=>{
-   const {open,onOpen,onClose} = useMyDialog()
+   const {onOpen, dialogProps} = useMyDialog()
    return (
      <>
        <button onClick={onOpen}>ダイアログ開く</button>
        <MyDialog
-         open={open}
-         onClose={onClose}
+         {...dialogProps}
        >
          ダイアログです
        </MyDialog>
      </>
    )
  }

よくなった点

  • MyDialog コンポーネントを利用する上で考えないといけないことが減った
- MyDialogコンポーネントには
  - openをbooleanで渡す
  - onCloseを関数で渡す

⏬ ⏬ ⏬ これが ⏬ ⏬ ⏬

  - MyDialogコンポーネントには
-   - openをbooleanで渡す
-   - onCloseを関数で渡す
+   - とりあえず脳死でdialogPropsを渡す

結論


// MyDialog.tsx
const useMyDialog = (initialOpen:boolean = false)=>{
  const [open,setOpen] = useState(false)
  const onOpen = ()=>setOpen(true)
  const onClose = ()=>setOpen(false)
  return {
    open,
    onOpen,
    onClose,
    dialogProps,
  }
}

const MyDialog :FC<MyDialog> = ({open,onClose})=>{
  ... // 略
}

// Home.tsx(利用側)
const Home :FC<{}> = ()=>{
  const {onOpen, dialogProps} = useMyDialog()
  return (
    <>
      <button onClick={onOpen}>ダイアログ開く</button>
      <MyDialog
        {...dialogProps}
      >
        ダイアログです
      </MyDialog>
    </>
  )
}

おまけ:アイデアは JetpackCompose から

JetpackCompose とは Android の宣言的 UI で UI を構築するツールです。

React に大きな影響を受けており React から移った自分としては「ほぼ React」感が否めませんでしたが、React(というか JSX)よりも記述量が少なく Kotlin DSL の力を使って補完が気持ちいくらい出るなど開発体験の良いものになっています。

そんな JetpackCompose では以下のように state を複数に分けるのではなく一つのオブジェクトにまとめてprops として渡すことが多いように感じたのがきっかけです。

JetpackComposeの記述例

@Composable
fun MyApp(){
  Scaffold(scaffoldState = scaffoldState) {    // 👈👈👈 🈁
    Button(
      onClick = { ... }
    ) {
      Text("Press me")
    }
  }
}

Discussion