LoginSignup
6
1

More than 5 years have passed since last update.

本日は iOS でお馴染みの Toggle Switch です。
code: github / $ yarn 1204

1204.jpg 1204-1.jpg

コンポーネントの使用感は以下の通り。props は全て optional で、型定義の通りボタン毎に指定が可能です。

<ToggleSwitch />
type Props = {
  width?: number
  height?: number
  inactiveColor?: string
  activeColor?: string
  defaultChecked?: boolean
  onChangeChecked?: (checked: boolean) => void
  className?: string
}

useToggleSwitch

今回定義する Custom Hooks です。useEffect では、checked の変更に反応し props から受け付ける callback handler を叩きます。stateスキーマは単純で、checked のみを保持しています。

type State = {
  checked: boolean
}
function useToggleSwitch(props: Props) {
  const [state, update] = useState<State>(...)
  const options = useMemo(...)
  const nodeStyle = useMemo(...)
  const baseStyle = useMemo(...)
  const knobStyle = useMemo(...)
  const inputStyle = useMemo(...)
  const handleToggle = useCallback(...)
  useEffect(...)
  return {
    state,
    nodeStyle,
    baseStyle,
    knobStyle,
    inputStyle,
    handleToggle
  }
}

Optional Injection

State とは別に、Custom Hooks 内部で「Options」を保持、幅・高さ・色を変更可能にしています。Optional なので、規定値を生成する defaultOptions 関数を定義します。

const defaultOptions = (): Options => ({
  width: 50,
  height: 30,
  inactiveColor: '#eee',
  activeColor: '#4ed164'
})

useMemo で memoize しつつ、規定値と props をマージする処理を行うことで、ロジックに必要な設定値を確保します。lodash.merge を利用することで、面倒な undefined マージ処理を簡略化することができます。

const options = useMemo(
  (): Options =>
    merge(defaultOptions(), {
      width: props.width,
      height: props.height,
      inactiveColor: props.inactiveColor,
      activeColor: props.activeColor,
      onChangeChecked: props.onChangeChecked
    }),
  [
    props.width,
    props.height,
    props.inactiveColor,
    props.activeColor,
    props.onChangeChecked
  ]
)

useState による値保持との違いは「props による影響を初期限定にするか、継続的に影響を受けるか」です。useState で確保された state は、初期マウント時以降、props の変更を受け付けません。変更するためには同時に生成された更新関数を利用する必要があります。Custom Hooks を利用する Component では、以下の様に Optional Injection を受けます。

const View = (props: Props) => {
  const {
    state,
    nodeStyle,
    baseStyle,
    knobStyle,
    handleToggle
  } = useToggleSwitch({
    width: props.width,
    height: props.height,
    checked: props.defaultChecked,
    inactiveColor: props.inactiveColor,
    activeColor: props.activeColor,
    onChangeChecked: props.onChangeChecked
  })
  return (
    <div className={props.className} style={nodeStyle}>
      <span className="base" style={baseStyle} />
      <span className="knob" style={knobStyle} />
      <input
        type="checkbox"
        onChange={handleToggle}
        checked={state.checked}
      />
    </div>
  )
}

不要な汎用化はNG

今日のサンプルは汎用化のためのテクニックとして、Optional Injection を紹介しました。プロダクトコードでこの様な汎用化が常に必要とは限りません。汎用化を必要最小限に留めることも時として必要です。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1