フロントエンドは多くの場合、データを保存する必要がありますが、ここでの保存とは長期保存を指します。以前のアイデアは、データを Cookie に保存するか、キーを Cookie に保存して、その他のデータをサーバーに保存するというものでした。こういったシーンでも使い道が多く、大人っぽくて使いやすいです。しかし、データベースや Web SQL と同様に、ローカルに長期間保存できるデータが必要です。
新世代のブラウザは基本的にローカル データベースをサポートしています。必要なときに直接使用してください。それが機能しない場合でも、ストレージを使用して対応できます。
データベースのような機能が必要で、互換性のある保存方法がない場合はどうすればよいですか?これらのものをローカルに暗号化して保存する必要がありますか?たくさんのものを保存したいですか?
現在、ReactNative を使用しているときにこの状況に遭遇しました。大量のデータをローカルに保存する必要があります。 SQLite を直接使用したほうが良いのではないか、と誰かが言いました。
わかりました、全然大丈夫です。ここではフロントエンドの姿勢で開発しているだけです。 SQLite は必要ないが、大量のデータを保存する簡単な方法だけが必要な学生がいる場合はどうすればよいでしょうか?
多くの使用シナリオがあり、実装の最下層を自由に置き換えることができ、実装メソッドさえも自由に記述することができます。ここでは、世界を創造するフロントエンドの姿勢に基づいて、非公式でフロントエンドに優しいデータ リポジトリを構築しています。
ここでの使用シナリオは ReactNative なので、RN の AsyncStorage を使用します。
保存するすべてのデータをオブジェクトと文字列に変換します。ここでの中心となるアイデアはシリアル化です。すべてのデータを文字列として保存します。
import { AsyncStorage } from 'react-native'; exports.setItem = async (key, value) => { let item = JSON.stringify({ v: value }); return await AsyncStorage.setItem(key, item); }
読み取り時には、文字列を元に保存されているデータに変換する変換も行う必要があります。
exports.getItem = async (key) => { let item = await AsyncStorage.getItem(key); if (!item) { return null; } return JSON.parse(item).v || null; }
特別な処理が必要なのはリストの取得です。 RN には、複数のキーに基づいて複数のデータを返す API があります。配列オブジェクトを返します。配列番号 0 はデータ ストレージのキー値、シーケンス番号 1 はデータ ストレージの特定の文字列です。
rreee他に使用されているメソッドもいくつか取り上げてみましょう。ここにもう 1 つのレイヤーをネストし、上と同じ構成を維持します。
exports.getlist = async (keys) => { let list = await AsyncStorage.multiGet(keys); list = list.map(item => JSON.parse(item[1]).v || null); return list; }
上記は単純な実装です。特別な要件がなければ、ほぼ同じです。ただし、さらに進化したい場合は、最適化を開始することを検討できます。
たとえば、JSON 変換の速度を最適化します。 JSON オブジェクト メソッドを使用して変換する場合、実際には数値型を決定するプロセスが存在します。事前にデータ型を定義した場合。変換時に改めて判定する必要はありません。
モデルオブジェクトを定義し、このテーブルに必要なフィールドを事前定義できます。 Sequelize がどのように定義されているかを確認できます。リレーショナル行データベースの方法に従ってこれを行うのは非常に簡単です。
exports.removeItem = async (key) => await AsyncStorage.removeItem(key); exports.removeItems = async (keys) => await AsyncStorage.multiRemove(keys); exports.allKeys = async () => await AsyncStorage.getAllKeys();
ここでリレーショナルデータベースの実装を参照しましょう。
まず、テーブルとデータベースを分割する必要があります。こうすることで、データを保存するときにこの情報にあまり注意を払わず、データの操作に集中できます。
//用户对象const users = db.define('t_users', { id: { type: Sequelize.INTEGER, primaryKey: true, }, //用户名 username: { type: Sequelize.STRING }, //密码 pwd: { type: Sequelize.STRING }, //状态 status: { type: Sequelize.INTEGER }, //昵称 nickname: { type: Sequelize.STRING }, //token token: { type: Sequelize.STRING }, create_time: { type: Sequelize.TIME } }, { freezeTableName: true, timestamps: false, });
オブジェクトを作成するときに、異なるライブラリとテーブルに基づいて異なる操作メソッドを作成できます。ここではクラスが使用されており、各テーブルは個別のオブジェクトに対応します。
RNが提供する保存方法を使用しているため、ここでの追加と更新は実際には同じ方法です。追加すると、現在のタイムスタンプに基づいて一意の ID が作成され、この ID がデータベースに保存するためのキーとして使用されます。したがって、使用する際に ID を別途保存する必要はありませんが、必要な ID と異なると思われる場合は、独自に ID を定義してキーの値を保存することもできます。
constructor(tableName = "table", db = "db") { //检查库,表是否存在 //初始化索引表 this.db = db; this.tableName = tableName; this.tableKey = db + "_" + tableName; this.init(); }
では取得時にIDを元に取得する方法を別途用意しております。ここで考慮されるのは、ID によって取得するのは非常に簡単で便利であり、一部のデータはすぐに読み取ることができ、行ごとにクエリを実行する必要がないということです。
//添加和更新 async add(data = {}) { if (data.constructor !== Object) return; if (!data._id)data._id = uuid(); await setItem(this.tableKey + "_" + data._id, data); return data; }
ID に基づくクエリと比較すると、ファジー クエリは確かに非常に遅いため、本当に必要ない場合は、このファジー クエリを使用しない方がよいでしょう。ここではカスタム クエリ メソッドが提供されており、返されたオブジェクトに基づいてこのデータ行が必要かどうかを判断できます。最上位パラメータを追加して、返される数を制限することもできます。このパラメーターを使用すると、データが大量にある場合のパフォーマンスも向上します。
/** * 通过id查询 * @param {*} id */async getById(id) { if (!id) return {}; return await getItem(this.tableKey + "_" + id); }
最後に削除メソッドとクリアメソッドを追加して、簡易削除ライブラリが完成します。
/** * 通过过滤方法查询 * @param {*} fn */async get(fn, top = 0) { let keys = await allKeys(); if (keys.length == 0) return []; if (top > 0 && keys.length > top) keys.length = top; const listkey = keys.filter(item => item.indexOf(this.tableKey + "_") === 0); if (listkey.length == 0) return []; let list = await getlist(listkey); list = list.filter(item => fn(item)); return list; }
それを使用するときは、オブジェクトを作成して、必要な場所でそれを呼び出すだけです。シンプルで使いやすく、最適化後はクライアントサイド Redis としても使用できます。
/** * 删除 * @param {*} id */async delete(id) { if (!id) return {}; await removeItem(this.tableKey + "_" + id); }/** * 清空表 */async clear() { let keys = await allKeys(); const listkey = keys.filter(item => item.indexOf(this.tableKey + "_") === 0); if (listkey.length == 0) return; removeItems(listkey); }
最初に最適化するのはオブジェクトの作成です。実際、各オブジェクトの作成には多額の費用がかかりますが、この消費を削減できれば素晴らしいと思いませんか。
ここでは、データベース プールの概念を学び、オブジェクト プール メソッドを実装します。オブジェクトの作成後に直接戻ることはなく、プール操作を経る必要があります。
オブジェクトをプールに入れ、ページが破棄されたときに空のオブジェクトにリセットします。次回リクエストするときに新しいものを作成する必要はありません。テーブル名やライブラリ名を直接指定して使用できます。思い出は変わらず、ちょっと笑ってしまいます。
クエリのたびに Stroage を読み取るのは依然として非常に面倒です。特にこの操作は非同期操作であり、ネイティブ側にメッセージを送信する必要があります。
最後に読み取ったデータを変数に保存できます。次回このデータ行を使用する必要がある場合、再度読み取る必要はありません。これにより、読み取り速度を簡単に向上させることができます。
このメソッドは引き続き最適化される可能性があります。変数に保存されるデータの量を制限して、量が APP の制限を超えないようにします。保存期限を論理的に判断して、頻繁に使用するデータを保存し、あまり使用しないデータは削除する機会を見つけることもできます。
この方法を使用すると、変数内のデータの効率を最適化し、変数によって占有されるメモリ サイズを削減することもできます。ただし、実装方法ではタイマーを使用しないようにし、トリガー タイプの使用を検討してください。条件が満たされると、削除アクションがトリガーされます。
上で述べたように、読み取り速度を上げるには読み取り時に変数を入れる必要があります。ところで、書き込み速度も向上するのではないかと考えました。
保存するデータを一時変数に入れます。設定した時間またはデータ長がチェックされ、設定された量がチェックされると、保存操作がトリガーされます。
ここで、データの損失を防ぐために、データの保存に使用される変数と保存時に使用される変数は同じ意味で使用される必要があることに注意してください。
例: 保存時に変数 1 を使用します。データベースに書き込む前に、保存するオブジェクトを変数 2 に変更し、変数 1 のデータを読み取ってデータベースに保存します。これはダブルバッファ書き込みです。
もちろん、APP の終了イベントを決定する必要があります。APP が終了する場合は、必ず内容を変数に一度保存してください。そうしないと、苦労した作業がすべて失われます。
さて、簡単なデータベースが完成しました。これを使用したい場合は、まず npm でこのライブラリを検索します。実装の最初の部分は npm に置き、その後の最適化は完全にオープンソース化されます。 react-native-jsdb
import { AsyncStorage } from 'react-native'; exports.setItem = async (key, value) => { let item = JSON.stringify({ v: value }); return await AsyncStorage.setItem(key, item); }
exports.getItem = async (key) => { let item = await AsyncStorage.getItem(key); if (!item) { return null; } return JSON.parse(item).v || null; }
rreee
他に使用されているメソッドもいくつか取り上げてみましょう。ここにもう 1 つのレイヤーをネストし、上と同じ構成を維持します。exports.getlist = async (keys) => { let list = await AsyncStorage.multiGet(keys); list = list.map(item => JSON.parse(item[1]).v || null); return list; }
exports.removeItem = async (key) => await AsyncStorage.removeItem(key); exports.removeItems = async (keys) => await AsyncStorage.multiRemove(keys); exports.allKeys = async () => await AsyncStorage.getAllKeys();
constructor(tableName = "table", db = "db") { //检查库,表是否存在 //初始化索引表 this.db = db; this.tableName = tableName; this.tableKey = db + "_" + tableName; this.init(); }
将它们分开存储在当前对象内部,在创建对象的时候就可以根据不同的库、表创建不同的操作方法。这里使用的是class,每个表都对应一个单独的对象。
由于我们使用的是RN提供的存储方法,所以这里的添加和更新其实是一个方法。在添加的时候会根据当前时间戳创建一个唯一id,使用这个id作为key存储在数据库中。所以在使用的时候不需要再单独存入id,不过如果你觉得这个id跟你需要的有差别也可以自己定义一个id来作为key值存储。
//添加和更新 async add(data = {}) { if (data.constructor !== Object) return; if (!data._id)data._id = uuid(); await setItem(this.tableKey + "_" + data._id, data); return data; }
在获取的时候单独提供了一个根据id获取的方式。这里考虑的是通过id获取非常的简单方便,对于某些数据完全可以快速读取,没必要一行一行的去查询。
/** * 通过id查询 * @param {*} id */async getById(id) { if (!id) return {}; return await getItem(this.tableKey + "_" + id); }
相对于根据id查询来说,模糊查询确实很慢,如果不是真实需要,还是不要使用这种模糊查询的好。这里提供了一个自定义查询的方法,可以根据返回的对象判断是否需要这行数据。同时也可以添加top参数来限制返回的数量。使用这个参数也可以在数据很多的时候提高性能。
/** * 通过过滤方法查询 * @param {*} fn */async get(fn, top = 0) { let keys = await allKeys(); if (keys.length == 0) return []; if (top > 0 && keys.length > top) keys.length = top; const listkey = keys.filter(item => item.indexOf(this.tableKey + "_") === 0); if (listkey.length == 0) return []; let list = await getlist(listkey); list = list.filter(item => fn(item)); return list; }
最后把删除和清空的方法加上,这样一个简单的删除库就完成了。
/** * 删除 * @param {*} id */async delete(id) { if (!id) return {}; await removeItem(this.tableKey + "_" + id); }/** * 清空表 */async clear() { let keys = await allKeys(); const listkey = keys.filter(item => item.indexOf(this.tableKey + "_") === 0); if (listkey.length == 0) return; removeItems(listkey); }
使用的时候只需要创建对象,然后在需要的地方调用即可。使用起来简单又方便,再加上优化之后的情况甚至可以当成客户端的redis来使用。
//初始化数据库let db=new JSDB();//添加一个自定义数据db.add({name:"test",key:"abc"});//根据id获取数据db.getById("1223467890");//根据条件查询数据db.get(d=>d.name==="test");//删除对应的数据db.delete("1223467890");//情况所有数据db.clear()
首先要优化的就是对象的创建。每个对象创建其实都是一个很大的消耗,如果能把这个消耗降低岂不是美滋滋!
这里我们借鉴数据库池的概念,实现一个对象池的方法。在对象创建之后并没有直接返回,要在经过池的操作。
将对象放入池内,并在页面销毁的时候重置为一个空对象。下次请求创建的时候就不必再创建新的了。直接赋值表、库的名称就可以使用了。内存毫无变化,并且有点想笑。
每次查询都需要去读Stroage还是很麻烦的,尤其这个操作是异步操作,是需要发消息到native端的。
我们可以将上次读取的数据先存在一个变量中,如果下次还需要使用这行数据,就完全不需要再去读取了。这样就可以很简单的提供读取速度。
这个方式还可以继续优化。将变量中保存的数据限制数量,防止数量太多超过了APP的限制。还可以将这个保存的时限做一个逻辑判断,常使用的数据放在里面,不常用的就找机会删除。
使用这种方式也可以优化变量中数据的有效性,减少变量占用内存的大小。不过实现的方式尽量不要使用定时器的形式,可以考虑使用触发式的。在条件满足的时候再触发删除动作。
上面提到读取的时候需要放入变量来提高读取速度。我们顺便想到写入的速度是不是也可以提高啊?
我们将要存的数据放在临时的变量里,如果查过我们设置的时间或者数据长度查过我们设置的数量就触发一次保存操作。
这里要注意,保存数据的变量和存入时候使用的变量要交替使用,防止出现丢数据的情况。
比如:存的时候使用变量1,在写到数据库之前,将要存的对象改成变量2,然后读取变量1的数据并存入数据库中。这就是双缓存写入。
当然还是要判断一次APP的退出事件的,如果APP退出了,请一定要把变量中的内容保存一次,不然你的心血就全丢了。
好了,一个简单的数据库就完成了。想要使用的可以先在npm上搜索react-native-jsdb
这个库。我已经将第一部分实现放在了npm上,后续的优化也会满满的开源出来的。
以上がjs開発データベースの共有例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。