【React Native】Container Componentパターンについて

#はじめに
React(またはReact Native)のデザインパターンについて勉強したので、一つずつ整理することにしました。
今回はContainer Componentパターンについてまとめました。

#Container Componentパターン
Container Componentパターンとは、ロジック(およびState管理やライフサイクル)部分をContainerコンポーネント、表示(見た目)部分をPresentationalコンポーネントに分割して管理するルールのことをいいます。

役割を各コンポーネントに分離してあげることでコードの保守性を高めることができ、結果として、開発者の生産性の向上やプロジェクトの参入障壁の低減に寄与します。

また、State管理をContainerコンポーネントでまとめて行っている(Presentationalコンポーネントにpropsを渡すだけ)ことから、ロジックに対するテストも簡単になります。

#実装例
ログインフォームである`App.jsx`にContainer Componentパターンを適用し、新しくサインインボタンを追加していきます。

```jsx:App.jsx
export const App = () => {
  const InitialUserInfo ={
    isCurrentUser: false,
    user: '',
    password: '',
    email: ''
  };

  const [userInfo, setUserInfo] = useState(InitialUserInfo)

  return (
    <View style={styles.container}>
      <Text style={styles.label}>username</Text>
      <TextInput
        style={styles.input}
        onChangeText={user => setUserInfo({ user })}
        value={userInfo.user}
      />
      <Text style={styles.label}>password</Text>
      <TextInput
        style={styles.input}
        onChangeText={password => setUserInfo({ password })}
        value={userInfo.password}
      />
      {userInfo.isCurrentUser ? (
        // ログインフォーム
        <TouchableOpacity style={styles.fancyButton}>
          <Text style={styles.label}>Log in</Text>
        </TouchableOpacity>
      ) : (
        // サインアップフォーム
        <React.Fragment>
          <Text style={styles.label}>email</Text>
          <TextInput
            style={styles.input}
            onChangeText={email => setUserInfo({ email })}
            value={userInfo.email}
          />
          <TouchableOpacity style={styles.fancyButton}>
            <Text style={styles.label}>Sign up</Text>
          </TouchableOpacity>
        </React.Fragment>
      )}
    </View>
  );
}
```
まずは`App.jsx`の構造を整理します。

Log in, Sign upボタン部分(`<TouchableOpacity />`)やusername, password, emailの入力フォーム部分(`<Text />` `<TextInput />`)をみてみると、構造がよく似ていることがわかります。
そこで、共通化できそうな箇所をPresentationalコンポーネントとして、以下のようにまとめます。
`FancyButton`と`FancyInput`はどちらもStateをもたず、上位のContainerコンポーネントから引数で渡されます。

```jsx
const FancyButton = ({ text, value, item, setItem }) => {
  return (
    <TouchableOpacity 
      style={styles.fancyButton}
      onPress={() => setItem([item], value)}
    >
      <Text style={styles.label}>{text}</Text>
    </TouchableOpacity>
  )
}

const FancyInput = ({ value, item, setItem }) => {
  return (
    <React.Fragment>
      <Text style={styles.label}>{item}</Text>
      <TextInput
        style={styles.input}
        onChangeText={value => setItem([item], value)}
        value={value}
      />
      <TouchableOpacity style={styles.fancyButton}>
        <Text style={styles.label}>Sign up</Text>
      </TouchableOpacity>
    </React.Fragment>
  )
}
```
次にロジック部分をContainerコンポーネント`Container`としてまとめます。
Presentationalコンポーネント内で状態更新を同じように行うために、keyとvalueでプロパティを指定して更新する`setItem`を作成します。

```js
  const setItem = (key, value) => {
    setUserInfo({ [key]: value })
  }
```
新しく追加するSign inボタンは、`FancyButton`に必要な引数を与えてあげるだけで作成されます。

```jsx
          <FancyButton 
            text={'Sign in'} 
            value={userInfo.isCurrentUser ? false : true}
            item={'isCurrentUser'}
            setItem={setItem}
          />
```
最後に`Container`をルートの`App`でラッピングしてあげれば完成です。


```jsx:App.jsx
const Container = () => {
  const InitialUserInfo ={
    isCurrentUser: false,
    user: '',
    password: '',
    email: ''
  };

  const [userInfo, setUserInfo] = useState(InitialUserInfo)

  const setItem = (key, value) => {
    setUserInfo({ [key]: value })
  }

  return (
    <View style={styles.container}>
      <FancyInput 
        value={userInfo.user} 
        item={'user'}  
        setItem={setItem} 
      />
      <FancyInput 
        value={userInfo.password} 
        item={'password'}  
        setItem={setItem} 
      />
      {userInfo.isCurrentUser ? (
        // ログインフォーム
        <FancyButton text={'Log in'} />
      ) : (
        // サインアップフォーム
        <React.Fragment>
          <FancyInput 
            value={userInfo.email} 
            item={'email'}  
            setItem={setItem} 
          />
          <FancyButton text={'Sign up'} />
          <FancyButton 
            text={'Sign in'} 
            value={userInfo.isCurrentUser ? false : true}
            item={'isCurrentUser'}
            setItem={setItem}
          />
        </React.Fragment>
      )}
    </View>
  );
}

export const App = () => {
  return <Container />
}
```

#参考資料

https://www.udemy.com/share/1021u23@aoDrEP-32hw_JRu1JD_i5_Q2gJJNTCHBnq7_D-0WVPEccD1H2kYXmaQjlpgUkYPj/

https://moneyforward.com/engineers_blog/2020/02/18/react-component-rules/