Saya menulis ulang aplikasi saya untuk menggunakan Flux dan saya memiliki masalah dengan mengambil data dari Toko. Saya memiliki banyak komponen, dan mereka banyak bersarang. Beberapa di antaranya besar ( Article
), beberapa kecil dan sederhana ( UserAvatar
, UserLink
).
Saya telah berjuang dengan di mana dalam hierarki komponen saya harus membaca data dari Toko.
Saya mencoba dua pendekatan ekstrim, yang tidak satu pun yang saya sukai:
Semua komponen entitas membaca datanya sendiri
Setiap komponen yang membutuhkan beberapa data dari Store hanya menerima ID entitas dan mengambil entitasnya sendiri.
Misalnya, Article
dilewatkan articleId
, UserAvatar
dan UserLink
dilewatkan userId
.
Pendekatan ini memiliki beberapa kelemahan signifikan (dibahas di bawah contoh kode).
var Article = React.createClass({
mixins: [createStoreMixin(ArticleStore)],
propTypes: {
articleId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
article: ArticleStore.get(this.props.articleId);
}
},
render() {
var article = this.state.article,
userId = article.userId;
return (
<div>
<UserLink userId={userId}>
<UserAvatar userId={userId} />
</UserLink>
<h1>{article.title}</h1>
<p>{article.text}</p>
<p>Read more by <UserLink userId={userId} />.</p>
</div>
)
}
});
var UserAvatar = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<img src={user.thumbnailUrl} />
)
}
});
var UserLink = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<Link to='user' params={{ userId: this.props.userId }}>
{this.props.children || user.name}
</Link>
)
}
});
Kerugian dari pendekatan ini:
- Sungguh membuat frustrasi memiliki 100-an komponen yang berpotensi berlangganan Toko;
- Ini sulit untuk melacak bagaimana data diperbarui dan dalam rangka apa karena masing-masing komponen mengambil data secara independen;
- Meskipun Anda mungkin sudah memiliki entitas dalam status, Anda terpaksa meneruskan ID-nya kepada anak-anak, yang akan mengambilnya lagi (atau merusak konsistensi).
Semua data dibaca sekali di tingkat teratas dan diteruskan ke komponen
Ketika saya lelah melacak bug, saya mencoba menempatkan semua pengambilan data di tingkat atas. Ini, bagaimanapun, terbukti tidak mungkin karena untuk beberapa entitas saya memiliki beberapa tingkat bersarang.
Sebagai contoh:
- A
Category
berisiUserAvatar
orang yang berkontribusi pada kategori itu; - Sebuah
Article
mungkin memiliki beberapaCategory
s.
Oleh karena itu, jika saya ingin mengambil semua data dari Toko di tingkat Article
, saya perlu:
- Ambil artikel dari
ArticleStore
; - Ambil semua kategori artikel dari
CategoryStore
; - Ambil secara terpisah setiap kontributor kategori dari
UserStore
; - Entah bagaimana meneruskan semua data itu ke komponen.
Yang lebih membuat frustrasi, setiap kali saya membutuhkan entitas yang sangat bersarang, saya perlu menambahkan kode ke setiap tingkat bersarang untuk meneruskannya.
Menyimpulkan
Kedua pendekatan tersebut tampaknya cacat. Bagaimana cara mengatasi masalah ini dengan paling elegan?
Tujuan saya:
Toko seharusnya tidak memiliki jumlah pelanggan yang gila-gilaan. Itu bodoh bagi masing-masing
UserLink
untuk mendengarkanUserStore
jika komponen induk sudah melakukan itu.Jika komponen induk telah mengambil beberapa objek dari penyimpanan (misalnya
user
), saya tidak ingin komponen bersarang harus mengambilnya lagi. Saya harus bisa meneruskannya melalui alat peraga.Saya tidak perlu mengambil semua entitas (termasuk hubungan) di tingkat atas karena akan mempersulit penambahan atau penghapusan hubungan. Saya tidak ingin memperkenalkan properti baru di semua level bersarang setiap kali entitas bersarang mendapat hubungan baru (misalnya kategori mendapat a
curator
).
sumber
Jawaban:
Kebanyakan orang memulai dengan mendengarkan penyimpanan yang relevan dalam komponen tampilan pengontrol di dekat bagian atas hierarki.
Nanti, ketika tampaknya banyak props yang tidak relevan diturunkan melalui hierarki ke beberapa komponen bersarang yang dalam, beberapa orang akan memutuskan bahwa sebaiknya biarkan komponen yang lebih dalam mendengarkan perubahan di penyimpanan. Ini menawarkan enkapsulasi yang lebih baik dari domain masalah yang merupakan cabang lebih dalam dari pohon komponen ini. Ada argumen bagus yang harus dibuat untuk melakukan ini dengan bijaksana.
Namun, saya lebih suka untuk selalu mendengarkan di atas dan hanya meneruskan semua data. Saya kadang-kadang bahkan akan mengambil seluruh negara bagian toko dan menyebarkannya melalui hierarki sebagai satu objek, dan saya akan melakukan ini untuk beberapa toko. Jadi saya akan memiliki prop untuk
ArticleStore
status, dan satu lagi untukUserStore
status, dll. Saya menemukan bahwa menghindari controller-views yang sangat bersarang mempertahankan titik masuk tunggal untuk data, dan menyatukan aliran data. Jika tidak, saya memiliki banyak sumber data, dan ini bisa menjadi sulit untuk di-debug.Pengecekan jenis lebih sulit dengan strategi ini, tetapi Anda dapat menyiapkan "bentuk", atau jenis template, untuk large-object-as-prop dengan PropTypes dari React. Lihat: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -validasi
Perhatikan bahwa Anda mungkin ingin meletakkan logika data asosiasi antara penyimpanan di penyimpanan itu sendiri. Jadi Anda
ArticleStore
kekuatanwaitFor()
yangUserStore
, dan termasuk Pengguna yang relevan dengan setiapArticle
record menyediakan melaluigetArticles()
. Melakukan ini dalam pandangan Anda terdengar seperti mendorong logika ke dalam lapisan tampilan, yang merupakan praktik yang harus Anda hindari jika memungkinkan.Anda mungkin juga tergoda untuk menggunakan
transferPropsTo()
, dan banyak orang suka melakukan ini, tetapi saya lebih suka menjaga agar semuanya tetap eksplisit agar mudah dibaca dan karenanya dapat dipelihara.FWIW, pemahaman saya adalah bahwa David Nolen mengambil pendekatan serupa dengan kerangka kerja Om-nya (yang agak kompatibel dengan Flux ) dengan satu titik entri data pada simpul akar - padanan di Flux adalah hanya memiliki satu tampilan pengontrol mendengarkan semua toko. Ini dibuat efisien dengan menggunakan
shouldComponentUpdate()
dan struktur data yang tidak berubah yang dapat dibandingkan dengan referensi, dengan ===. Untuk struktur data yang tidak dapat diubah, periksa mori David atau -js Facebook yang tidak dapat diubah . Pengetahuan saya yang terbatas tentang Om terutama berasal dari The Future of JavaScript MVC Frameworkssumber
Pendekatan di mana saya tiba adalah meminta setiap komponen menerima datanya (bukan ID) sebagai prop. Jika beberapa komponen bersarang membutuhkan entitas terkait, terserah komponen induk untuk mengambilnya.
Dalam contoh kami,
Article
harus memilikiarticle
prop yang merupakan objek (mungkin diambil olehArticleList
atauArticlePage
).Karena
Article
juga ingin merenderUserLink
danUserAvatar
untuk penulis artikel akan berlanggananUserStore
dan mempertahankanauthor: UserStore.get(article.authorId)
statusnya. Ini kemudian akan membuatUserLink
danUserAvatar
dengan inithis.state.author
. Jika mereka ingin menyebarkannya lebih jauh, mereka bisa. Tidak ada komponen turunan yang perlu mengambil pengguna ini lagi.Untuk mengulangi:
Ini memecahkan masalah saya dengan cukup baik. Contoh kode yang ditulis ulang untuk menggunakan pendekatan ini:
var Article = React.createClass({ mixins: [createStoreMixin(UserStore)], propTypes: { article: PropTypes.object.isRequired }, getStateFromStores() { return { author: UserStore.get(this.props.article.authorId); } }, render() { var article = this.props.article, author = this.state.author; return ( <div> <UserLink user={author}> <UserAvatar user={author} /> </UserLink> <h1>{article.title}</h1> <p>{article.text}</p> <p>Read more by <UserLink user={author} />.</p> </div> ) } }); var UserAvatar = React.createClass({ propTypes: { user: PropTypes.object.isRequired }, render() { var user = this.props.user; return ( <img src={user.thumbnailUrl} /> ) } }); var UserLink = React.createClass({ propTypes: { user: PropTypes.object.isRequired }, render() { var user = this.props.user; return ( <Link to='user' params={{ userId: this.props.user.id }}> {this.props.children || user.name} </Link> ) } });
Ini membuat komponen terdalam tetap bodoh tetapi tidak memaksa kita untuk memperumit komponen tingkat atas.
sumber
Solusi saya jauh lebih sederhana. Setiap komponen yang memiliki statusnya sendiri diperbolehkan untuk berbicara dan mendengarkan penyimpanan. Ini adalah komponen yang sangat mirip pengontrol. Komponen bersarang yang lebih dalam yang tidak mempertahankan status tetapi hanya merender barang tidak diizinkan. Mereka hanya menerima props untuk rendering murni, sangat mirip tampilan.
Dengan cara ini semuanya mengalir dari komponen stateful ke komponen stateless. Menjaga statefuls tetap rendah.
Dalam kasus Anda, Artikel akan menjadi stateful dan oleh karena itu berbicara ke toko dan UserLink dll hanya akan merender sehingga akan menerima article.user sebagai prop.
sumber
Masalah yang dijelaskan dalam 2 filosofi Anda adalah umum untuk aplikasi satu halaman mana pun.
Mereka dibahas secara singkat dalam video ini: https://www.youtube.com/watch?v=IrgHurBjQbg dan Relay ( https://facebook.github.io/relay ) dikembangkan oleh Facebook untuk mengatasi pengorbanan yang Anda jelaskan.
Pendekatan Relai sangat sentris data. Ini adalah jawaban untuk pertanyaan "Bagaimana cara mendapatkan hanya data yang diperlukan untuk setiap komponen dalam tampilan ini dalam satu kueri ke server?" Dan pada saat yang sama, Relay memastikan bahwa Anda memiliki sedikit penggandaan di seluruh kode saat komponen digunakan dalam beberapa tampilan.
Jika Relay bukan merupakan pilihan , "Semua komponen entitas membaca datanya sendiri" tampaknya merupakan pendekatan yang lebih baik bagi saya untuk situasi yang Anda gambarkan. Saya pikir kesalahpahaman di Flux adalah apa itu toko. Konsep penyimpanan tidak ada menjadi tempat di mana model atau koleksi benda disimpan. Toko adalah tempat sementara tempat aplikasi Anda meletakkan data sebelum tampilan dirender. Alasan sebenarnya mereka ada adalah untuk memecahkan masalah ketergantungan di seluruh data yang ada di penyimpanan yang berbeda.
Apa yang Flux tidak tentukan adalah bagaimana sebuah toko berhubungan dengan konsep model dan kumpulan objek (a la Backbone). Dalam hal ini beberapa orang sebenarnya membuat toko fluks sebagai tempat untuk meletakkan koleksi objek dari tipe tertentu yang tidak disiram untuk sepanjang waktu pengguna membiarkan browser tetap terbuka tetapi, seperti yang saya pahami, fluks, itu bukan penyimpanan. seharusnya.
Solusinya adalah memiliki lapisan lain tempat Anda tempat entitas yang diperlukan untuk membuat tampilan Anda (dan kemungkinan lainnya) disimpan dan terus diperbarui. Jika Anda lapisan ini yang model dan koleksi abstrak, tidak masalah jika Anda subkomponen harus query lagi untuk mendapatkan datanya sendiri.
sumber