Command Palette

Search for a command to run...

Lưu trữ dữ liệu cục bộ React Native: Cách dùng AsyncStorage & SecureStore

Lưu trữ dữ liệu cục bộ React Native

Ở bài trước, ứng dụng của chúng ta đã biết cách "nói chuyện" với Server thông qua API để lấy dữ liệu. Tuy nhiên, có một vấn đề lớn nảy sinh:

Mỗi khi người dùng tắt app và mở lại, mọi thứ đều quay về con số không! Người dùng phải đăng nhập lại từ đầu, ứng dụng quên mất họ đang dùng chế độ Nền tối (Dark Mode) hay Nền sáng, và những bài viết họ đã đọc cũng không được lưu lại.

Nếu API là cánh cửa kết nối với thế giới bên ngoài, thì Local Storage (Lưu trữ cục bộ) chính là "cuốn sổ tay" bí mật giấu bên trong chiếc điện thoại. Trong bài học này, chúng ta sẽ học cách ghi chép vào cuốn sổ tay đó để ứng dụng của bạn có một "trí nhớ" tuyệt vời!

1. AsyncStorage: Tiêu chuẩn "vàng" của React Native

Nếu bạn đã từng làm Web, bạn chắc chắn biết đến localStorage. Trong thế giới React Native, người anh em họ hàng của nó mang tên AsyncStorage.

AsyncStorage React Native

AsyncStorage là một hệ thống lưu trữ dữ liệu dưới dạng Key - Value. Mọi dữ liệu bạn lưu vào đây đều được chuyển thành chuỗi văn bản (String) và sẽ tồn tại vĩnh viễn trên điện thoại cho đến khi người dùng xóa app hoặc tự tay xóa dữ liệu cài đặt.

Cài đặt AsyncStorage trong Expo

Mở Terminal của dự án và chạy dòng lệnh sau:

npx expo install @react-native-async-storage/async-storage

Cách thao tác với AsyncStorage

Vì hệ thống lưu trữ trên điện thoại cần thời gian để đọc/ghi vào ổ cứng, mọi thao tác với AsyncStorage đều là bất đồng bộ (Asynchronous). Do đó, bạn luôn phải dùng async / await khi làm việc với nó.

Hãy xem cách ứng dụng lưu lại tên hiển thị của người dùng:

import AsyncStorage from '@react-native-async-storage/async-storage'

// 1. LƯU DỮ LIỆU (Cất vào tủ)
const saveUsername = async (name) => {
  try {
    await AsyncStorage.setItem('@username_key', name)
    console.log('Đã lưu tên thành công!')
  } catch (e) {
    console.error('Lỗi khi lưu dữ liệu:', e)
  }
}

// 2. LẤY DỮ LIỆU (Mở tủ lấy ra)
const getUsername = async () => {
  try {
    const value = await AsyncStorage.getItem('@username_key')
    if (value !== null) {
      console.log('Xin chào mừng trở lại:', value)
    }
  } catch (e) {
    console.error('Lỗi khi đọc dữ liệu:', e)
  }
}

// 3. XÓA DỮ LIỆU (Đăng xuất)
const removeUsername = async () => {
  try {
    await AsyncStorage.removeItem('@username_key')
    console.log('Đã xóa dữ liệu!')
  } catch (e) {
    console.error('Lỗi khi xóa:', e)
  }
}

Lưu ý: AsyncStorage CHỈ lưu được chuỗi (String). Nếu bạn muốn lưu một Object chứa thông tin user (ví dụ: { id: 1, role: 'admin' }), bạn bắt buộc phải dùng JSON.stringify() trước khi lưu, và JSON.parse() sau khi lấy ra.

2. Giữ an toàn tuyệt đối với Expo SecureStore

AsyncStorage rất tuyệt vời để lưu cấu hình giao diện (Theme, Ngôn ngữ) hoặc các dữ liệu không quan trọng.

Nhưng CẢNH BÁO: Bạn tuyệt đối KHÔNG ĐƯỢC dùng AsyncStorage để lưu trữ mật khẩu, thông tin thẻ tín dụng, hay Token đăng nhập (JWT)! Dữ liệu trong AsyncStorage là dạng văn bản thuần (plain-text), các hacker có thể dễ dàng cắm cáp vào điện thoại và trích xuất toàn bộ dữ liệu này.

Đối với những thông tin nhạy cảm, hệ sinh thái Expo cung cấp cho chúng ta một "két sắt bọc thép" mang tên SecureStore. Nó sẽ mã hóa dữ liệu của bạn bằng các thuật toán bảo mật cấp cao nhất của hệ điều hành (Keychain trên iOS và Keystore trên Android).

Expo SecureStore

Cài đặt:

npx expo install expo-secure-store

Cách sử dụng (Gần giống hệt AsyncStorage nhưng an toàn hơn 100 lần):

import * as SecureStore from 'expo-secure-store'

// Lưu Token đăng nhập một cách bảo mật
async function saveToken(token) {
  await SecureStore.setItemAsync('user_token', token)
}

// Lấy Token ra để gắn vào API Request
async function getToken() {
  let result = await SecureStore.getItemAsync('user_token')
  if (result) {
    console.log('Mật khẩu/Token của bạn đã được bảo vệ an toàn!')
  }
}

3. MMKV: Con "quái vật" về hiệu năng (Dành cho dự án lớn)

Khi ứng dụng của bạn lớn lên, bạn có thể nhận ra AsyncStorage hoạt động hơi chậm khi phải lưu/đọc một danh sách hàng ngàn sản phẩm đã xem.

Cộng đồng React Native hiện đang phát cuồng vì một giải pháp lưu trữ mới mang tên react-native-mmkv (do Tencent phát triển).

MMKV React Native

Tại sao MMKV lại được săn đón?

  1. Nhanh gấp 30 lần so với AsyncStorage.
  2. Nó hoạt động Đồng bộ (Synchronous)! Tức là bạn không cần phải viết chữ async/await mệt mỏi nữa. Giao diện sẽ hiển thị dữ liệu ngay lập tức mà không có độ trễ (Loading).

Lưu ý cho người dùng Expo: Để sử dụng MMKV, bạn không thể dùng app Expo Go thông thường mà phải tạo bản build phát triển (Development Build) vì nó chứa mã nguồn Native C++ can thiệp sâu vào bộ nhớ. Đây là một kiến thức nâng cao mà chúng ta sẽ tìm hiểu ở những giai đoạn cuối của series!

4. Thực hành: Chức năng Onboarding

Hãy áp dụng ngay AsyncStorage vào một kịch bản rất thực tế: Khi người dùng vừa tải app về, bạn muốn hiện màn hình Hướng dẫn (Onboarding). Nhưng từ lần mở app thứ 2 trở đi, bạn muốn họ vào thẳng Trang Chủ.

Hãy sử dụng Hook useEffect kết hợp với AsyncStorage:

import React, { useEffect, useState } from 'react'
import { View, Text, ActivityIndicator } from 'react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useRouter } from 'expo-router'

export default function RootApp() {
  const [isFirstLaunch, setIsFirstLaunch] = useState(null)
  const router = useRouter()

  useEffect(() => {
    async function checkFirstLaunch() {
      // Đọc xem đã có cờ (flag) 'alreadyLaunched' chưa
      const hasLaunched = await AsyncStorage.getItem('alreadyLaunched')

      if (hasLaunched === null) {
        // Chưa có -> Mở app lần đầu
        await AsyncStorage.setItem('alreadyLaunched', 'true')
        setIsFirstLaunch(true)
      } else {
        // Đã có -> Không phải lần đầu
        setIsFirstLaunch(false)
      }
    }
    checkFirstLaunch()
  }, [])

  // Đang kiểm tra dữ liệu trong ổ cứng, hiện vòng xoay
  if (isFirstLaunch === null) {
    return <ActivityIndicator size="large" style={{ flex: 1 }} />
  }

  // Điều hướng dựa trên kết quả
  if (isFirstLaunch) {
    // Nếu là lần đầu, router.replace() tới màn hình Hướng Dẫn
    return <Text>Chào mừng bạn mới! Đang chuyển sang trang Hướng Dẫn...</Text>
  } else {
    // Không phải lần đầu, vào thẳng Trang Chủ
    return <Text>Chào mừng trở lại! Đang vào Trang Chủ...</Text>
  }
}

Kết luận: Dữ liệu cục bộ cũng rất quan trọng

Sổ tay ghi nhớ trong React Native có 3 ngăn rõ ràng:

  1. AsyncStorage: Dùng cho dữ liệu thông thường, cấu hình app. (Dễ cài, dùng nhiều nhất).
  2. SecureStore: Két sắt bảo mật. Chỉ dùng cho Mật khẩu, JWT Token, dữ liệu thanh toán.
  3. MMKV: Động cơ phản lực. Dùng khi bạn cần tốc độ truy xuất dữ liệu khổng lồ trong chớp mắt.

Bây giờ, ứng dụng của bạn đã có khả năng ghi nhớ và gọi API. Nhưng nếu bạn lấy dữ liệu User ở trang Profile, và lại muốn dùng dữ liệu đó ở trang Settings thì sao? Việc truyền dữ liệu qua lại giữa hàng chục màn hình sẽ tạo ra một mớ bòng bong cực kỳ rắc rối (gọi là Prop Drilling).

Đã đến lúc chúng ta tìm hiểu tới giải pháp quản lý state tập trung, hẹn gặp lại bạn ở bài viết tiếp theo!

Bài viết liên quan

Animation trong React Native: Hướng dẫn toàn tập Reanimated 3

Hướng dẫn cách khắc phục lỗi giật lag UI bằng React Native Reanimated 3. Khám phá bí quyết tạo animation mượt mà 60fps với useSharedValue và withSpring.

Cách gọi API trong React Native: Hướng dẫn Fetch Data và xử lý JSON

Hướng dẫn chi tiết cách gọi API trong React Native. Nắm vững kỹ thuật sử dụng fetch, axios kết hợp với useEffect, useState để tải và hiển thị dữ liệu server mượt mà.

Tối ưu việc gọi API trong React Native bằng React Query

Hướng dẫn tối ưu hóa luồng gọi API trong React Native với TanStack Query. Khám phá cách sử dụng useQuery để quản lý Server State, tạo bộ nhớ đệm (cache) tự động siêu mượt.

Event Handling trong React Native: Sự khác biệt giữa Pressable và TouchableOpacity

Hướng dẫn cách bắt sự kiện chạm, nhấn trong React Native. Tìm hiểu vì sao không nên dùng thẻ Button và cách thay thế bằng Pressable, TouchableOpacity.