
Dalam artikel ini, saya akan memberi tahu Anda langkah demi langkah bagaimana mempersiapkan IndexDB (database yang dibangun di browser modern) untuk digunakan dalam proyek yang ditulis dalam ReactJS. Sebagai hasilnya, Anda dapat menggunakan data dari IndexDB semudah di
Redux Store aplikasi Anda.
IndexDB adalah DBMS
berorientasi dokumen, alat yang mudah digunakan untuk penyimpanan sementara dengan jumlah yang relatif kecil (unit dan puluhan megabita) data terstruktur di sisi browser. Tugas standar yang harus saya gunakan IndexDB termasuk caching data direktori bisnis di sisi klien (nama negara, kota, mata uang dengan kode, dll.). Setelah menyalinnya ke sisi klien, Anda hanya dapat sesekali mengunduh pembaruan dari direktori ini dari server (atau keseluruhan - mereka kecil) dan tidak melakukan ini setiap kali Anda membuka jendela browser.
Ada cara-cara non-standar, sangat kontroversial, tetapi bekerja untuk menggunakan IndexDB:
- caching data tentang semua objek bisnis sehingga di sisi browser menggunakan kemampuan penyortiran dan penyaringan yang luas
- menyimpan status aplikasi di IndexDB bukan Redux Store
Tiga perbedaan utama antara IndexDB dan Redux Store penting bagi kami:
- IndexDB adalah penyimpanan eksternal yang tidak dihapus saat meninggalkan halaman. Selain itu, sama untuk beberapa tab terbuka (yang terkadang mengarah ke perilaku yang agak tidak terduga)
- IndexDB adalah DBMS yang sepenuhnya tidak sinkron. Semua operasi - membuka, membaca, menulis, mencari - tidak sinkron.
- IndexDB tidak dapat (dengan cara sepele) disimpan dalam JSON dan menggunakan teknik-teknik trik otak dari Redux untuk membuat Snapshots, kemudahan debugging, dan perjalanan ke masa lalu.
Langkah 0: daftar tugas
Sudah menjadi contoh klasik dengan daftar tugas. Varian dengan penyimpanan keadaan dalam keadaan komponen saat ini dan hanya
Implementasi komponen daftar tugas menyimpan daftar dalam status komponenimport React, { PureComponent } from 'react'; import Button from 'react-bootstrap/Button'; import counter from 'common/counter'; import Form from 'react-bootstrap/Form'; import Table from 'react-bootstrap/Table'; export default class Step0 extends PureComponent { constructor() { super( ...arguments ); this.state = { newTaskText: '', tasks: [ { id: counter(), text: 'Sample task' }, ], }; this.handleAdd = () => { this.setState( state => ( { tasks: [ ...state.tasks, { id: counter(), text: state.newTaskText } ], newTaskText: '', } ) ); }; this.handleDeleteF = idToDelete => () => this.setState( state => ( { tasks: state.tasks.filter( ( { id } ) => id !== idToDelete ), } ) ); this.handleNewTaskTextChange = ( { target: { value } } ) => this.setState( { newTaskText: value || '', } ); } render() { return <Table bordered hover striped> <thead><tr> <th>#</th><th>Text</th><th /> </tr></thead> <tbody> { this.state.tasks.map( task => <tr key={task.id}> <td>{task.id}</td> <td>{task.text}</td> <td><Button onClick={this.handleDeleteF( task.id )} type="button" variant="danger"></Button></td> </tr> ) } <tr key="+1"> <td /> <td><Form.Control onChange={this.handleNewTaskTextChange} placeholder=" " type="text" value={this.state.newTaskText || ''} /></td> <td><Button onClick={this.handleAdd} type="button" variant="primary"></Button></td> </tr> </tbody> </Table>; } }
(
kode sumber github )
Sejauh ini, semua operasi dengan tugas bersifat sinkron. Jika menambahkan tugas membutuhkan 3 detik, maka browser hanya akan membeku selama 3 detik. Tentu saja, sementara kita menyimpan semuanya dalam ingatan kita, kita tidak bisa memikirkannya. Ketika kami menyertakan pemrosesan dengan server atau dengan database lokal, kami juga harus menjaga pemrosesan asinkron yang indah. Misalnya, memblokir pekerjaan dengan tabel (atau elemen individual) sambil menambahkan atau menghapus elemen.
Agar tidak mengulangi deskripsi UI di masa mendatang, kami akan menempatkannya di komponen TaskList yang terpisah, satu-satunya tugas yang akan menghasilkan kode HTML dari daftar tugas. Pada saat yang sama, kami akan mengganti tombol yang biasa dengan pembungkus khusus di sekitar Tombol bootstrap, yang akan memblokir tombol sampai penangan tombol menyelesaikan eksekusi, bahkan jika penangan ini adalah fungsi asinkron.
Menerapkan komponen yang menyimpan daftar tugas dalam keadaan bereaksi import React, { PureComponent } from 'react'; import counter from 'common/counter'; import TaskList from '../common/TaskList'; export default class Step01 extends PureComponent { constructor() { super( ...arguments ); this.state = { tasks: [ { id: counter(), text: 'Sample task' }, ] }; this.handleAdd = newTaskText => { this.setState( state => ( { tasks: [ ...state.tasks, { id: counter(), text: newTaskText } ], } ) ); }; this.handleDelete = idToDelete => this.setState( state => ( { tasks: state.tasks.filter( ( { id } ) => id !== idToDelete ), } ) ); } render() { return <> <h1> </h1> <h2> </h2> <TaskList onAdd={this.handleAdd} onDelete={this.handleDelete} tasks={this.state.tasks} /> </>; } }
(
kode sumber github )
Menerapkan komponen yang menampilkan daftar tugas dan berisi formulir untuk menambahkan yang baru import React, { PureComponent } from 'react'; import Button from './AutoDisableButtonWithSpinner'; import Form from 'react-bootstrap/Form'; import Table from 'react-bootstrap/Table'; export default class TaskList extends PureComponent { constructor() { super( ...arguments ); this.state = { newTaskAdding: false, newTaskText: '', }; this.handleAdd = async() => { this.setState( { newTaskAdding: true } ); try {
(
kode sumber github )
Sudah dalam kode sampel Anda dapat melihat kata kunci async / menunggu. Async / wait constructs dapat secara signifikan mengurangi jumlah kode yang berfungsi dengan Janji. Kata kunci yang menunggu memungkinkan Anda untuk menunggu respons dari fungsi yang mengembalikan Janji, seolah-olah itu adalah fungsi biasa (alih-alih menunggu hasil di saat itu ()). Tentu saja, fungsi asinkron tidak secara ajaib berubah menjadi fungsi sinkron, dan, misalnya, utas eksekusi akan terganggu ketika menunggu digunakan. Tapi kemudian kode menjadi lebih ringkas dan mudah dimengerti, dan menunggu dapat digunakan baik dalam loop dan dalam mencoba / menangkap / akhirnya membangun.
Misalnya,
TaskList
memanggil tidak hanya penangan
this.props.onAdd
, tetapi apakah itu menggunakan kata kunci
await
. Dalam kasus ini, jika handler adalah fungsi normal yang tidak akan mengembalikan apa-apa, atau mengembalikan nilai apa pun selain
Promise
, maka komponen
TaskList
hanya
TaskList
melanjutkan metode
handleAdd
dengan cara biasa. Tetapi jika pawang mengembalikan
Promise
(termasuk jika pawang dinyatakan sebagai fungsi async), maka Daftar
TaskList
akan menunggu pawang menyelesaikan eksekusi, dan hanya kemudian akan mengatur ulang nilai
newTaskText
dan
newTaskText
.
Langkah 1: Tambahkan IndexDB ke React Component
Untuk menyederhanakan pekerjaan kami, pertama kami akan menulis komponen sederhana yang mengimplementasikan metode Janji untuk:
- membuka database bersama dengan penanganan kesalahan sepele
- mencari item dalam database
- menambahkan item ke database
Yang pertama adalah yang paling “non-sepele” - sebanyak 5 penangan acara. Namun, tidak ada ilmu roket:
openDatabasePromise () - buka database function openDatabasePromise( keyPath ) { return new Promise( ( resolve, reject ) => { const dbOpenRequest = window.indexedDB.open( DB_NAME, '1.0.0' ); dbOpenRequest.onblocked = () => { reject( ' , , ' + ' .' ); }; dbOpenRequest.onerror = err => { console.log( 'Unable to open indexedDB ' + DB_NAME ); console.log( err ); reject( ' , .' + ( err.message ? ' : ' + err.message : '' ) ); }; dbOpenRequest.onupgradeneeded = event => { const db = event.target.result; try { db.deleteObjectStore( OBJECT_STORE_NAME ); } catch ( err ) { console.log( err ); } db.createObjectStore( OBJECT_STORE_NAME, { keyPath } ); }; dbOpenRequest.onsuccess = () => { console.info( 'Successfully open indexedDB connection to ' + DB_NAME ); resolve( dbOpenRequest.result ); }; dbOpenRequest.onerror = reject; } ); }
getAllPromise / getPromise / putPromise - panggilan IndexDb wrapper dalam Janji Menyatukan Semuanya Menjadi Satu IndexedDbRepository Class
IndexedDbRepository - wrapper seputar IDBDatabase const DB_NAME = 'objectStore'; const OBJECT_STORE_NAME = 'objectStore'; export default class IndexedDbRepository { constructor( keyPath ) { this.error = null; this.keyPath = keyPath;
(
kode sumber github )
Sekarang Anda dapat mengakses IndexDB dari kode:
const db = new IndexedDbRepository( 'id' );
Hubungkan "repositori" ini ke komponen kami. Menurut aturan reaksi, panggilan ke server harus dalam metode componentDidMount ():
import IndexedDbRepository from '../common/IndexedDbRepository'; componentDidMount() { this.repository = new IndexedDbRepository( 'id' );
Secara teoritis, fungsi
componentDidMount()
dapat dideklarasikan sebagai async, kemudian async / menunggu konstruksi dapat digunakan sebagai ganti kemudian (). Tapi masih
componentDidMount()
bukan fungsi "kami", tetapi dipanggil oleh React. Siapa yang tahu bagaimana perpustakaan bereaksi 17.x akan bertindak sebagai tanggapan terhadap upaya untuk mengembalikan
Promise
bukan
undefined
?
Sekarang di konstruktor, alih-alih mengisinya dengan array kosong (atau array dengan data uji), kita akan mengisinya dengan nol. Dan dalam render, ini akan memproses null ini sebagai kebutuhan untuk menunggu pemrosesan data. Mereka yang berharap, pada prinsipnya, dapat menempatkan ini dalam bendera yang terpisah, tetapi mengapa menghasilkan entitas?
constructor() { super( ...arguments ); this.state = { tasks: null }; } render() { if ( this.state.tasks === null ) return <><Spinner animation="border" aria-hidden="true" as="span" role="status" /><span> ...</span></>; /* ... */ }
Tetap menerapkan
handleDelete
/
handleDelete
:
constructor() { this.handleAdd = async( newTaskText ) => { await this.repository.save( { id: counter(), text: newTaskText } ); this.setState( { tasks: null } ); this.setState( { tasks: await this.repository.findAll() } ); }; this.handleDelete = async( idToDelete ) => { await this.repository.deleteById( idToDelete ); this.setState( { tasks: null } ); this.setState( { tasks: await this.repository.findAll() } ); }; }
Di kedua penangan, pertama-tama kita beralih ke repositori untuk menambah atau menghapus item, dan kemudian kita menghapus status komponen saat ini dan sekali lagi meminta daftar baru dari repositori. Tampaknya panggilan ke setState () akan berurutan. Tetapi kata kunci yang menunggu di baris terakhir dari penangan akan menyebabkan panggilan setState () kedua terjadi hanya setelah Janji () yang diperoleh dari metode findAll () diselesaikan.
Langkah 2. Dengarkan perubahannya
Kelemahan besar dalam kode di atas adalah, pertama, repositori terhubung di setiap komponen. Kedua, jika satu komponen mengubah isi repositori, maka komponen lainnya tidak mengetahuinya sampai membaca ulang status sebagai akibat dari tindakan pengguna apa pun. Ini tidak nyaman.
Untuk mengatasi ini, kami akan memperkenalkan komponen RepositoryListener baru dan membiarkannya melakukan dua hal. Komponen ini, pertama, akan dapat berlangganan perubahan dalam repositori. Kedua, RepositoryListener akan memberi tahu komponen yang membuatnya dari perubahan ini.
Pertama-tama, menambahkan kemampuan untuk mendaftarkan penangan di IndexedDbRepository:
export default class IndexedDbRepository { constructor( keyPath ) { this.listeners = new Set(); this.stamp = 0; } addListener( listener ) { this.listeners.add( listener ); } onChange() { this.stamp++; this.listeners.forEach( listener => listener( this.stamp ) ); } removeListener( listener ) { this.listeners.delete( listener ); } }
(
kode sumber github )
Kami akan memberikan stempel kepada penangan, yang akan berubah dengan setiap panggilan ke onChange (). Dan kami memodifikasi metode _tx sehingga
onChange()
dipanggil untuk setiap panggilan dalam transaksi dengan mode
readwrite
:
async _tx( txMode, callback ) { await this.openDatabasePromise;
(
kode sumber github )
Jika kami masih menggunakan
then()
/
catch()
untuk bekerja dengan Promise, kami harus menduplikasi panggilan ke
onChange()
, atau menggunakan polyfill khusus untuk Promise () yang mendukung
final()
. Untungnya, async / await memungkinkan Anda melakukan ini dengan mudah dan tanpa kode yang tidak perlu.
Komponen RepositoryListener itu sendiri menghubungkan pendengar peristiwa dalam metode componentDidMount dan componentWillUnmount:
Kode RepositoryListener import IndexedDbRepository from './IndexedDbRepository'; import { PureComponent } from 'react'; export default class RepositoryListener extends PureComponent { constructor() { super( ...arguments ); this.prevRepository = null; this.repositoryListener = repositoryStamp => this.props.onChange( repositoryStamp ); } componentDidMount() { this.subscribe(); } componentDidUpdate() { this.subscribe(); } componentWillUnmount() { this.unsubscribe(); } subscribe() { const { repository } = this.props; if ( repository instanceof IndexedDbRepository && this.prevRepository !== repository ) { if ( this.prevRepository !== null ) { this.prevRepository.removeListener( this.repositoryListener ); } this.prevRepository = repository; repository.addListener( this.repositoryListener ); } } unsubscribe( ) { if ( this.prevRepository !== null ) { this.prevRepository.removeListener( this.repositoryListener ); this.prevRepository = null; } } render() { return this.props.children || null; } }
(
kode sumber github )
Sekarang, kami akan menyertakan pemrosesan perubahan repositori di komponen utama kami, dan, dipandu
oleh prinsip KERING , kami akan menghapus kode yang sesuai dari
handleDelete
/
handleDelete
:
constructor() { super( ...arguments ); this.state = { tasks: null }; this.handleAdd = async( newTaskText ) => { await this.repository.save( { id: counter(), text: newTaskText } ); }; this.handleDelete = async( idToDelete ) => { await this.repository.deleteById( idToDelete ); }; this.handleRepositoryChanged = async() => { this.setState( { tasks: null } ); this.setState( { tasks: await this.repository.findAll() } ); }; } componentDidMount() { this.repository = new IndexedDbRepository( 'id' ); this.handleRepositoryChanged();
(
kode sumber github )
Dan kami menambahkan panggilan ke handleRepositoryChanged dari RepositoryListener yang terhubung:
render() { return <RepositoryListener onChange={this.handleRepoChanged} repository={this.repository}> <TaskList onAdd={this.handleAdd} onDelete={this.handleDelete} tasks={this.state.tasks} /> </RepositoryListener>; }
(
kode sumber github )
Langkah 3. Keluarkan pemuatan data dan pembaruannya dalam komponen terpisah
Kami menulis komponen yang dapat menerima data dari repositori, dapat mengubah data dalam repositori. Tetapi jika Anda membayangkan proyek besar dengan 100+ komponen, ternyata
setiap komponen yang menampilkan data dari repositori akan dipaksa untuk:
- Pastikan repositori terhubung dengan benar dari satu titik
- Berikan pemuatan data awal dalam metode
componentDidMount()
- Hubungkan komponen
RepositoryListener
, yang menyediakan panggilan penangan untuk memuat ulang perubahan
Apakah ada terlalu banyak tindakan rangkap? Sepertinya tidak. Dan jika ada yang dilupakan? Tersesat dengan copy paste?
Akan sangat bagus untuk memastikan bahwa kita menulis sekali aturan untuk mendapatkan daftar tugas dari repositori, dan sesuatu yang ajaib mengeksekusi metode ini, memberi kita data, memproses perubahan dalam repositori, dan untuk heap itu juga dapat menghubungkan ini repositori.
this.doFindAllTasks = ( repo ) => repo.findAll(); <DataProvider doCalc={ this.doFindAllTasks }> {(data) => <span>... -, data...</span>} </DataProvider>
Satu-satunya momen nontrivial dalam implementasi komponen ini adalah doFindAllTasks () adalah Promise. Untuk memudahkan pekerjaan kami, kami akan membuat komponen terpisah yang menunggu Janji untuk dieksekusi dan memanggil keturunan dengan nilai yang dihitung:
Kode Kompromi Janji import { PureComponent } from 'react'; export default class PromiseComponent extends PureComponent { constructor() { super( ...arguments ); this.state = { error: null, value: null, }; this.prevPromise = null; } componentDidMount() { this.subscribe(); } componentDidUpdate( ) { this.subscribe(); } componentWillUnmount() { this.unsubscribe(); } subscribe() { const { cleanOnPromiseChange, promise } = this.props; if ( promise instanceof Promise && this.prevPromise !== promise ) { if ( cleanOnPromiseChange ) this.setState( { error: null, value: null } ); this.prevPromise = promise; promise.then( value => { if ( this.prevPromise === promise ) { this.setState( { error: null, value } ); } } ) .catch( error => { if ( this.prevPromise === promise ) { this.setState( { error, value: null } ); } } ); } } unsubscribe( ) { if ( this.prevPromise !== null ) { this.prevPromise = null; } } render() { const { children, fallback } = this.props; const { error, value } = this.state; if ( error !== null ) { throw error; } if ( value === undefined || value === null ) { return fallback || null; } return children( value ); } }
(
kode sumber github )
Komponen ini dalam logika dan struktur internalnya sangat mirip dengan RepositoryListener. Karena keduanya harus “menandatangani”, “mendengarkan” peristiwa dan memprosesnya. Dan juga perlu diingat bahwa acara yang perlu Anda dengarkan dapat berubah.
Lebih jauh lagi, komponen ajaib DataProvider sejauh ini terlihat sangat sederhana:
import repository from './RepositoryHolder'; export default class DataProvider extends PureComponent { constructor() { super( ...arguments ); this.handleRepoChanged = () => this.forceUpdate(); } render() { return <RepositoryListener onChange={this.handleRepoChanged} repository={repository}> <PromiseComponent promise={this.props.doCalc( repository )}> {data => this.props.children( data )} </PromiseComponent> </RepositoryListener>; } }
(
kode sumber github )
Memang, kami mengambil repositori (dan RepositoryHolder singlenton terpisah, yang sekarang dalam impor), disebut doCalc, ini akan memungkinkan kami untuk mentransfer data tugas ke this.props.children, dan karenanya, menggambar daftar tugas. Singlenton juga terlihat sederhana:
const repository = new IndexedDbRepository( 'id' ); export default repository;
Sekarang ganti panggilan basis data dari komponen utama dengan panggilan DataProvider:
import repository from './RepositoryHolder'; export default class Step3 extends PureComponent { constructor() { super( ...arguments ); this.doFindAllTasks = repository => repository.findAll(); } render() { return <DataProvider doCalc={this.doFindAllTasks} fallback={<><Spinner animation="border" aria-hidden="true" as="span" role="status" /><span> ...</span></>}> { tasks => <TaskList onAdd={this.handleAdd} onDelete={this.handleDelete} tasks={tasks} /> } </DataProvider>; } }
(
kode sumber github )
Ini bisa berhenti. Ternyata cukup baik: kami menggambarkan aturan untuk menerima data, dan komponen terpisah memantau penerimaan sebenarnya dari data ini, serta pembaruan. Ada beberapa hal kecil:
- Untuk setiap permintaan data, di suatu tempat dalam kode (tetapi tidak dalam metode
render()
Anda perlu menjelaskan fungsi mengakses repositori, dan kemudian meneruskan fungsi ini ke DataProvider - Panggilan ke DataProvider hebat dan cukup dalam semangat React, tetapi sangat jelek dari sudut pandang JSX. Jika Anda memiliki beberapa komponen, maka dua atau tiga level bersarang dari Penyedia Data yang berbeda akan sangat membingungkan Anda.
- Sangat menyedihkan bahwa menerima data dilakukan dalam satu komponen (DataProvider), dan perubahannya dilakukan pada komponen lain (komponen utama). Saya ingin menggambarkannya dengan mekanisme yang sama.
Langkah 4. terhubung ()
Akrab dengan reaksi-redux dengan judul judul yang sudah ditebak. Saya akan membuat petunjuk berikut kepada yang lain: alangkah baiknya jika, alih-alih memanggil anak-anak () dengan parameter, komponen layanan DataProvider akan mengisi properti komponen kami berdasarkan aturan. Dan jika ada sesuatu yang berubah di repositori, maka itu hanya akan mengubah properti dengan mekanisme Bereaksi standar.
Untuk ini kita akan menggunakan Komponen Tingkat Tinggi. Sebenarnya, ini tidak rumit, hanya fungsi yang mengambil kelas komponen sebagai parameter dan memberikan komponen lain yang lebih kompleks. Karena itu, fungsi kita yang kita tulis adalah:
- Ambil sebagai argumen, komponen kelas tempat melewati parameter
- Terima seperangkat aturan, cara mendapatkan data dari repositori, dan properti apa yang akan dimasukkan
- Dalam penggunaannya, ini akan mirip dengan fungsi connect () dari react-redux .
Panggilan ke fungsi ini akan terlihat seperti ini:
const mapRepoToProps = repository => ( { tasks: repository.findAll(), } ); const mapRepoToActions = repository => ( { doAdd: ( newTaskText ) => repository.save( { id: counter(), text: newTaskText } ), doDelete: ( idToDelete ) => repository.deleteById( idToDelete ), } ); const Step4Connected = connect( mapRepoToProps, mapRepoToActions )( Step4 );
Baris pertama
Step4
pemetaan antara nama properti komponen
Step4
dan nilai-nilai yang akan dimuat dari repositori. Selanjutnya muncul pemetaan untuk tindakan: apa yang akan terjadi jika
Step4
memanggil
this.props.doAdd(...)
atau
this.props.doDelete(...)
dari dalam komponen
Step4
. Dan baris terakhir menyatukan semuanya dan memanggil fungsi connect. Hasilnya adalah komponen baru (itulah sebabnya teknik ini disebut Komponen Pesanan Tinggi). Dan kami tidak akan lagi mengekspor dari file komponen Step4 asli, tetapi bungkus di sekitarnya:
class Step4 extends PureComponent { } const Step4Connected = connect( )( Step4 ); export default Step4Connected;
Dan komponen bekerja dengan TaskList sekarang terlihat seperti pembungkus sederhana:
class Step4 extends PureComponent { render() { return this.props.tasks === undefined || this.props.tasks === null ? <><Spinner animation="border" aria-hidden="true" as="span" role="status" /><span> ...</span></> : <TaskList onAdd={this.props.doAdd} onDelete={this.props.doDelete} tasks={this.props.tasks} />; } }
(
kode sumber github )
Dan itu dia. Tidak ada perancang, tidak ada penangan tambahan - semuanya ditempatkan oleh fungsi connect () di dalam penyangga komponen.
Tetap melihat bagaimana fungsi connect () seharusnya terlihat.
Hubungkan () kode import repository from './RepositoryHolder'; class Connected extends PureComponent { constructor() { super( ...arguments ); this.handleRepoChanged = () => this.forceUpdate(); } render() { const { childClass, childProps, mapRepoToProps, mapRepoToActions } = this.props; const promises = mapRepoToProps( repository, childProps ); const actions = mapRepoToActions( repository, childProps ); return <RepositoryListener onChange={this.handleRepoChanged} repository={repository}> <PromisesComponent promises={promises}> { values => React.createElement( childClass, { ...childProps, ...values, ...actions, } )} </PromisesComponent> </RepositoryListener>; } } export default function connect( mapRepoToProps, mapRepoToActions ) { return childClass => props => <Connected childClass={childClass} childProps={props} mapRepoToActions={mapRepoToActions} mapRepoToProps={mapRepoToProps} />; }
(
kode sumber github )
Kode ini juga tampaknya tidak terlalu rumit ... walaupun jika Anda mulai menyelam, pertanyaan akan muncul. Anda harus membaca dari akhir. Di sanalah fungsi
connect()
didefinisikan. Dibutuhkan dua parameter, dan kemudian mengembalikan fungsi yang mengembalikan ... lagi fungsi? Tidak juga. Konstruksi terakhir
props => <Connected...
mengembalikan tidak hanya fungsi, tetapi
komponen Bereaksi fungsional .
Jadi, ketika kita menanamkan ConnectedStep4 ke pohon virtual, itu akan mencakup:- parent -> komponen fungsional anonim -> Connect -> RepositoryListener -> PromisesComponent -> Step4
Sebanyak 4 kelas menengah, tetapi masing-masing melakukan fungsinya. Komponen yang tidak disebutkan namanya mengambil parameter yang dilewatkan ke fungsi connect()
, kelas komponen bersarang, properti yang ditransfer ke komponen itu sendiri ketika dipanggil (props) dan melewati mereka sudah ke komponen Connect
. Komponen Connect
ini bertanggung jawab untuk mendapatkan satu set dari parameter yang diteruskan Promise
(objek kamus dengan kunci baris dan nilai janji). PromisesComponent
memberikan perhitungan nilai, meneruskannya kembali ke komponen Connect, yang, bersama dengan properti yang ditransfer asli (props), properti yang dihitung (nilai) dan properti-tindakan (tindakan), meneruskannya ke komponen Step4
(melalui panggilan React.createElement(...)
). Nah, komponennyaRepositoryListener
memperbarui komponen, memaksa untuk menghitung ulang janji jika ada sesuatu yang berubah dalam repositori.Akibatnya, jika ada komponen yang ingin menggunakan repositori dari IndexDb, itu akan cukup baginya untuk menghubungkan satu fungsi connect()
, menentukan pemetaan antara properti dan fungsi mendapatkan properti dari repositori, dan menggunakannya tanpa sakit kepala tambahan.Akhirnya, kami mengatur semua ini dalam bentuk perpustakaan sehingga pengembang lain dapat menggunakannya dalam proyek mereka. Hasil dari langkah ini adalah:Alih-alih kesimpulan: apa yang tertinggal
Kode di atas sudah cukup praktis untuk digunakan dalam solusi industri. Tapi tetap saja, jangan lupa tentang keterbatasannya:- Tidak selalu mengubah properti harus mengarah pada janji baru. Di sini Anda perlu menggunakan fungsi memoisasi, tetapi memperhitungkan bendera perubahan basis data. Di sinilah cap dari IndexedDbRepository berguna (mungkin untuk beberapa bagian kode ini tampak berlebihan).
- Menghubungkan repositori melalui impor, meskipun di satu tempat, salah. Penting untuk melihat ke arah penggunaan konteks.
- , IndexedDB .
- : . . IndexDB «» , — .
- , IndexDB Redux Storage. IndexDB , .
Online-