react-native-mmkv

在使用React Native工作时,你很可能已经使用了 AsyncStorage 作为存储解决方案。例如,你可以使用 AsyncStorage 来存储键值对,比如你的应用程序的当前主题,甚至为了各种原因存储状态和令牌。

除了 AsyncStorage ,我们还可以使用一些第三方存储解决方案。在这篇文章中,我们将研究 react-native-mmkv 库,并探讨为什么你可能想要用它替代 AsyncStorage ,以及如何在我们的应用程序中使用它。

为什么使用 react-native-mmkv ?

由微信开发的 react-native-mmkv 库允许我们高效地从 MMKV 存储框架存储和读取键值对。它的名字是 React Native memory-map key-value 存储的简称。

类似于 AsyncStorage , react-native-mmkv 也具有跨平台兼容性,意味着它适用于 iOS 和 Android 平台。让我们来看看你可能会考虑使用 MMKV 而不是 AsyncStorage 的一些原因。

加密

AsyncStorage 是一个未加密的存储系统。不建议使用像 AsyncStorage 这样的存储解决方案来存储密码、令牌和其他私人信息。

MMKVAsyncStorage 更安全,提供数据加密和其他更高级的安全特性。如果你想存储需要高级别安全保护的敏感数据,MMKV是更好的选择。

完全同步存储

AsyncStorage 是一个异步存储系统,利用 async/await 与promises。相比之下, react-native-mmkv 的所有调用都是完全同步的,因此可以不使用任何promises进行。

我们看一下下面的代码以便更好地理解:

// AsyncStorage
 // storing data const storeUser = async (value) => { try { await AsynStorage.setItem("user", JSON.stringify(value)); } catch (error) { console.log(error); } }; storeUser("Chimezie") // getting data const getUser = async () => { try { const userData = await AsynStorage.getItem("user") const user = JSON.parse(userData) } catch (error) { console.log(error); } };
// AsyncStorage
 // storing data const storeUser = async (value) => { try { await AsynStorage.setItem("user", JSON.stringify(value)); } catch (error) { console.log(error); } }; storeUser("Chimezie") // getting data const getUser = async () => { try { const userData = await AsynStorage.getItem("user") const user = JSON.parse(userData) } catch (error) { console.log(error); } };

如果你看上面的代码,你可以看到我们在存储和检索存储数据时都使用了async/await

使用 react-native-mmkv ,我们可以如下所示调用它,而不是发出Promise 请求:

// react-native-mmkv storage.set('username', 'Chimezie'); const username = storage.getString('username')
// react-native-mmkv storage.set('username', 'Chimezie'); const username = storage.getString('username')

在上面的 react-native-mmkv 示例中,我们正在将我们的用户名设置到我们的存储中。当我们想要检索数据时,我们使用 getString() 方法,因为我们获取的是字符串数据。react-native-mmkv 不像 AsyncStorage 那样返回一个 promise

序列化

如果你观察我们上面的例子,你会注意到我们正在将我们想要存储在 AsyncStorage 中的用户的值转化为字符串。这是因为 AsyncStorage 处理的是字符串值,所以我们在保存之前必须将所有非字符串数据类型序列化。

const storeUser = async () => { 
try { 
const userData = { name: "Chimezie", location: "Nigeria" } const serializedUser = JSON.stringify(userData) await AsynStorage.setItem("user", serializedUser); } catch (error) { console.log(error); } };
const storeUser = async () => { 
try { 
const userData = { name: "Chimezie", location: "Nigeria" } const serializedUser = JSON.stringify(userData) await AsynStorage.setItem("user", serializedUser); } catch (error) { console.log(error); } };

同样地,要使用 AsyncStorage 检索数据,我们必须将数据解析回原始数据类型——在这个例子中是一个对象:

const getUser = async () => { try { const userData = await AsynStorage.getItem("user") const userObject = JSON.parse(userData) } catch (error) { console.log(error); } };
const getUser = async () => { try { const userData = await AsynStorage.getItem("user") const userObject = JSON.parse(userData) } catch (error) { console.log(error); } };

对于MMKV来说,并非如此。在这方面,MMKV更为高效,因为它支持不同的基本类型或数据类型,如布尔值,数字和字符串。简单来说,你不需要在存储之前手动序列化所有值。

storage.set('username', 'Innocent') // string storage.set('age', 25) // number storage.set('is-mmkv-fast-asf', true) // boolean
storage.set('username', 'Innocent') // string storage.set('age', 25) // number storage.set('is-mmkv-fast-asf', true) // boolean

更快的性能

由于 react-native-mmkv 不序列化和解析非字符串数据,所以它比 AsyncStorage 更快。下面的图片来自MMKV团队,展示了从不同存储解决方案中读取数据一千次所需的时间的基准测试结果。MMKV证明比其他所有方案都要快:

此外,由于 MMKV 是完全同步的,它消除了等待 promise 完成以获取数据的压力。这使得读写数据变得更加容易和快速——而且不需要处理任何 promise 或错误逻辑。

react-native-mmkv 的限制

尽管 react-native-mmkv 库有许多优点,但它也有一些限制。在这一部分,我们将讨论在使用MMKV时需要注意的一些事项。

调试

MMKV 利用了JavaScript接口(JSI),它提供了同步的本地访问以提高效率和性能。然而,这对远程调试构成了挑战,因为像 Chrome DevTools 这样的工具使用的是传统的React Native桥接,而不是JSI桥接。

作为这个限制的一个解决方法,你可以使用Flipper调试工具。Flipper是为了在你的应用启用JSI或者你的应用使用像MMKV这样的JSI库时进行调试而设计的。或者,你也可以将你的错误记录到控制台以进行调试。

内存大小

MMKV库对于存储小量数据如用户偏好、主题状态或应用设置非常高效。没有特定的大小测量或限制,但推荐使用MMKV来存储小数据。

然而,由于它提供内存存储,存储大量数据将消耗内存并妨碍应用程序的性能。因此,不建议使用MMKV来存储大量数据。

文档

AsyncStorage 不同, react-native-mmkv 库的文档非常有限。唯一可用的文档是库的GitHub仓库中的 README.md 文件,它解释了如何使用这个库。

使用 react-native-mmkv

我们现在已经看到了一些你可能会考虑使用MMKV而不是 AsyncStorage 的原因,以及它的一些限制。在这个部分,我们将看看如何在我们的应用程序中使用 react-native-mmkv 包来存储和检索键值对数据。

MMKV 在 Expo 中不工作,因此你可以在一个裸 React Native 项目中使用它,或者通过预构建和弹出你的 Expo 应用来使用。要安装该包,请运行以下任一命令:

// npm npm install react-native-mmkv //yarn yarn add react-native-mmkv
// npm npm install react-native-mmkv //yarn yarn add react-native-mmkv

接下来,我们将创建一个实例,然后对其进行初始化。然后我们可以在应用程序的任何地方调用这个实例。

创建一个名为 Storage.js 的文件,并复制下面的代码:

// Storage.js import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-storage`, path: `${USER_DIRECTORY}/storage`, encryptionKey: 'encryptionkey' })
// Storage.js import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-storage`, path: `${USER_DIRECTORY}/storage`, encryptionKey: 'encryptionkey' })

在上述代码中,我们首先导入已安装的包。接下来,我们创建了一个名为 storage 的实例,它可以接受三个选项—— idpathencryptionKey

id 是一个用于区分 MMKV 实例的唯一标识符。你可以创建不同的实例来存储不同种类的数据。 id 有助于区分或分隔它们:

const passwordStorage = new MMKV({ id: `password-storage`, }) const themeStorage = new MMKV({ id: `theme-storage`, })
const passwordStorage = new MMKV({ id: `password-storage`, }) const themeStorage = new MMKV({ id: `theme-storage`, })

path 是你的设备中MMKV存储数据的根文件。它也允许你自定义你喜欢的路径或目录。

encryptionKey 是一个用于在存储前加密数据和在检索后解密数据的唯一密钥。

在我们创建了实例之后,我们可以在任何组件中导入该实例并使用它。

存储数据

正如我们之前看到的,MMKV支持不同的数据类型,这意味着我们可以存储不同的数据类型而无需序列化它们。

首先,让我们导入我们已经安装的包:

//App.js import { storage } from './Storage'
//App.js import { storage } from './Storage'

接下来,我们将使用存储来储存我们的数据:

storage.set('username', 'Innocent') // string storage.set('age', 25) // number storage.set('is-mmkv-fast-asf', true) // boolean
storage.set('username', 'Innocent') // string storage.set('age', 25) // number storage.set('is-mmkv-fast-asf', true) // boolean

就是这样!如你在上述代码注释中所见, react-native-mmkv 支持字符串、数字和布尔数据类型。然而,对于对象和数组,我们必须先序列化数据才能保存。

// objects const user = { name: "Chimezie", location: "Nigeria", email: 'chimezieinnocent39@gmail.com', } storage.set("userDetails", JSON.stringify(user)) // arrays const numberArray = [1, 2, 3, 4, 5]; const serializedArray = JSON.stringify(numberArray); storage.set('numbers', serializedArray);
// objects const user = { name: "Chimezie", location: "Nigeria", email: 'chimezieinnocent39@gmail.com', } storage.set("userDetails", JSON.stringify(user)) // arrays const numberArray = [1, 2, 3, 4, 5]; const serializedArray = JSON.stringify(numberArray); storage.set('numbers', serializedArray);

检索数据

使用MMKV检索数据就像存储它一样简单直接。您使用 getString() 来获取字符串数据,使用 getNumber() 来获取数字数据,使用 getBoolean() 来获取布尔类型的数据:

const username = storage.getString('username') // 'Innocent' const age = storage.getNumber('age') // 25 const isMmkvFastAsf = storage.getBoolean('is-mmkv-fast-asf') // true
const username = storage.getString('username') // 'Innocent' const age = storage.getNumber('age') // 25 const isMmkvFastAsf = storage.getBoolean('is-mmkv-fast-asf') // true

对于对象和数组,我们将使用 getString() ,因为我们在保存之前对其进行了序列化——换句话说,将其存储为字符串数据。然后,我们将使用 JSON.parse() 来反序列化或转换回原始状态或数据类型。

// objects const serializedUser = storage.getString('userDetails'); const userObject = JSON.parse(serializedUser); console.log(userObject); /* output: const user = { name: "Chimezie", location: "Nigeria", email: 'chimezieinnocent39@gmail.com', } */ // arrays const serializedArray = storage.getString('numbers'); const numberArray = JSON.parse(serializedArray); console.log(numberArray); // Output: [1, 2, 3, 4, 5]
// objects const serializedUser = storage.getString('userDetails'); const userObject = JSON.parse(serializedUser); console.log(userObject); /* output: const user = { name: "Chimezie", location: "Nigeria", email: 'chimezieinnocent39@gmail.com', } */ // arrays const serializedArray = storage.getString('numbers'); const numberArray = JSON.parse(serializedArray); console.log(numberArray); // Output: [1, 2, 3, 4, 5]

此外,在你想查看存储中所有键的情况下,MMKV通过提供一个名为 getAllKeys() 的方法允许我们做到这一点。这个方法返回一个包含实例中存储的所有键的数组:

// Set some key-value pairs storage.set('name', 'Innocent'); storage.set('age', 25); const allKeys = storage.getAllKeys(); console.log(allKeys); // Output: ['name', 'age']
// Set some key-value pairs storage.set('name', 'Innocent'); storage.set('age', 25); const allKeys = storage.getAllKeys(); console.log(allKeys); // Output: ['name', 'age']

删除数据

使用MMKV删除数据类似于 AsyncStorage 。我们可以删除特定的键或我们实例中的所有键:

// delete a key storage.delete('username') // delete all keys storage.clearAll()
// delete a key storage.delete('username') // delete all keys storage.clearAll()

加密数据

数据加密是MMKV相较于 AsyncStorage 的另一个优势所在。MMKV提供了在存储数据前加密的选项,而 AsyncStorage 并未提供加密功能。

在加密任何数据之前,我们必须首先在我们的实例中提供一个加密密钥。MMKV使用这个密钥来加密和解密数据:

// Storage.js import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-storage`, encryptionKey: 'EncrypTedKey123' })
// Storage.js import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-storage`, encryptionKey: 'EncrypTedKey123' })

然后,我们可以像这样加密我们想要的任何数据:

// App.js storage.set('userPassword', 'This is a secret user password'); // Retrieving data from the encrypted storage const password = storage.getString('userPassword'); console.log(password); // Output: 'This is a secret user passwor
// App.js storage.set('userPassword', 'This is a secret user password'); // Retrieving data from the encrypted storage const password = storage.getString('userPassword'); console.log(password); // Output: 'This is a secret user passwor

订阅更新

react-native-mmkv 允许我们订阅键值对数据的更新或更改。要订阅更新,我们可以使用 addOnValueChangedListener() 方法注册一个事件监听器。每当指定的键值对数据发生变化时,监听器就会收到通知。

我们看看我们如何能做到这一点:

// App.tsx import React, {useState, useEffect} from 'react'; import {Colors} from 'react-native/Libraries/NewAppScreen'; import { Text, View, Button, StatusBar, StyleSheet, SafeAreaView, } from 'react-native'; import {storage} from './Storage'; function App(): JSX.Element { const [isDarkMode, setIsDarkMode] = useState<boolean>(false); const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; useEffect(() => { const listener = storage.addOnValueChangedListener(changedKey => { if (changedKey === 'isDarkMode') { const newValue = storage.getBoolean(changedKey); console.log('theme:', newValue); } }); return () => { listener.remove(); }; }, []); const toggleTheme = () => { const newMode = !isDarkMode; setIsDarkMode(newMode); storage.set('isDarkMode', newMode); }; return ( <SafeAreaView style={[backgroundStyle, styles.sectionContainer]}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={backgroundStyle.backgroundColor} /> <View> <Text style={[ styles.sectionTitle, { color: isDarkMode ? Colors.white : Colors.black, }, ]}> {isDarkMode ? 'Dark Mode' : 'Light Theme'} </Text> <Text style={[ styles.sectionDescription, { color: isDarkMode ? Colors.light : Colors.dark, }, ]}> React Native MMKV Tutorial </Text> <Button onPress={toggleTheme} title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'} /> </View> </SafeAreaView> ); } const styles = StyleSheet.create({ sectionContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, sectionTitle: { fontSize: 24, fontWeight: '600', textAlign: 'center', }, sectionDescription: { marginVertical: 8, fontSize: 18, fontWeight: '400', textAlign: 'center', }, highlight: { fontWeight: '700', }, }); export default App;
// App.tsx import React, {useState, useEffect} from 'react'; import {Colors} from 'react-native/Libraries/NewAppScreen'; import { Text, View, Button, StatusBar, StyleSheet, SafeAreaView, } from 'react-native'; import {storage} from './Storage'; function App(): JSX.Element { const [isDarkMode, setIsDarkMode] = useState<boolean>(false); const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; useEffect(() => { const listener = storage.addOnValueChangedListener(changedKey => { if (changedKey === 'isDarkMode') { const newValue = storage.getBoolean(changedKey); console.log('theme:', newValue); } }); return () => { listener.remove(); }; }, []); const toggleTheme = () => { const newMode = !isDarkMode; setIsDarkMode(newMode); storage.set('isDarkMode', newMode); }; return ( <SafeAreaView style={[backgroundStyle, styles.sectionContainer]}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={backgroundStyle.backgroundColor} /> <View> <Text style={[ styles.sectionTitle, { color: isDarkMode ? Colors.white : Colors.black, }, ]}> {isDarkMode ? 'Dark Mode' : 'Light Theme'} </Text> <Text style={[ styles.sectionDescription, { color: isDarkMode ? Colors.light : Colors.dark, }, ]}> React Native MMKV Tutorial </Text> <Button onPress={toggleTheme} title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'} /> </View> </SafeAreaView> ); } const styles = StyleSheet.create({ sectionContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, sectionTitle: { fontSize: 24, fontWeight: '600', textAlign: 'center', }, sectionDescription: { marginVertical: 8, fontSize: 18, fontWeight: '400', textAlign: 'center', }, highlight: { fontWeight: '700', }, }); export default App;

在上述代码中,我们使用 addOnValueChangedListener() 方法来注册一个回调函数。该函数监听MMKV存储中特定键的变化。

回调函数将 changedKey 作为参数,使用 storage.getBoolean(changedKey) 获取与该键关联的新值,并将其新值记录到控制台。

每当MMKV中的键值对被修改时,回调函数将会被调用并带有被改变的键的名称,允许你在应用程序中对变化做出反应。

最后,我们在 useEffect 返回函数中取消订阅监听器。这样做是为了避免或防止我们的组件卸载时出现内存泄漏。

总结

在这篇文章中,我们探讨了一些你可能会考虑使用MMKV而非 AsyncStorage 的原因,并考虑了它的一些限制。你可以在这个GitLab仓库中查看我们使用的代码示例。

确实,MMKV比 AsyncStorage 新,也没有像 AsyncStorage 那样广泛的社区。然而,MMKV提供了一个更快、更高效、更安全的存储系统。这个库有超过50个贡献者,并且维护得非常好。