この記事は、主に下記の記事を参考にしています。
Vueを用いた開発プロジェクト用に「コンポーネント設計・実装ガイドライン」を作った話 - Qiita
Vue使いなら知っておきたいVueのパターン・小技集 - Qiita
ContainerとPresenter
もともとはReactから派生してきた考え方です。
下記記事がわかりやすいです。
[redux] Presentational / Container componentの分離 - react-redux.connect()のつかいかた - Qiita
ComponentとContainerについて - Qiita
Presentational and Container Components - Dan Abramov - Medium
ContainerとPresenterを分けることのメリット
最初はなんでわざわざ分けるのかと不思議でしたが、下記のメリットが大きいのかなと思いました。
同じPresentational Componentに異なる状態ソースを持たせることで、それらを別のContainer Componentsに再利用することができるようになる
ComponentとContainerについて - Qiita より引用
似たようなPresentational Componentが増えてきたらかなり恩恵を受けられるのではないでしょうか。
実装してみる
全体像
-
container
がロジック、presenter
が UI - それぞれのcomponentがに、container, presenterファイルが生成される
-
container
がpresenter
をwrap
する - componentを呼び出す時は、
container
を呼び出す
Container
特徴
・データや振る舞いに責務を持つ
・データや振る舞いをPresentational Componentや他のContainer Componentに提供する
・VuexのstoreやVue Routerのrouteを参照しても良い(しなくても良い)
・通常、DOMのマークアップやCSSスタイルを持たない。(仮にDOMを持つとしても、それはラッパー用のdivタグなど)
Vueを用いた開発プロジェクト用に「コンポーネント設計・実装ガイドライン」を作った話 - Qiita より引用
presenterを読み込む
// presenterの読み込み
// ここでcontainer が presenter を wrap することになる
import SampleModal from './presenter'
// connect 高階関数
const connect = (SampleModal) => {
return {
name: `${SampleModal.name}Container`,
methods: {
handleLogin(social) {
this.$auth.loginWith(social)
}
},
render(h) {
return h(SampleModal, {
props: {
// propsとしてmethod,
handleLogin: this.handleLogin
}
})
}
}
}
export default connect(SampleModal)
高階関数 connect
connect
が、ContainerとPresenterを結びつけるキーとなっています。
connect
は、高階関数で、map
とかと同じですね。
JavaScript 高階関数を説明するよ - Qiita
以下の
connect
は、Presentational Componentを引数に取り、そのコンポーネントが関心を持つ、
VuexのmoduleのデータとVue Routerのメソッドへのアクセスを与えたContainer Componentを返す高階関数
Vueを用いた開発プロジェクト用に「コンポーネント設計・実装ガイドライン」を作った話 - Qiita より引用
containrの呼び出し
com@ponentを使用する時は、container
を呼び出して使用します。
import SampleModal from '@/components/common/Sample/container'
Presenter
特徴
- 見た目に責務を持つ
- コンポーネント自身の状態はほとんど持たない
- propsとしてデータとコールバックを受け取れる
- アクションやストアに依存しない
実装
<script>
export default {
name: 'SampleModal',
// props としてuketoru
props: {
handleLogin: {
type: Function,
default: () => {}
}
},
render(h, context) {
return (
<modal
name="sign-in-modal"
resizable={true}
adaptive={true}
scrollable={true}
width={this.$device.isMobile ? '90%' : '40%'}
height="auto"
>
<div class="modal-sign-in">
<p class="modal-sign-in-word">Sign In</p>
<div class="modal-sign-in-button">
<button
class="modal-sign-in-facebook"
onClick={() => {
this.handleLogin('facebook')
}}
>
<font-awesome-icon
icon={['fab', 'facebook']}
class="modal-sign-in-facebook-font"
/>
<span class="modal-sign-in-facebook-word">
Sign In with Facebook
</span>
</button>
</div>
</div>
</modal>
)
}
}
</script>
// sampleなので、CSSは適当
<style scoped lang="scss">
@import '~assets/scss/variables';
.modal-sign-in {
padding: 10px;
.modal-sign-in-word {
margin: 10px;
}
.modal-sign-in-button {
margin: 5px;
.modal-sign-in-facebook {
max-width: 250px;
.modal-sign-in-facebook-font {
margin-right: 10px;
}
.modal-sign-in-facebook-word {
font-weight: bold;
}
}
}
}
</style>