
Dans cet article, je vais vous expliquer étape par étape comment préparer IndexDB (une base de données intégrée à tout navigateur moderne) pour une utilisation dans des projets écrits en ReactJS. Par conséquent, vous pouvez utiliser les données d'IndexDB aussi facilement que si elles se trouvaient dans le
magasin Redux de votre application.
IndexDB est un SGBD
orientĂ© document, un outil pratique pour le stockage temporaire d'une quantitĂ© relativement petite (unitĂ©s et dizaines de mĂ©gaoctets) de donnĂ©es structurĂ©es cĂŽtĂ© navigateur. La tĂąche standard pour laquelle je dois utiliser IndexDB comprend la mise en cache des donnĂ©es des rĂ©pertoires d'entreprises cĂŽtĂ© client (noms de pays, villes, devises par code, etc.). AprĂšs les avoir copiĂ©s cĂŽtĂ© client, vous ne pouvez tĂ©lĂ©charger qu'occasionnellement des mises Ă jour Ă partir de ces rĂ©pertoires depuis le serveur (ou l'ensemble - ils sont petits) et ne pas le faire Ă chaque fois que vous ouvrez la fenĂȘtre du navigateur.
Il existe des méthodes non standard, trÚs controversées, mais efficaces pour utiliser IndexDB:
- la mise en cache des données sur tous les objets métier de sorte que du cÎté du navigateur, utilisez les capacités de tri et de filtrage étendues
- stockage de l'état de l'application dans IndexDB au lieu de Redux Store
Trois différences clés entre IndexDB et Redux Store sont importantes pour nous:
- IndexDB est un stockage externe qui n'est pas effacĂ© lorsque vous quittez la page. De plus, il en va de mĂȘme pour plusieurs onglets ouverts (ce qui conduit parfois Ă un comportement quelque peu inattendu)
- IndexDB est un SGBD complÚtement asynchrone. Toutes les opérations - ouverture, lecture, écriture, recherche - asynchrone.
- IndexDB ne peut pas (de maniĂšre triviale) ĂȘtre stockĂ© dans JSON et utiliser des techniques de lavage de cerveau Redux pour crĂ©er des instantanĂ©s, faciliter le dĂ©bogage et voyager dans le passĂ©.
Ătape 0: liste des tĂąches
Déjà un exemple classique avec une liste de tùches. Variante avec stockage d'état dans l'état du composant actuel et unique
Implémentation d'un composant de liste de tùches stockant la liste dans un état de composantimport 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>; } }
(
code source github )
Jusqu'à présent, toutes les opérations avec des tùches sont synchrones. Si l'ajout d'une tùche prend 3 secondes, le navigateur se fige juste pendant 3 secondes. Bien sûr, alors que nous gardons tout en mémoire, nous ne pouvons pas y penser. Lorsque nous incluons le traitement avec un serveur ou avec une base de données locale, nous devrons également nous occuper du beau traitement de l'asynchronie. Par exemple, bloquer le travail avec une table (ou des éléments individuels) lors de l'ajout ou de la suppression d'éléments.
Afin de ne pas rĂ©pĂ©ter la description de l'interface utilisateur Ă l'avenir, nous la placerons dans un composant TaskList distinct, dont la seule tĂąche gĂ©nĂ©rera le code HTML de la liste des tĂąches. Dans le mĂȘme temps, nous remplacerons les boutons habituels par un wrapper spĂ©cial autour du bouton d'amorçage, qui bloquera le bouton jusqu'Ă ce que le gestionnaire de boutons termine son exĂ©cution, mĂȘme si ce gestionnaire est une fonction asynchrone.
Implémentation d'un composant stockant une liste de tùches en état réactif 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} /> </>; } }
(
code source github )
Implémentation d'un composant qui affiche une liste de tùches et contient un formulaire pour en ajouter une nouvelle 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 {
(
code source github )
DĂ©jĂ dans l'exemple de code, vous pouvez voir les mots-clĂ©s async / wait. Les constructions asynchrones / attendent peuvent rĂ©duire considĂ©rablement la quantitĂ© de code qui fonctionne avec Promises. Le mot-clĂ© wait vous permet d'attendre une rĂ©ponse d'une fonction renvoyant Promise, comme s'il s'agissait d'une fonction rĂ©guliĂšre (au lieu d'attendre un rĂ©sultat dans then ()). Bien sĂ»r, une fonction asynchrone ne se transforme pas par magie en une fonction synchrone et, par exemple, le thread d'exĂ©cution sera interrompu lorsque l'attente est utilisĂ©e. Mais alors le code devient plus concis et comprĂ©hensible, et wait peut ĂȘtre utilisĂ© Ă la fois dans les boucles et dans les constructions try / catch / finally.
Par exemple,
TaskList
appelle non seulement le gestionnaire
this.props.onAdd
, mais le fait à l'aide du mot clé
this.props.onAdd
. Dans ce cas, si le gestionnaire est une fonction normale qui ne renverra rien ou ne renverra aucune valeur autre que
Promise
, le composant
TaskList
continuera simplement la méthode
handleAdd
de la maniĂšre habituelle. Mais si le gestionnaire renvoie
Promise
(y compris si le gestionnaire est déclaré en tant que fonction asynchrone), alors la
TaskList
attendra la fin de l'exécution du gestionnaire, et ce n'est
newTaskAdding
les valeurs des
newTaskText
newTaskAdding
et
newTaskText
seront réinitialisées.
Ătape 1: ajouter IndexDB au composant React
Pour simplifier notre travail, nous allons d'abord écrire un composant simple qui implémente les méthodes Promise pour:
- ouverture d'une base de données avec gestion des erreurs triviale
- rechercher des éléments dans la base de données
- ajout d'éléments à la base de données
Le premier est le plus «non trivial» - jusqu'à 5 gestionnaires d'événements. Cependant, pas de science fusée:
openDatabasePromise () - ouvre une base de données 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 - encapsule les appels IndexDb dans Promise Mettre tout cela ensemble dans une classe IndexedDbRepository
IndexedDbRepository - wrapper autour d'IDBDatabase const DB_NAME = 'objectStore'; const OBJECT_STORE_NAME = 'objectStore'; export default class IndexedDbRepository { constructor( keyPath ) { this.error = null; this.keyPath = keyPath;
(
code source github )
Vous pouvez maintenant accéder à IndexDB à partir du code:
const db = new IndexedDbRepository( 'id' );
Connectez ce «rĂ©fĂ©rentiel» Ă notre composant. Selon les rĂšgles de react, un appel au serveur doit ĂȘtre dans la mĂ©thode componentDidMount ():
import IndexedDbRepository from '../common/IndexedDbRepository'; componentDidMount() { this.repository = new IndexedDbRepository( 'id' );
Théoriquement, la fonction
componentDidMount()
peut ĂȘtre dĂ©clarĂ©e async, puis les constructions async / attendent peuvent ĂȘtre utilisĂ©es Ă la place de then (). Mais
componentDidMount()
n'est toujours pas «notre» fonction, mais appelé par React. Qui sait comment la bibliothÚque react 17.x se comportera en réponse à une tentative de retour de
Promise
au lieu d'ĂȘtre
undefined
?
Maintenant, dans le constructeur, au lieu de le remplir avec un tableau vide (ou un tableau avec des données de test), nous le remplirons avec null. Et dans le rendu, il traitera ce null comme la nécessité d'attendre le traitement des données. Ceux qui souhaitent, en principe, peuvent mettre cela dans des drapeaux séparés, mais pourquoi produire des entités?
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></>; /* ... */ }
Il reste à implémenter les
handleAdd
/
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() } ); }; }
Dans les deux gestionnaires, nous nous tournons d'abord vers le référentiel pour ajouter ou supprimer un élément, puis nous effaçons l'état du composant actuel et demandons à nouveau une nouvelle liste au référentiel. Il semble que les appels à setState () se feront l'un aprÚs l'autre. Mais le mot-clé wait dans les derniÚres lignes des gestionnaires provoquera le deuxiÚme appel setState () uniquement aprÚs la résolution de Promise () obtenue à partir de la méthode findAll ().
Ătape 2. Ăcoutez les changements
Un gros dĂ©faut dans le code ci-dessus est que, premiĂšrement, le rĂ©fĂ©rentiel est connectĂ© dans chaque composant. DeuxiĂšmement, si un composant modifie le contenu du rĂ©fĂ©rentiel, l'autre composant n'en est pas informĂ© jusqu'Ă ce qu'il relise l'Ă©tat suite Ă des actions de l'utilisateur. C'est gĂȘnant.
Pour lutter contre cela, nous allons introduire le nouveau composant RepositoryListener et le laisser faire deux choses. Ce composant, dans un premier temps, pourra s'abonner aux modifications du référentiel. DeuxiÚmement, RepositoryListener informera le composant qui l'a créé de ces modifications.
Tout d'abord, en ajoutant la possibilité d'enregistrer des gestionnaires dans 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 ); } }
(
code source github )
Nous passerons un tampon aux gestionnaires, qui changera à chaque appel à onChange (). Et nous modifions la méthode _tx pour que
onChange()
appelé pour chaque appel dans une transaction en mode
readwrite
:
async _tx( txMode, callback ) { await this.openDatabasePromise;
(
code source github )
Si nous utilisions toujours
then()
/
catch()
pour travailler avec Promise, nous devrions soit dupliquer l'appel Ă
onChange()
, soit utiliser des polyfills spéciaux pour Promise () qui prennent en charge
final()
. Heureusement, async / wait vous permet de le faire simplement et sans code inutile.
Le composant RepositoryListener lui-mĂȘme connecte un Ă©couteur d'Ă©vĂ©nements dans les mĂ©thodes componentDidMount et componentWillUnmount:
Code 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; } }
(
code source github )
Nous allons maintenant inclure le traitement des modifications du référentiel dans notre composant principal et, guidés
par le principe DRY , nous supprimerons le code correspondant du
handleDelete
handleAdd
/
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();
(
code source github )
Et nous ajoutons un appel à handleRepositoryChanged à partir du RepositoryListener connecté:
render() { return <RepositoryListener onChange={this.handleRepoChanged} repository={this.repository}> <TaskList onAdd={this.handleAdd} onDelete={this.handleDelete} tasks={this.state.tasks} /> </RepositoryListener>; }
(
code source github )
Ătape 3. Retirez le chargement des donnĂ©es et leur mise Ă jour dans un composant distinct
Nous avons écrit un composant qui peut recevoir des données du référentiel, peut changer des données dans le référentiel. Mais si vous imaginez un grand projet avec plus de 100 composants, il s'avÚre que
chaque composant qui affiche des données du référentiel sera obligé de:
- Assurez-vous que le référentiel est correctement connecté à partir d'un seul point
- Fournir le chargement initial des données dans la méthode
componentDidMount()
- Connectez le composant
RepositoryListener
, qui fournit un appel de gestionnaire pour recharger les modifications
Y a-t-il trop d'actions en double? Il semble que non. Et si quelque chose est oublié? Se perdre avec le copier-coller?
Ce serait formidable de s'assurer en quelque sorte que nous écrivons une fois la rÚgle pour obtenir la liste des tùches du référentiel, et quelque chose de magique exécute ces méthodes, nous donne des données, traite les changements dans le référentiel, et pour le tas, il peut également connecter ce référentiel.
this.doFindAllTasks = ( repo ) => repo.findAll(); <DataProvider doCalc={ this.doFindAllTasks }> {(data) => <span>... -, data...</span>} </DataProvider>
Le seul moment non trivial dans l'implémentation de ce composant est que doFindAllTasks () est Promise. Pour faciliter notre travail, nous allons créer un composant distinct qui attend l'exécution de Promise et appelle un descendant avec une valeur calculée:
PromiseComponent Code 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 ); } }
(
code source github )
Ce composant dans sa logique et sa structure interne est trÚs similaire à RepositoryListener. Parce que l'un et l'autre doivent «signer», «écouter» les événements et les traiter d'une maniÚre ou d'une autre. Et gardez également à l'esprit que les événements dont vous devez écouter peuvent changer.
De plus, le composant trÚs magique DataProvider semble jusqu'à présent trÚs simple:
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>; } }
(
code source github )
En effet, nous avons pris le référentiel (et un RepositoryHolder singlenton séparé, qui est maintenant en importation), appelé doCalc, cela vous permettra de transférer des données de tùche vers this.props.children, et donc de dresser une liste de tùches. Singlenton a aussi l'air simple:
const repository = new IndexedDbRepository( 'id' ); export default repository;
Remplacez maintenant l'appel de base de données du composant principal par l'appel 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>; } }
(
code source github )
Cela pourrait s'arrĂȘter. Cela s'est plutĂŽt bien passĂ©: nous dĂ©crivons la rĂšgle de rĂ©ception des donnĂ©es, et un composant sĂ©parĂ© surveille la rĂ©ception rĂ©elle de ces donnĂ©es, ainsi que la mise Ă jour. Il y avait littĂ©ralement quelques petites choses:
- Pour chaque demande de données, quelque part dans le code (mais pas dans la méthode
render()
vous devez décrire la fonction d'accÚs au référentiel, puis transmettre cette fonction au DataProvider - L'appel au DataProvider est grand et tout à fait dans l'esprit de React, mais trÚs moche du point de vue de JSX. Si vous avez plusieurs composants, deux ou trois niveaux d'imbrication de différents DataProviders vous embrouilleront beaucoup.
- Il est regrettable que la rĂ©ception des donnĂ©es se fasse dans un composant (DataProvider) et que leur modification se fasse dans un autre (composant principal). Je voudrais le dĂ©crire avec le mĂȘme mĂ©canisme.
Ătape 4. connect ()
Familier avec react-redux par le titre du titre déjà deviné. Je ferai le conseil suivant au reste: ce serait bien si, au lieu d'appeler children () avec des paramÚtres, le composant de service DataProvider remplirait les propriétés de notre composant en fonction des rÚgles. Et si quelque chose a changé dans le référentiel, cela changerait simplement les propriétés avec le mécanisme React standard.
Pour cela, nous utiliserons le composant d'ordre supĂ©rieur. En fait, ce nâest rien de compliquĂ©, câest juste une fonction qui prend une classe de composants comme paramĂštre et donne un autre composant plus complexe. Par consĂ©quent, notre fonction que nous Ă©crivons sera:
- Prenez comme argument la classe de composants oĂč passer les paramĂštres
- Acceptez un ensemble de rÚgles, comment obtenir des données du référentiel et dans quelles propriétés les mettre
- Dans son utilisation, elle sera similaire Ă la fonction connect () de react-redux .
L'appel Ă cette fonction ressemblera Ă ceci:
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 );
Les premiĂšres lignes
Step4
mappage entre le nom de propriété du composant
Step4
et les valeurs qui seront chargées à partir du référentiel. Vient ensuite le mappage des actions: que se passera-t-il si
Step4
appelez
this.props.doAdd(...)
ou
this.props.doDelete(...)
partir du composant
Step4
. Et la derniÚre ligne rassemble tout et appelle la fonction de connexion. Le résultat est un nouveau composant (c'est pourquoi cette technique est appelée composant d'ordre supérieur). Et nous n'exporterons plus du fichier le composant Step4 d'origine, mais le wrapper qui l'entoure:
class Step4 extends PureComponent { } const Step4Connected = connect( )( Step4 ); export default Step4Connected;
Et le composant de travail avec TaskList ressemble maintenant Ă un simple wrapper:
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} />; } }
(
code source github )
Et c'est tout. Aucun constructeur, aucun gestionnaire supplémentaire - tout est placé par la fonction connect () dans les accessoires du composant.
Reste Ă voir Ă quoi devrait ressembler la fonction connect ().
Code Connect () 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} />; }
(
code source github )
Le code ne semble pas non plus trÚs compliqué ... bien que si vous commencez à plonger, des questions se poseront. Vous devez lire depuis la fin. C'est là que la fonction
connect()
est définie. Il prend deux paramÚtres, puis renvoie une fonction qui renvoie ... encore une fonction? Pas vraiment. La derniÚre construction d'
props => <Connected...
renvoie non seulement une fonction, mais un
composant fonctionnel React .
Ainsi, lorsque nous intégrerons ConnectedStep4 dans une arborescence virtuelle, cela inclura:- parent -> composant fonctionnel anonyme -> Connect -> RepositoryListener -> PromisesComponent -> Step4
Jusqu'à 4 classes intermédiaires, mais chacune remplit sa fonction. Un composant sans nom prend les paramÚtres passés à la fonction connect()
, la classe du composant imbriquĂ©, les propriĂ©tĂ©s qui sont transfĂ©rĂ©es au composant lui-mĂȘme lorsqu'il est appelĂ© (props) et les transmet dĂ©jĂ au composant Connect
. Le composant Connect
est chargé d'obtenir un ensemble à partir des paramÚtres transmis Promise
(un objet dictionnaire avec des clés de ligne et des valeurs de promesse). PromisesComponent
fournit le calcul des valeurs, en les renvoyant au composant Connect, qui, avec les propriétés transférées d'origine (accessoires), les propriétés calculées (valeurs) et les propriétés-actions (actions), les transmet au composant Step4
(via un appel React.createElement(...)
). Eh bien, le composantRepositoryListener
met à jour le composant, forçant à recalculer promis'y si quelque chose a changé dans le référentiel.Par conséquent, si l'un des composants souhaite utiliser le référentiel depuis IndexDb, il lui suffira de connecter une fonction connect()
, de dĂ©terminer le mappage entre les propriĂ©tĂ©s et les fonctions d'obtention de propriĂ©tĂ©s du rĂ©fĂ©rentiel et de les utiliser sans aucun mal de tĂȘte supplĂ©mentaire.Enfin, nous organisons tout cela sous la forme d'une bibliothĂšque afin que d'autres dĂ©veloppeurs puissent l'utiliser dans leurs projets. Le rĂ©sultat de cette Ă©tape a Ă©tĂ©:Au lieu d'une conclusion: ce qui reste Ă la mer
Le code ci-dessus est dĂ©jĂ suffisamment pratique pour ĂȘtre utilisĂ© dans des solutions industrielles. Mais n'oubliez pas les limitations:- Les propriĂ©tĂ©s qui ne changent pas toujours devraient conduire Ă de nouvelles promesses. Ici, vous devez utiliser la fonction de mĂ©morisation, mais prenez en compte l'indicateur de changement de base de donnĂ©es. C'est lĂ que le tampon de IndexedDbRepository est utile (peut-ĂȘtre que ce morceau de code semblait redondant pour quelqu'un).
- La connexion du rĂ©fĂ©rentiel via l'importation, mĂȘme en un seul endroit, est incorrecte. Il faut regarder vers l'utilisation des contextes.
- , IndexedDB .
- : . . IndexDB «» , â .
- , IndexDB Redux Storage. IndexDB , .
Online-