React + IndexDb +自动更新=几乎是AsyncRedux

在本文中,我将逐步告诉您如何准备IndexDB(任何现代浏览器中内置的数据库)以用于用ReactJS编写的项目中。 结果,您可以像使用应用程序的Redux存储一样方便地使用IndexDB中的数据。

IndexDB面向文档 DBMS,这是一种方便的工具,用于在浏览器端临时存储相对少量(单位和数十兆字节)的结构化数据。 我必须使用IndexDB的标准任务包括在客户端缓存业务目录的数据(国家/地区名称,城市名称,按代码显示的货币等)。 将它们复制到客户端后,您只能偶尔从服务器(或整个目录-很小)从这些目录下载更新,而不是每次打开浏览器窗口时都执行此操作。

有一些非标准的,很有争议的但使用IndexDB的可行方法:

  • 缓存有关所有业务对象的数据,以便在浏览器端使用广泛的排序和过滤功能
  • 将应用程序状态存储在IndexDB中而不是Redux Store中

IndexDB和Redux Store之间的三个主要区别对我们很重要:

  1. IndexDB是一个外部存储,离开页面时不会清除。 此外,多个打开的标签页也是一样的(有时会导致某些意外情况)
  2. IndexDB是一个完全异步的DBMS。 所有操作-打开,读取,写入,搜索-异步。
  3. IndexDB不能(以简单的方式)存储在JSON中,并且不能使用Redux的脑力激荡技术来创建Snapshots,调试方便以及过去的旅程。

步骤0:任务列表


已经是带有任务列表的经典示例。 在当前和唯一组件的状态下具有状态存储的变体

任务列表组件的实现,该任务列表组件以组件状态存储列表
import 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>; } } 
github源代码

到目前为止,所有带任务的操作都是同步的。 如果添加任务需要3秒钟,那么浏览器将冻结3秒钟。 当然,虽然我们将所有内容都保留在内存中,但我们无法考虑它。 当我们使用服务器或本地数据库进行处理时,我们还必须注意异步处理的优美性。 例如,在添加或删除元素时阻止使用表(或单个元素)。

为了以后不再重复对UI的描述,我们将其放置在单独的TaskList组件中,该组件的唯一任务将为任务列表生成HTML代码。 同时,我们将用引导程序按钮周围的特殊包装替换常规按钮,该包装将阻塞按钮,直到按钮处理程序完成其执行,即使该处理程序是异步函数也是如此。

实现以反应状态存储任务列表的组件
 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} /> </>; } } 
github源代码

一个组件的实现,该组件显示任务列表并包含用于添加新任务的表单
 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 { //   ,     await this.props.onAdd( this.state.newTaskText ); this.setState( { newTaskText: '' } ); } finally { this.setState( { newTaskAdding: false } ); } }; this.handleDeleteF = idToDelete => async() => await this.props.onDelete( 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.props.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 disabled={this.state.newTaskAdding} 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>; } } 
github源代码

在示例代码中,您已经可以看到关键字async / await。 异步/等待构造可以大大减少可用于Promises的代码量。 关键字await允许您等待返回Promise的函数的响应,就好像它是常规函数一样(而不是在then()中等待结果)。 当然,异步函数不会神奇地变成同步函数,例如,当使用await时,执行线程将被中断。 但是随后代码变得更加简洁易懂,并且可以在循环和try / catch / finally结构中使用await。

例如, TaskList调用 this.props.onAdd处理程序,而且使用await关键字进行调用 。 在这种情况下,如果处理程序是将不返回任何内容或返回除Promise之外的任何值的普通函数,则TaskList组件TaskList handleAdd常规方式继续执行handleAdd方法。 但是,如果处理程序返回Promise (包括如果将处理程序声明为异步函数),则TaskList将等待处理程序完成执行,然后才重置newTaskAddingnewTaskText

步骤1:将IndexDB添加到React组件


为了简化我们的工作,首先我们将编写一个简单的组件来实现Promise方法:

  • 打开数据库以及简单的错误处理
  • 在数据库中搜索项目
  • 将项目添加到数据库

第一个是最“平凡的”-多达5个事件处理程序。 但是,没有火箭科学:

openDatabasePromise()-打开数据库
 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-Promise中的包装器IndexDb调用
 //    ObjectStore,   IDBRequest //     Promise function wrap( methodName ) { return function() { const [ objectStore, ...etc ] = arguments; return new Promise( ( resolve, reject ) => { const request = objectStore[ methodName ]( ...etc ); request.onsuccess = () => resolve( request.result ); request.onerror = reject; } ); }; } const deletePromise = wrap( 'delete' ); const getAllPromise = wrap( 'getAll' ); const getPromise = wrap( 'get' ); const putPromise = wrap( 'put' ); } 

将所有内容放到一个IndexedDbRepository类中

IndexedDbRepository-IDBDatabase的包装器
 const DB_NAME = 'objectStore'; const OBJECT_STORE_NAME = 'objectStore'; /* ... */ export default class IndexedDbRepository { /* ... */ constructor( keyPath ) { this.error = null; this.keyPath = keyPath; //     async //      this.openDatabasePromise = this._openDatabase(); } async _openDatabase( keyPath ) { try { this.dbConnection = await openDatabasePromise( keyPath ); } catch ( error ) { this.error = error; throw error; } } async _tx( txMode, callback ) { await this.openDatabasePromise; // await db connection const transaction = this.dbConnection.transaction( [ OBJECT_STORE_NAME ], txMode ); const objectStore = transaction.objectStore( OBJECT_STORE_NAME ); return await callback( objectStore ); } async findAll() { return this._tx( 'readonly', objectStore => getAllPromise( objectStore ) ); } async findById( key ) { return this._tx( 'readonly', objectStore => getPromise( objectStore, key ) ); } async deleteById( key ) { return this._tx( 'readwrite', objectStore => deletePromise( objectStore, key ) ); } async save( item ) { return this._tx( 'readwrite', objectStore => putPromise( objectStore, item ) ); } } 
github源代码

现在您可以从代码访问IndexDB:

  const db = new IndexedDbRepository( 'id' ); // ,     await db.save( { id: 42, text: 'Task text' } ); const item = await db.findById( 42 ); const items = await db.findAll(); 

将此“存储库”连接到我们的组件。 根据反应规则,对服务器的调用应在componentDidMount()方法中:

 import IndexedDbRepository from '../common/IndexedDbRepository'; /*...*/ componentDidMount() { this.repository = new IndexedDbRepository( 'id' ); //     this.repository.findAll().then( tasks => this.setState( { tasks } ) ); } 

从理论上讲,可以将componentDidMount()函数声明为异步,然后可以使用async / await构造代替then()。 但是componentDidMount()仍然不是“我们的”函数,而是被React调用。 谁知道react 17.x库将如何响应尝试返回Promise而不是undefined

现在在构造函数中,我们将用null填充它,而不是用空数组(或包含测试数据的数组)填充它。 在渲染中,它将根据需要等待数据处理的情况来处理此null。 原则上希望这样做的人可以将其放在单独的标记中,但是为什么要产生实体?

  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></>; /* ... */ } 

它仍然需要实现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() } ); }; } 

在这两个处理程序中,我们首先转到存储库以添加或删除项目,然后清除当前组件的状态,然后再次从存储库中请求新列表。 似乎对setState()的调用将一个接一个地进行。 但是,处理程序最后几行中的await关键字仅在从findAll()方法获得的Promise()被解析之后才导致第二个setState()调用发生。

步骤2.聆听变更


上面代码中的一个大缺陷是,首先,存储库连接在每个组件中。 其次,如果一个组件更改了存储库的内容,那么另一组件将不知道它,直到它由于任何用户操作而重新读取状态为止。 这很不方便。

为了解决这个问题,我们将引入新的RepositoryListener组件,并使其做两件事。 首先,该组件将能够订阅存储库中的更改。 其次,RepositoryListener将这些更改通知创建它的组件。

首先,添加了在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 ); } } 

github源代码

我们将向处理程序传递一个戳记,该戳记将在每次调用onChange()时更改。 然后,我们修改_tx方法,以便在具有readwrite模式的事务中为每个调用调用onChange()

  async _tx( txMode, callback ) { await this.openDatabasePromise; // await db connection try { const transaction = this.dbConnection.transaction( [ OBJECT_STORE_NAME ], txMode ); const objectStore = transaction.objectStore( OBJECT_STORE_NAME ); return await callback( objectStore ); } finally { if ( txMode === 'readwrite' ) this.onChange(); // notify listeners } } 

github源代码

如果我们仍然使用then() / catch()来与Promise一起使用,我们将不得不复制对onChange()的调用,或者对Promise()使用支持final()特殊polyfills。 幸运的是,异步/等待允许您简单地执行此操作,而无需不必要的代码。

RepositoryListener组件本身在componentDidMount和componentWillUnmount方法中连接事件侦听器:

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; } } 
github源代码

现在,我们将在主组件中包括存储库更改的处理,并且在DRY原则的指导 ,我们将从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(); // initial load } 

github源代码

然后,从连接的RepositoryListener中添加对handleRepositoryChanged的调用:

  render() { /* ... */ return <RepositoryListener onChange={this.handleRepoChanged} repository={this.repository}> <TaskList onAdd={this.handleAdd} onDelete={this.handleDelete} tasks={this.state.tasks} /> </RepositoryListener>; } 

github源代码

步骤3.在单独的组件中进行数据的加载和更新


我们编写了一个组件,该组件可以从存储库接收数据,可以更改存储库中的数据。 但是,如果您想象一个包含100多个组件的大型项目,那么事实证明,显示存储库中数据的每个组件都将被迫执行以下操作:

  • 确保从单个点正确连接存储库
  • componentDidMount()方法中提供初始数据加载
  • 连接RepositoryListener组件,该组件提供处理程序调用以重新加载更改

是否有太多重复的动作? 似乎并非如此。 如果忘记了什么? 迷糊复制粘贴?

确保以某种方式确保一旦编写了从存储库中获取任务列表的规则,这很妙,并且神奇地执行了这些方法,为我们提供了数据,处理了存储库中的更改,并且对于堆,它还可以将其连接起来资料库。

 this.doFindAllTasks = ( repo ) => repo.findAll(); /*...*/ <DataProvider doCalc={ this.doFindAllTasks }> {(data) => <span>...   -,   data...</span>} </DataProvider> 

该组件实现中唯一不平凡的时刻是doFindAllTask​​s()是Promise。 为了方便我们的工作,我们将创建一个单独的组件,等待Promise执行并调用具有计算值的后代:

PromiseComponent代码
 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 ); } } 

github源代码

该组件的逻辑和内部结构与RepositoryListener非常相似。 因为彼此都必须“签名”,“监听”事件并以某种方式处理它们。 另外请记住,您需要听的事件可能会发生变化。

此外,到目前为止,非常神奇的组件DataProvider看起来非常简单:

 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>; } } 

github源代码

实际上,我们采用了名为doCalc的存储库(以及现在正在导入的单独的singlenton RepositoryHolder),这将使我们能够将任务数据传输到this.props.children,从而绘制任务列表。 Singlenton看起来也很简单:

 const repository = new IndexedDbRepository( 'id' ); export default repository; 

现在,将主组件中的数据库调用替换为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>; } } 

github源代码

这可能会停止。 结果非常好:我们描述了接收数据的规则,并且有一个单独的组件监视此数据的实际接收以及更新。 从字面上看,有几件事:

  • 对于每个数据请求,您需要在代码中的某个地方(而不是render()方法中render() ,描述访问存储库的功能,然后将此功能传递给DataProvider
  • 对DataProvider的调用非常棒,完全符合React的精神,但是从JSX的角度来看非常难看。 如果您有多个组件,则不同的DataProvider的两个或三个嵌套级别将使您非常困惑。
  • 令人遗憾的是,数据的接收是在一个组件(DataProvider)中完成的,而它们的更改是在另一个组件(主组件)中完成的。 我想用相同的机制来描述它。

步骤4.连接()


熟悉react-redux的标题已经猜到了标题。 我将对其余内容进行以下提示:如果DataProvider服务组件根据规则填充了组件的属性,而不是使用参数调用children(),那将是很好的。 并且,如果存储库中发生了某些更改,则只需使用标准React机制更改属性即可。

为此,我们将使用高阶组件。 实际上,这并不复杂,它只是一个将组件类作为参数并提供另一个更复杂的组件的函数。 因此,我们编写的函数将是:

  • 以组件类作为参数传递参数
  • 接受一组规则,如何从存储库中获取数据以及将其放入哪些属性中
  • 在使用中,它将类似于react-redux的connect()函数

对该函数的调用将如下所示:

 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 ); 

第一行Step4组件的属性名称和将从存储库加载的值之间Step4映射。 接下来是操作映射:如果从Step4组件中调用this.props.doAdd(...)this.props.doDelete(...) ,将会发生什么。 最后一行将所有内容组合在一起,并调用connect函数。 结果是一个新的组件(这就是为什么将此技术称为“高阶组件”的原因)。 而且我们将不再从文件中导出原始Step4组件,而是围绕它的包装器:

 /*...*/ class Step4 extends PureComponent { /*...*/ } /*...*/ const Step4Connected = connect( /*...*/ )( Step4 ); export default Step4Connected; 

现在,使用TaskList的组件看起来像一个简单的包装器:

 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} />; } } 

github源代码

就是这样。 没有构造函数,没有其他处理程序-一切都由connect()函数放置在组件的props中。

剩下的要看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} />; } 
github源代码

该代码似乎也不是很复杂...尽管如果您开始潜水,则会出现问题。 您需要从头开始阅读。 在那里定义了connect()函数。 它需要两个参数,然后返回一个函数,该函数又返回...一个函数? 不完全是 最后的props => <Connected...构造不仅返回一个函数,而且返回一个React函数组件 因此,当我们将ConnectedStep4嵌入虚拟树中时,它将包括:

  • 父->匿名功能组件->连接-> RepositoryListener-> PromisesComponent-> Step4

多达4个中间类,但每个都执行其功能。未命名的组件采用传递给函数的参数connect(),嵌套组件的类,在调用时传递给组件本身的属性(props),并将其已经传递给组件Connect。该组件Connect负责从传递的参数Promise(具有行键和Promise值的字典对象)中获取集合PromisesComponent提供值的计算,并将其传递回Connect组件,Connect组件与原始传输的属性(props),计算的属性(值)和action属性(actions)一起,将它们传递给组件Step4(通过call React.createElement(...))。好吧,组件RepositoryListener更新组件,如果存储库中发生了某些更改,则强制重新计算有偿。

结果,如果任何组件都想使用IndexDb中的存储库,那么他只要连接一个功能connect(),定义属性与从存储库中获取属性的功能之间的映射,就可以使用它们而不会造成任何其他麻烦。

步骤5. @ vlsergey / react-indexdb-repo


最后,我们以库的形式安排所有这些内容,以便其他开发人员可以在其项目中使用它。此步骤的结果是:



而不是得出结论:什么都遗忘了


上面的代码已经足够实用,可以在工业解决方案中使用。但是,请不要忘记这些限制:

  • 并非总是更改属性应该带来新的希望。在这里,您需要使用备忘录功能,但要考虑数据库更改标志。这是来自IndexedDbRepository的戳记派上用场的地方(对于某些这段代码来说似乎有些多余)。
  • 即使在一个地方,通过导入连接存储库也是错误的。有必要考虑使用上下文。
  • , IndexedDB .
  • : . . IndexDB «» , — .
  • , IndexDB Redux Storage. IndexDB , .



Online-

Source: https://habr.com/ru/post/zh-CN472246/


All Articles