はじめに
React(またはReact Native)のデザインパターンについて勉強したので、一つずつ整理することにしました。
今回はContainer Componentパターンについてまとめました。
Container Componentパターン
Container Componentパターンとは、ロジック(およびState管理やライフサイクル)部分をContainerコンポーネント、表示(見た目)部分をPresentationalコンポーネントに分割して管理するルールのことをいいます。
役割を各コンポーネントに分離してあげることでコードの保守性を高めることができ、結果として、開発者の生産性の向上やプロジェクトの参入障壁の低減に寄与します。
また、State管理をContainerコンポーネントでまとめて行っている(Presentationalコンポーネントにpropsを渡すだけ)ことから、ロジックに対するテストも簡単になります。
実装例
ログインフォームであるApp.jsx
にContainer Componentパターンを適用し、新しくサインインボタンを追加していきます。
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コンポーネントから引数で渡されます。
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
を作成します。
const setItem = (key, value) => {
setUserInfo({ [key]: value })
}
新しく追加するSign inボタンは、FancyButton
に必要な引数を与えてあげるだけで作成されます。
<FancyButton
text={'Sign in'}
value={userInfo.isCurrentUser ? false : true}
item={'isCurrentUser'}
setItem={setItem}
/>
最後にContainer
をルートのApp
でラッピングしてあげれば完成です。
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 />
}
参考資料