はじめに
今日も、日々それっぽく仕上がっていくTodoアプリをいじっていきます。今回は、データを永続化してみたいと思います。
ディ○ゴスティーニ式ですね。
追いつけないほど新しい技術が次々出てくるプログラミングほど楽しいものは無いと思います。マニアックでヲタクな世界を敬遠せず、みんなでコーディングが楽しめたら良いですね〜
マニアックといえば、BrainF*chですね。
リンクを踏むのもためらうようなフザけた名前ですが、真面目に取り組んでみるととても奥が深いです。頭の体操にどうぞ。
データベースを何にするか?
データを永続化するための仕組みはたくさんあります。数えればキリがないです。
今回は、ローカルへの保存で十分かつユーザ管理をしない、とってもこじんまりとしたアプリです。ですので、シンプルにSQLiteやRealmあたりでもと思ったのですが、いいものを見つけました。
NeDBです。
公式サイト曰く、
Embedded persistent or in memory database for Node.js, nw.js, Electron and browsers, 100% JavaScript, no binary dependency
https://github.com/louischatriot/nedb
100% JavaScript
とは、なんともElectron
アプリと親和性が良さそうではないですか!
採用!😉
NeDBを使う下準備
NeDBの導入
まずはなにはともあれyarn。
yarn add nedb
ローカルシステムのファイルを読み書きできるようにする
この件はローカルファイルの読み書き・electron-vueを参考にしました。
Electronからは自由にローカルシステム内のファイルを読み書きできるようです。でも、レンダリングを担うChromiunの制限によりやりたい放題はできないようです。
そりゃそうか。
この制限をクリアするために、app.getPath(name)
でDBのパスを明示してやります。
import Datastore from 'nedb'
import path from 'path'
import { remote } from 'electron'
export default new Datastore({
autoload: true,
filename: path.join(remote.app.getPath('userData'), '/data.db')
})
import
などはsrc/renderer/main.js
に入れておまとめしまえばいいやんと思いました。
でも、あえて分けることでソースの見通しが良くなるだけでなく、別のDBを使うときに(同名にしておけば)ファイルの差し替えで済みますね。
勘違いしている気がするけど、なんとなく納得。
そして、これをsrc/renderer/main.js
で読み込んでやります。
import db from './datastore'
Vue.prototype.$db = db
2行目がミソのようです。
これにより、全てのコンポーネントファイルからthis.$db
でDBの読み書きができるようになります!!
これまで配列に出し入れしていた処理を書き換えていこう
タスクの保存
src/renderer/components/ToDo.vue
に戻ってきました。
NeDBを用いてデータを書き込むときはinsert
メソッドを使います。JSONデータをそのまま扱えるので、配列に保存する処理とよく似た形で書くことができます。大きく変わったところといえば、コールバックがひっついたくらいですかね。
コールバックの引数の1つ目にはエラーが入っています。問題なく書き込みができたときはnull
ですが、なにかしら問題が有ったときは何か入るので見てください。
2つ目は、insertされたデータが入っています。正確にはinsertに成功したデータですかね。
ちなみに、NeDBにデータをinsertすると、一意の値が割り当てられた_id
フィールドが付加されます。すごく気が利いたことをして頂いて恐縮です。
insertTask
を書き換えます。
insertTask () {
this.$db.insert({task: this.input, isComplete: false}, (error, newDoc) => {
if (error !== null) {
}
})
}
ね、簡単でしょ😏
タスクの読み込み
新しいメソッドfetchTask
を付け加えます。NeDBを用いてデータを読み込むfind
メソッドは、先に出てきたinsert
メソッドとよく似た形をしています。
1つ目の引数には検索条件を入れます。
今回はDB内のデータ全件を取得するため{}
としています。何か条件を与えるときは、JSON形式でKeyとValueを与えます。例えば{ name: Tama }
とすると、name
フィールドがTama
のデータが取り出せます。
検索条件はいろいろあるので調べてみてください。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Farticle-ogp-background-9f5428127621718a910c8b63951390ad.png?ixlib=rb-4.0.0&w=1200&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTkxNiZ0eHQ9TmVEQiUyMCVFMyU4MiU5MiVFNCVCRCVCRiVFMyU4MSVBMyVFMyU4MSVBNiVFMyU4MSVCRiVFMyU4MSU5RiZ0eHQtY29sb3I9JTIzMjEyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTU2JnR4dC1jbGlwPWVsbGlwc2lzJnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnM9NWIwZWZmMWRkODg3M2RjNzRjNTNiNGE5MmU4YTMzYzU&mark-x=142&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTYxNiZ0eHQ9JTQwdGlueW1vdXNlJnR4dC1jb2xvcj0lMjMyMTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9MzYmdHh0LWFsaWduPWxlZnQlMkN0b3Amcz04ZWJiYmU2YzU0NTlmNjczOWYzOTM1YzJmYjkzZWFlNA&blend-x=142&blend-y=491&blend-mode=normal&s=50e1080603e9c501f2211ec437aed729)
コールバックの引数1つ目はおなじみですね。エラー情報が入っています。
肝心なのは2つ目のdocs
です。DBを検索しえられたデータが入っています。
言わずもがなJSON形式。条件を与えたときに何も得られなかったときは空が返ってきます。
この返ってきたデータをthis.taskList
に放り込んでやりましょう。
fetchTask () {
this.$db.find({}, (error, docs) => {
if (error !== null) {
}
this.taskList = docs
})
}
タスクの読み込み
ここまでで、単純に保存することと、単純に全件取得することができました。
今度は保存したあとに読み込み処理を加えてやりましょう。先程のinsertTask
メソッドの末尾でDBの全件読み込みを行います。
insertTask () {
this.$db.insert({task: this.input, isComplete: false}, (error, newDoc) => {
if (error !== null) {
}
this.fetchTask()
})
}
いざ実行!
![入力したタスクを追加して表示しています。裏ではDBにデータを保存しています。](https://s-cape.dev/wp-content/uploads/2020/04/86c279e1092b4aeb3fc9bdfb614e66d5.png)
うまくいきました!
タスクの初期表示
読み書きができました。めでたい!
……なのですが、アプリを再度起動してみると、登録したはずのタスクが表示されていません。
それもそのはず。
どこにもアプリ起動時にDBを読み込む処理が書かれていないからです。他の言語と同様、プログラムが気を利かせて「読み込んでおきましたよ」とはしてくれません。アプリ起動時にDBを読み込むようにしましょう。
ここで重要になるのが、アプリのライフサイクルです。
勉強しました。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Farticle-ogp-background-9f5428127621718a910c8b63951390ad.png?ixlib=rb-4.0.0&w=1200&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTkxNiZ0eHQ9VnVlJUUzJTgxJUFFJUUzJTgzJUE5JUUzJTgyJUE0JUUzJTgzJTk1JUUzJTgyJUI1JUUzJTgyJUE0JUUzJTgyJUFGJUUzJTgzJUFCJUUzJTgyJTkyJUU1JUFFJThDJUU1JTg1JUE4JUUzJTgxJUFCJUU3JTkwJTg2JUU4JUE3JUEzJUUzJTgxJTk3JUUzJTgxJTlGJnR4dC1jb2xvcj0lMjMyMTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9NTYmdHh0LWNsaXA9ZWxsaXBzaXMmdHh0LWFsaWduPWxlZnQlMkN0b3Amcz03MDAzOTRhM2JhMzU4YTkwY2Y3ZjVjYjA2YjQ2OTczMg&mark-x=142&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTYxNiZ0eHQ9JTQwY2hhbl9rYWt1JnR4dC1jb2xvcj0lMjMyMTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9MzYmdHh0LWFsaWduPWxlZnQlMkN0b3Amcz00NmZjMjQ2YjNmMTI2NjQxZjdjYWIyOTcyMDA2ZDk1ZQ&blend-x=142&blend-y=491&blend-mode=normal&s=1809dd4da1e5ed8cbb77d80e86d853c6)
「必要なことを必要なときに」がモットーなので、今必要なところだけを調べます。
View関連(element
)の呼び出しと生成処理が行われる前に、DBを読み込んでデータを用意しておかないといけません。また、コンポーネント自体のインスタンスが生成されてDBから読み込んだデータを保持する変数が存在しないといけません。
よって、インスタンスの生成が終わった後に実行されるcreated
で読み込めば良いことになりますね。
サクッと行きましょう。
data () {
return {
taskList: [],
input: '',
}
},
created () {
this.$db.find({}, (error, docs) => {
if (error !== null) {
}
this.taskList = docs
})
},
methods: {
なお、この段階ではfetchTask
メソッドは呼び出せません。2度同じことを書くのは抵抗がありますが、致し方ないでしょう。
これでアプリ起動時にタスクが表示されるようになりました。
![追加した後アプリを起動し直すとちゃんと以前追加したタスク表示されています。](https://s-cape.dev/wp-content/uploads/2020/04/83a09cdc6ab594eb7e7a14f3cdab2cec.png)
今日はここまで!
ところで、ここまで来てふと気が付きました。データが増え続けたり間違えて登録したときの削除処理はどうした?
次回はタスクが完了したときの処理と削除処理を考えていきます。
そういえばテストも書いてないや…