React-Native 之 數(shù)據(jù)持久化

前言


  • 因?yàn)?實(shí)戰(zhàn)項(xiàng)目系列 涉及到數(shù)據(jù)持久化,這邊就來補(bǔ)充一下。
  • 如本文有錯(cuò)或理解偏差歡迎聯(lián)系我,會(huì)盡快改正更新!
  • 如有什么問題,也可直接通過郵箱 277511806@qq.com 聯(lián)系我。

數(shù)據(jù)持久化


  • 數(shù)據(jù)持久化一直都是軟件開發(fā)中重要的一個(gè)環(huán)節(jié),幾乎所有的應(yīng)用都具備這一項(xiàng)功能;那什么是數(shù)據(jù)持久化呢?—— 說白了就是數(shù)據(jù)的本地化存儲(chǔ),將數(shù)據(jù)存儲(chǔ)到本地,在需要的時(shí)候進(jìn)行調(diào)用。

  • 這邊我們介紹兩種在 React-Native 中比較常用的存儲(chǔ)方式

    • AsyncStorage:這是官方使用的存儲(chǔ)方式,類似于 iOS 中的 NSUserDefault ,區(qū)別在于,AsyncStorage 只能存儲(chǔ) 字符串鍵值對(duì),而 NSUserDefault 可以存儲(chǔ) 字符串和number。
    • Realm:今天才發(fā)現(xiàn) Realm 也已經(jīng)支持 React-Native ,這是新興的移動(dòng)端數(shù)據(jù)存儲(chǔ)方式,在沒有它之前,一直都是使用 sqlist 進(jìn)行數(shù)據(jù)存儲(chǔ),在性能上,各有優(yōu)勢(shì),但是操作上,Realm 有著明顯優(yōu)勢(shì),更方便使用。
  • 接下來我們就來看看怎么使用它們。

AsyncStorage 簡(jiǎn)單使用


  • AsyncStorage方法官方文檔寫得很詳細(xì),這邊就不對(duì)贅述了!

  • AsyncStorage 使用方法很簡(jiǎn)單,我們就直接上代碼:

    // 增加
    createData() {
        AsyncStorage.setItem('name', JSON.stringify('吉澤明步'), (error, result) => {
            if (!error) {
                this.setState({
                    data:'保存成功!'
                })
            }
        });
    }

    // 查詢
    inquireData() {
        AsyncStorage.getItem('name')
            .then((value) => {
                let jsonValue = JSON.parse((value));

                this.setState({
                    data:jsonValue
                })
            })
    }

    // 更新
    upData() {
        AsyncStorage.setItem('name', JSON.stringify('蒼井空'), (error, result) => {
            if (!error) {
                this.setState({
                    data:'更新成功!'
                })
            }
        });
    }

    // 刪除
    removeData() {
        AsyncStorage.removeItem('name');

        this.setState({
            data:'刪除完成!'
        })
    }
    
AsyncStorage效果演示.gif
  • 按照官方推薦,我們使用 AsyncStorage 前,最好進(jìn)行一層封裝,React-Native中文網(wǎng) 給我們提供了一個(gè)比較好的框架 —— react-native-storage,我們可以直接使用它,方法很簡(jiǎn)單,說明文檔中說得很詳細(xì)。

  • 既然是第三方框架,那么第一部肯定就是導(dǎo)入到我們的工程中:

    npm install react-native-storage --save

  • 接著,我們根據(jù)創(chuàng)建一個(gè) Storage 文件專門對(duì)框架進(jìn)行初始化操作:
    import { 
        AsyncStorage, 
    } from 'react-native';

    // 第三方框架
    import Storage from 'react-native-storage';
    
    var storage = new Storage({
      // 最大容量,默認(rèn)值1000條數(shù)據(jù)循環(huán)存儲(chǔ)
      size: 1000,
    
      // 存儲(chǔ)引擎:對(duì)于RN使用AsyncStorage,對(duì)于web使用window.localStorage
      // 如果不指定則數(shù)據(jù)只會(huì)保存在內(nèi)存中,重啟后即丟失
      storageBackend: AsyncStorage,
        
      // 數(shù)據(jù)過期時(shí)間,默認(rèn)一整天(1000 * 3600 * 24 毫秒),設(shè)為null則永不過期
      defaultExpires: 1000 * 3600 * 24,
        
      // 讀寫時(shí)在內(nèi)存中緩存數(shù)據(jù)。默認(rèn)啟用。
      enableCache: true,
        
      // 如果storage中沒有相應(yīng)數(shù)據(jù),或數(shù)據(jù)已過期,
      // 則會(huì)調(diào)用相應(yīng)的sync方法,無縫返回最新數(shù)據(jù)。
      // sync方法的具體說明會(huì)在后文提到
      // 你可以在構(gòu)造函數(shù)這里就寫好sync的方法
      // 或是寫到另一個(gè)文件里,這里require引入
      // 或是在任何時(shí)候,直接對(duì)storage.sync進(jìn)行賦值修改
      sync: require('./sync')
    })  
    
    // 全局變量
    global.storage = storage;

  • 到這里,我們需要注意的就是要在哪里初始化這個(gè)文件,其實(shí)一個(gè)思路就是 —— 在哪個(gè)地方,我們只需要引用一次文件,就可以在其他文件中使用(比如:我們程序默認(rèn)的進(jìn)口就是 index.ios/android.js 文件,那么只要在他們中引用一次文件即可,這樣就不需要去注意什么調(diào)用順序,因?yàn)?index.ios/android.js 文件肯定是最先調(diào)用的,它們才是真正的王)。

  • 然而,為了方便我們使用同一套代碼,我們會(huì)創(chuàng)建一個(gè) Main 文件作為程序入口的 中轉(zhuǎn)總站 來管理其他的文件,然后外界只要調(diào)用這個(gè) Main 文件,就可以展示里面的所有東西。所以,將引用放到 Main 文件中是最好的選擇。

    
    // 在 main 文件中添加
    import storage from '封裝的文件位置';

  • 到這里,我們就完成了最基礎(chǔ)的配置,我們只需要在需要用到的地方直接使用就可以了,首先我們?cè)谛陆ㄒ粋€(gè)文件,然后從Main文件跳轉(zhuǎn)到這個(gè)文件中。

  • 接著,我們就真正地自己來使用一下這個(gè)框架:

        // 增加
    createData() {
        // 使用key保存數(shù)據(jù)
        storage.save({
            key:'storageTest',    // 注意:請(qǐng)不要在key中使用_下劃線符號(hào)!
            rawData: {
                name:'吉澤明步',
                city:'xx省xxx市'
            },

            // 設(shè)為null,則不過期,這里會(huì)覆蓋初始化的時(shí)效
           expires: 1000 * 3600
        });
    }

    // 查詢
    inquireData() {
        storage.load({
            key:'storageTest',

            // autoSync(默認(rèn)為true)意味著在沒有找到數(shù)據(jù)或數(shù)據(jù)過期時(shí)自動(dòng)調(diào)用相應(yīng)的sync方法
            autoSync: true,

            // syncInBackground(默認(rèn)為true)意味著如果數(shù)據(jù)過期,
            // 在調(diào)用sync方法的同時(shí)先返回已經(jīng)過期的數(shù)據(jù)。
            // 設(shè)置為false的話,則始終強(qiáng)制返回sync方法提供的最新數(shù)據(jù)(當(dāng)然會(huì)需要更多等待時(shí)間)。
            syncInBackground: true,

            // 你還可以給sync方法傳遞額外的參數(shù)
            syncParams: {
                extraFetchOptions: {
                    // 各種參數(shù)
                },
                someFlag: true,
            },
        }).then(ret => {
            // 如果找到數(shù)據(jù),則在then方法中返回
            // 注意:這是異步返回的結(jié)果(不了解異步請(qǐng)自行搜索學(xué)習(xí))
            // 你只能在then這個(gè)方法內(nèi)繼續(xù)處理ret數(shù)據(jù)
            // 而不能在then以外處理
            // 也沒有辦法“變成”同步返回
            // 你也可以使用“看似”同步的async/await語(yǔ)法

            // 更新data值
            this.setState({
                data: ret.name
            });

        }).catch(err => {
            //如果沒有找到數(shù)據(jù)且沒有sync方法,
            //或者有其他異常,則在catch中返回
            console.warn(err.message);
            switch (err.name) {
                case 'NotFoundError':
                    // 更新
                    this.setState({
                        data:'數(shù)據(jù)為空'
                    });
                    
                    break;
                case 'ExpiredError':
                    // TODO
                    break;
            }
        })
    }

    // 更新
    upData() {
        // 重新存儲(chǔ)即可
        storage.save({
            key:'storageTest',    // 注意:請(qǐng)不要在key中使用_下劃線符號(hào)!
            rawData: {
                name:'蒼井空',
                city:'xx省xxx市'
            },

            // 設(shè)為null,則不過期,這里會(huì)覆蓋初始化的時(shí)效
            expires: 1000 * 3600
        });
    }

    // 刪除
    removeData() {
        // 刪除單個(gè)數(shù)據(jù)
        storage.remove({
            key: 'storageTest'
        });

        // storage.remove({
        //     key: 'react-native-storage-test',
        //     name:'吉澤明步'
        // });

//         // !! 清空map,移除所有"key-id"數(shù)據(jù)(但會(huì)保留只有key的數(shù)據(jù))
//         storage.clearMap();
//
//         // 獲取某個(gè)key下的所有id
//         storage.getIdsForKey('user').then(ids => {
//             console.log(ids);
//         });
//
//         // 獲取某個(gè)key下的所有數(shù)據(jù)
//         storage.getAllDataForKey('user').then(users => {
//             console.log(users);
//         });
//
//         // !! 清除某個(gè)key下的所有數(shù)據(jù)
//         storage.clearMapForKey('user');
    }
    
react-native-storage效果演示.gif
  • 很簡(jiǎn)單對(duì)不,那對(duì)于 react-native-storage 的使用就先講到這里。

Realm 配置與常見錯(cuò)誤處理


  • 很驚喜,Realm 也支持了 React-Native ,這樣我們可以在移動(dòng)端 愉快地 進(jìn)行存儲(chǔ)操作了。

  • 而且使用方法 Realm 官方提供的文檔都一如既往地詳細(xì),所以如果感興趣,也可以到 Realm說明文檔 進(jìn)行學(xué)習(xí)(不知是網(wǎng)絡(luò)問題還是官方?jīng)]有整理好,我這邊中文版文檔是打不開的,所以只能看英文版),這邊我們直接將里面常用到的內(nèi)容整理出來,簡(jiǎn)單說下怎么使用。

  • 首先,一樣還是需要打開終端將 Realm 放到我們的工程中

    npm install --save realm

  • 接著,添加 Realm 與 工程的鏈接

    • React-Native >= 0.31.0
        react-native link realm
    
    
    • React-Native < 0.31.0
        rnpm link realm
    
    
配置成功.png
  • 出現(xiàn)上面的提示表示成功,然后我們需要卸載模擬器中已經(jīng)安裝的 APP 并重新安裝(Xcode會(huì)進(jìn)行一系列配置,其中會(huì)在網(wǎng)絡(luò)下載一下必要的組件,時(shí)間視網(wǎng)絡(luò)情況而定),來測(cè)試下安卓和iOS,2端是否能正常使用
Xcode配置.png
  • 如果出現(xiàn)有 err! 等字樣或者在安卓中出現(xiàn)錯(cuò)誤警告,說明安卓端沒有成功地進(jìn)行全部配置,需要我們手動(dòng)進(jìn)行配置,步驟如下:

    • 如果出現(xiàn) android Missing Realm constructor - please ensure RealmReact framework is included 報(bào)錯(cuò):

      • MainApplication 中添加
          new RealmReactPackage()
      
      
    • 如果還是鏈接不上,我們檢查以下幾處代碼是否有自動(dòng)添加

      • settings.gradle 中是否有下面代碼,不存在手動(dòng)添加
          include ':realm'
          project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
      
      
      • 如果還不行,到app => build.gradle 中是否有下面代碼,不存在手動(dòng)添加
          dependencies {
              compile project(':realm') // 是否存在,不存在手動(dòng)添加(再舊版本有效,新版本不需要添加此項(xiàng))
              compile fileTree(dir: "libs", include: ["*.jar"])
              compile "com.android.support:appcompat-v7:23.0.1"
              compile "com.facebook.react:react-native:+"  // From node_modules
          }       
      
      
  • 接著,重新運(yùn)行安卓:

    react-native run-android

  • 如果還是不行,可聯(lián)系官方,或者將錯(cuò)誤代碼發(fā)送給我,也許可以幫忙解決。

Realm 常用操作


  • 作為數(shù)據(jù)庫(kù),使用它無法就是 增刪改查 這老四樣,使用之前,還是老規(guī)矩,初始化表格:
    • name:表格名稱。
    • primaryKey:主鍵,這個(gè)屬性的類型可以是 'int' 和 'string',并且如果設(shè)置主鍵之后,在更新和設(shè)置值的時(shí)候這個(gè)值必須保持唯一性,并且無法修改。
    • properties:這個(gè)屬性內(nèi)放置我們需要的字段。
    // 新建表模型
    const PersonSchema = {
        name: 'Person',
        primaryKey:'id',    // 官方?jīng)]給出自增長(zhǎng)的辦法,而且一般不會(huì)用到主鍵,這也解決了重復(fù)訪問的問題,而且實(shí)際開發(fā)中我們不需要主鍵的,讓服務(wù)端管就是了
        properties: {
            id:'int',
            name: 'string',
            tel_number: {type: 'string', default: '156xxxxxxxx'},   // 添加默認(rèn)值的寫法
            city: 'string' // 直接賦值的方式設(shè)置類型
        }
    };

  • 初始化 Realm:
    // 根據(jù)提供的表初始化 Realm,可同時(shí)往數(shù)組中放入多個(gè)表
    let realm = new Realm({schema: [PersonSchema]});

  • 增加數(shù)據(jù):
    // 增加
    createData() {
        realm.write(() => {
            realm.create('Person', {id:0, name:'吉澤明步', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
            realm.create('Person', {id:1, name:'蒼井空', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
            realm.create('Person', {id:2, name:'小澤瑪利亞', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
            realm.create('Person', {id:3, name:'皮皮蝦我們走', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
            realm.create('Person', {id:4, name:'波多野結(jié)衣', tel_number:'137xxxxxxxx', city:'xx省xx市xxxxxx'});
        })
    }
    
  • 查詢數(shù)據(jù)

    • 查詢所有數(shù)據(jù):
        // 查詢所有數(shù)據(jù)
        let persons = realm.objects('Person');
        console.log ('name:' + persons[0].name + 'city:' + persons[0].city)
    
    
    • 根據(jù)條件查詢數(shù)據(jù)
        // 查詢
        inquireData() {
            let allData;
    
            // 獲取Person對(duì)象
            let Persons = realm.objects('Person');
    
            // 遍歷表中所有數(shù)據(jù)
            for (let i = 0; i<Persons.length; i++) {
                let tempData = '第' + i + '個(gè)' + Persons[i].name + Persons[i].tel_number + Persons[i].city + '\n';
                allData += tempData
            }
    
            this.setState({
                data:allData
            })
        }
    
        // 根據(jù)條件查詢
        filteredData() {
            let allData;
    
            // 獲取Person對(duì)象
            let Persons = realm.objects('Person');
            // 設(shè)置篩選條件
            let person = Persons.filtered('id == 1');
    
            if (person) {
                // 遍歷表中所有數(shù)據(jù)
                for (let i = 0; i<person.length; i++) {
                    let tempData = '第' + (person[i].id + 1) + '個(gè)數(shù)據(jù):' + person[i].name + person[i].tel_number + person[i].city + '\n';
                    allData += tempData
                }
            }
    
            this.setState({
                data:'篩選到的數(shù)據(jù):' + allData
            })
        }
    
    
  • 更新數(shù)據(jù):

    
    // 更新
    upData() {
        realm.write(() => {
            // 方式一
            realm.create('Person', {id: 0, name: '皮皮蝦,我們走', tel_number: '156xxxxxxxx', city: 'xx省xx市xxxxxx'}, true);

            // // 方式二:如果表中沒有主鍵,那么可以通過直接賦值更新對(duì)象
            // // 獲取Person對(duì)象
            // let Persons = realm.objects('Person');
            // // 設(shè)置篩選條件
            // let person = Persons.filtered('name == 蒼井空');
            // // 更新數(shù)據(jù)
            // person.name = '黃鱔門'

        })
    }

  • 刪除數(shù)據(jù):
    // 刪除
    removeData() {
        realm.write(() => {
            // 獲取Person對(duì)象
            let Persons = realm.objects('Person');
            // 刪除
            realm.delete(Persons);
        })
    }

realm效果演示.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容