aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa các hook nâng cao trong React Native kết nối với ứng dụng mobile

Các hook nâng cao giúp tổ chức state, tối ưu render và tái sử dụng logic trong React Native.

Các hook nâng cao trong React Native: dùng đúng để code gọn và tối ưu hiệu năng

admin by admin
30/07/2026
in Hướng dẫn, Lập trình Mobile, React Native
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Ở giai đoạn đầu, phần lớn ứng dụng React Native có thể chạy tốt với useState và useEffect. Nhưng khi màn hình có nhiều trạng thái liên quan, danh sách bắt đầu giật, component con render lại quá nhiều hoặc logic bị sao chép ở nhiều nơi, chúng ta cần thêm công cụ.

Bài này đi sâu vào các hook nâng cao thường gặp trong dự án React Native: useReducer, useContext, useMemo, useCallback, useRef, useLayoutEffect, useImperativeHandle, useTransition, useDeferredValue, useSyncExternalStore và custom hook. Mục tiêu không phải dùng thật nhiều hook, mà là biết chính xác vấn đề nào đáng giải quyết bằng hook nào.

Điều kiện trước khi đọc: bạn nên nắm component, props, state, vòng đời render và useEffect. Có thể xem lại Hooks trong React Native cho người mới và cách dùng useEffect đúng.

Nội dung chính

  • Tư duy trước khi tối ưu bằng hook
  • useReducer: quản lý state có nhiều nhánh
  • useContext: chia sẻ dữ liệu theo cây component
  • useMemo và useCallback: tối ưu có đo lường
  • useRef: giữ dữ liệu mà không render lại
  • useLayoutEffect và useImperativeHandle
  • useTransition và useDeferredValue
  • useSyncExternalStore
  • Thiết kế custom hook dễ tái sử dụng
  • Checklist chọn hook

1. Tư duy trước khi tối ưu bằng hook

Một hook nâng cao chỉ có giá trị khi nó làm code dễ hiểu hơn hoặc giải quyết một vấn đề đã quan sát được. Đừng thêm useMemo và useCallback vào mọi biến, mọi hàm theo phản xạ. Memoization cũng có chi phí: React phải giữ giá trị cũ, so sánh dependency và làm code khó đọc hơn.

Trước khi chọn hook, hãy trả lời bốn câu hỏi:

  • State này có nhiều hành động và nhiều nhánh chuyển đổi không?
  • Giá trị này cần tồn tại qua các lần render nhưng thay đổi của nó có cần cập nhật UI không?
  • Component thực sự chậm hay chỉ đang render lại nhưng rất nhẹ?
  • Logic này có đang lặp lại ở từ hai component trở lên không?

Trong React Native, render JavaScript không đồng nghĩa với việc toàn bộ native view bị dựng lại. Vì vậy, một lần render thêm chưa chắc là vấn đề. Hãy dùng React DevTools Profiler, log có chủ đích và đo trên thiết bị thật trước khi tối ưu.

2. useReducer: quản lý state có nhiều nhánh

useReducer phù hợp khi nhiều giá trị state thay đổi cùng nhau, hoặc khi màn hình có những hành động rõ ràng như bắt đầu tải, tải thành công, tải thất bại và thử lại. Thay vì rải nhiều lời gọi setState, reducer gom quy tắc chuyển đổi state vào một hàm thuần.

import React, {useEffect, useReducer} from 'react';
import {ActivityIndicator, Button, Text, View} from 'react-native';

type User = {id: number; name: string};

type State = {
  status: 'idle' | 'loading' | 'success' | 'error';
  users: User[];
  error: string | null;
};

type Action =
  | {type: 'FETCH_START'}
  | {type: 'FETCH_SUCCESS'; payload: User[]}
  | {type: 'FETCH_ERROR'; payload: string};

const initialState: State = {
  status: 'idle',
  users: [],
  error: null,
};

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'FETCH_START':
      return {...state, status: 'loading', error: null};
    case 'FETCH_SUCCESS':
      return {status: 'success', users: action.payload, error: null};
    case 'FETCH_ERROR':
      return {...state, status: 'error', error: action.payload};
    default:
      return state;
  }
}

Component chỉ còn nhiệm vụ phát sự kiện bằng dispatch:

function UserScreen() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const loadUsers = async () => {
    dispatch({type: 'FETCH_START'});

    try {
      const response = await fetch('https://example.com/users');
      if (!response.ok) throw new Error('Không thể tải người dùng');

      const users: User[] = await response.json();
      dispatch({type: 'FETCH_SUCCESS', payload: users});
    } catch (error) {
      const message =
        error instanceof Error ? error.message : 'Có lỗi xảy ra';
      dispatch({type: 'FETCH_ERROR', payload: message});
    }
  };

  useEffect(() => {
    loadUsers();
  }, []);

  if (state.status === 'loading') return <ActivityIndicator />;

  if (state.status === 'error') {
    return (
      <View>
        <Text>{state.error}</Text>
        <Button title="Thử lại" onPress={loadUsers} />
      </View>
    );
  }

  return (
    <View>
      {state.users.map(user => (
        <Text key={user.id}>{user.name}</Text>
      ))}
    </View>
  );
}

Reducer phải là hàm thuần: không gọi API, không ghi storage và không điều hướng bên trong reducer. Side effect vẫn nằm ở event handler hoặc useEffect. TypeScript với discriminated union giúp phát hiện action thiếu payload ngay khi viết code.

Khi nào chưa cần useReducer?

Nếu màn hình chỉ có một ô tìm kiếm và một biến isLoading độc lập, useState thường dễ đọc hơn. Hãy chuyển sang reducer khi các state bắt đầu phụ thuộc nhau hoặc các quy tắc chuyển đổi khó theo dõi.

3. useContext: chia sẻ dữ liệu theo cây component

useContext giúp truyền dữ liệu xuống sâu mà không phải chuyển props qua nhiều tầng. Các trường hợp phù hợp gồm theme, phiên đăng nhập, ngôn ngữ hoặc cấu hình ứng dụng.

import React, {createContext, useContext, useMemo, useState} from 'react';

type Session = {token: string; displayName: string} | null;

type AuthContextValue = {
  session: Session;
  signIn: (session: NonNullable<Session>) => void;
  signOut: () => void;
};

const AuthContext = createContext<AuthContextValue | undefined>(undefined);

export function AuthProvider({children}: React.PropsWithChildren) {
  const [session, setSession] = useState<Session>(null);

  const value = useMemo(
    () => ({
      session,
      signIn: setSession,
      signOut: () => setSession(null),
    }),
    [session],
  );

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth phải được dùng bên trong AuthProvider');
  }
  return context;
}

Mỗi khi value của provider đổi, các component đang đọc context có thể render lại. Với context lớn và cập nhật liên tục, hãy tách thành nhiều context theo trách nhiệm, ví dụ AuthStateContext và AuthActionsContext. Không nên biến một context duy nhất thành kho chứa mọi state của ứng dụng.

4. useMemo và useCallback: tối ưu có đo lường

useMemo ghi nhớ một giá trị

useMemo hữu ích khi phép tính tương đối nặng hoặc khi cần giữ tham chiếu ổn định cho object truyền vào component đã được memo hóa.

const visibleProducts = useMemo(() => {
  const keyword = query.trim().toLowerCase();

  return products
    .filter(item => item.name.toLowerCase().includes(keyword))
    .sort((a, b) => b.rating - a.rating);
}, [products, query]);

Không dùng useMemo cho những phép tính rất rẻ như nối hai chuỗi. React có thể bỏ cache trong một số tình huống; vì vậy, code không được phụ thuộc vào useMemo để đảm bảo tính đúng đắn.

useCallback ghi nhớ một hàm

useCallback thường có ý nghĩa khi callback được truyền vào component con dùng React.memo, hoặc callback là dependency của hook khác.

type Product = {id: string; name: string};

type RowProps = {
  item: Product;
  onPress: (id: string) => void;
};

const ProductRow = React.memo(function ProductRow({item, onPress}: RowProps) {
  return (
    <Pressable onPress={() => onPress(item.id)}>
      <Text>{item.name}</Text>
    </Pressable>
  );
});

function ProductList({products}: {products: Product[]}) {
  const navigation = useNavigation();

  const openDetail = useCallback(
    (id: string) => navigation.navigate('ProductDetail', {id}),
    [navigation],
  );

  const renderItem = useCallback(
    ({item}: {item: Product}) => (
      <ProductRow item={item} onPress={openDetail} />
    ),
    [openDetail],
  );

  return (
    <FlatList
      data={products}
      keyExtractor={item => item.id}
      renderItem={renderItem}
    />
  );
}

Lưu ý: chỉ bọc renderItem bằng useCallback chưa chắc làm danh sách nhanh hơn. Tối ưu thường cần kết hợp key ổn định, row component gọn, React.memo, kích thước item hợp lý và tránh tạo object style mới không cần thiết. Xem thêm bài xây danh sách với FlatList.

Ba lỗi dependency phổ biến

  • Bỏ dependency để “ngăn render”, khiến callback đọc state cũ.
  • Đưa object được tạo mới mỗi render vào dependency, làm cache luôn mất hiệu lực.
  • Memo hóa mọi thứ mà không đo, khiến code dài hơn nhưng hiệu năng không đổi.

5. useRef: giữ dữ liệu mà không render lại

useRef lưu một object ổn định qua toàn bộ vòng đời component. Khi thay đổi ref.current, React không render lại UI. Có ba nhóm tình huống phổ biến.

5.1. Truy cập native component

function SearchBox() {
  const inputRef = useRef<TextInput>(null);

  return (
    <View>
      <TextInput ref={inputRef} placeholder="Nhập từ khóa" />
      <Button
        title="Focus ô tìm kiếm"
        onPress={() => inputRef.current?.focus()}
      />
    </View>
  );
}

5.2. Lưu timer và dọn dẹp đúng cách

const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

useEffect(() => {
  timerRef.current = setTimeout(() => {
    console.log('Hoàn tất');
  }, 1000);

  return () => {
    if (timerRef.current) clearTimeout(timerRef.current);
  };
}, []);

5.3. Lưu giá trị trước đó

function usePrevious<T>(value: T) {
  const ref = useRef<T | undefined>(undefined);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

Không dùng ref thay state chỉ để né render. Nếu một thay đổi cần hiển thị trên màn hình, nó phải nằm trong state. Ref phù hợp với dữ liệu phục vụ xử lý nội bộ như timer, request ID, giá trị trước đó hoặc handle của native component.

6. useLayoutEffect và useImperativeHandle

useLayoutEffect: đồng bộ trước khi màn hình được vẽ

useLayoutEffect chạy đồng bộ sau khi React cập nhật cây view nhưng trước khi thay đổi được hiển thị. Trong React Native, tình huống dễ gặp nhất là cập nhật header của React Navigation để tránh tiêu đề nháy từ giá trị mặc định sang giá trị mới.

function ProfileScreen({navigation, route}) {
  const {user} = route.params;

  useLayoutEffect(() => {
    navigation.setOptions({
      title: user.displayName,
    });
  }, [navigation, user.displayName]);

  return <Profile user={user} />;
}

Vì hook này chặn việc hiển thị, không đặt tác vụ nặng hoặc gọi API bên trong nó. Phần lớn side effect vẫn nên dùng useEffect.

useImperativeHandle: công khai một API nhỏ qua ref

Đôi khi component cha cần gọi hành động mang tính mệnh lệnh như focus, clear hoặc open. useImperativeHandle cho phép chỉ công khai đúng các hành động cần thiết thay vì để cha truy cập toàn bộ component con.

type SearchInputHandle = {
  focus: () => void;
  clear: () => void;
};

type Props = {
  value: string;
  onChangeText: (value: string) => void;
};

const SearchInput = React.forwardRef<SearchInputHandle, Props>(
  ({value, onChangeText}, ref) => {
    const inputRef = useRef<TextInput>(null);

    useImperativeHandle(
      ref,
      () => ({
        focus: () => inputRef.current?.focus(),
        clear: () => onChangeText(''),
      }),
      [onChangeText],
    );

    return (
      <TextInput
        ref={inputRef}
        value={value}
        onChangeText={onChangeText}
      />
    );
  },
);

Đây nên là ngoại lệ, không phải cách giao tiếp mặc định giữa component. Với dữ liệu thông thường, props và callback vẫn rõ ràng hơn.

7. useTransition và useDeferredValue

Hai hook này giúp ưu tiên cập nhật quan trọng, chẳng hạn thao tác nhập liệu, trước cập nhật có thể trì hoãn như lọc một danh sách lớn. Khả năng hoạt động phụ thuộc phiên bản React đi kèm React Native, vì vậy cần kiểm tra phiên bản trong dự án.

useTransition: đánh dấu cập nhật có độ ưu tiên thấp

function ProductSearch({products}: {products: Product[]}) {
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState(products);
  const [isPending, startTransition] = useTransition();

  const handleChange = (text: string) => {
    setQuery(text); // Cập nhật TextInput ngay

    startTransition(() => {
      const keyword = text.trim().toLowerCase();
      setFiltered(
        products.filter(item =>
          item.name.toLowerCase().includes(keyword),
        ),
      );
    });
  };

  return (
    <View>
      <TextInput value={query} onChangeText={handleChange} />
      {isPending && <ActivityIndicator />}
      <ProductList products={filtered} />
    </View>
  );
}

useDeferredValue: trì hoãn giá trị truyền xuống phần UI nặng

function SearchScreen({products}: {products: Product[]}) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  const filtered = useMemo(() => {
    const keyword = deferredQuery.trim().toLowerCase();
    return products.filter(item =>
      item.name.toLowerCase().includes(keyword),
    );
  }, [deferredQuery, products]);

  const isStale = query !== deferredQuery;

  return (
    <View>
      <TextInput value={query} onChangeText={setQuery} />
      <View style={{opacity: isStale ? 0.6 : 1}}>
        <ProductList products={filtered} />
      </View>
    </View>
  );
}

useTransition và useDeferredValue không phải debounce, không giảm số request mạng và không thay thế thư viện animation chạy trên UI thread. Nếu tìm kiếm gọi API, vẫn cần debounce hoặc hủy request cũ. Nếu animation bị giật, hãy xem xét Animated hoặc Reanimated thay vì kỳ vọng transition xử lý animation native.

8. useSyncExternalStore: đọc dữ liệu ngoài React an toàn

useSyncExternalStore dành cho dữ liệu được quản lý bên ngoài state của React và có cơ chế subscribe, ví dụ một store tự viết, trạng thái kết nối mạng hoặc SDK phát sự kiện. Hook đảm bảo React đọc snapshot nhất quán, kể cả trong cơ chế render đồng thời.

import {useSyncExternalStore} from 'react';

type Listener = () => void;

let online = true;
const listeners = new Set<Listener>();

export const networkStore = {
  getSnapshot: () => online,
  subscribe: (listener: Listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  },
  setOnline: (nextValue: boolean) => {
    online = nextValue;
    listeners.forEach(listener => listener());
  },
};

export function useOnlineStatus() {
  return useSyncExternalStore(
    networkStore.subscribe,
    networkStore.getSnapshot,
  );
}

Trong dự án thật, phần adapter có thể lắng nghe NetInfo hoặc một SDK native, sau đó cập nhật store. Nếu đang dùng Zustand, Redux hoặc một thư viện state phổ biến, hãy ưu tiên hook chính thức của thư viện; họ đã xử lý subscription và selector.

9. Custom hook: đóng gói logic, không đóng gói giao diện

Custom hook là một hàm bắt đầu bằng use và có thể gọi các hook khác. Nó giúp đặt tên cho một hành vi, tái sử dụng logic và làm component tập trung vào UI.

Ví dụ 1: useDebouncedValue

function useDebouncedValue<T>(value: T, delay = 400) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

Cách dùng với tìm kiếm API:

const [query, setQuery] = useState('');
const debouncedQuery = useDebouncedValue(query, 500);

useEffect(() => {
  if (!debouncedQuery.trim()) return;

  const controller = new AbortController();

  searchProducts(debouncedQuery, controller.signal);

  return () => controller.abort();
}, [debouncedQuery]);

Ví dụ 2: useAppState

import {AppState, AppStateStatus} from 'react-native';

function useAppState() {
  const [appState, setAppState] = useState<AppStateStatus>(
    AppState.currentState,
  );

  useEffect(() => {
    const subscription = AppState.addEventListener('change', setAppState);
    return () => subscription.remove();
  }, []);

  return appState;
}

Hook này có thể dùng để làm mới dữ liệu khi ứng dụng quay lại foreground:

const appState = useAppState();
const previousAppState = usePrevious(appState);

useEffect(() => {
  const returnedToForeground =
    previousAppState !== 'active' && appState === 'active';

  if (returnedToForeground) {
    refreshData();
  }
}, [appState, previousAppState, refreshData]);

Nguyên tắc thiết kế custom hook

  • Đặt tên theo hành vi: useAuth, useAppState, useDebouncedValue.
  • Giữ API đầu vào và đầu ra nhỏ, rõ nghĩa.
  • Không che giấu quá nhiều hành vi bất ngờ như tự điều hướng hoặc tự hiện toast.
  • Luôn cleanup listener, timer và request khi component unmount.
  • Trả về object khi có nhiều giá trị để code gọi hook dễ đọc.
  • Test logic quan trọng độc lập bằng thư viện test hook hoặc qua component sử dụng nó.

10. Rules of Hooks và lỗi stale closure

Dù dùng hook cơ bản hay nâng cao, hai quy tắc vẫn không thay đổi: chỉ gọi hook ở top level và chỉ gọi hook bên trong function component hoặc custom hook. Không gọi hook trong if, vòng lặp hay callback thông thường.

Stale closure xảy ra khi một callback giữ lại state của lần render cũ. Ví dụ sau luôn tăng từ giá trị cũ nếu callback được lưu và gọi về sau:

// Dễ đọc phải state cũ trong callback bất đồng bộ
setTimeout(() => setCount(count + 1), 1000);

// An toàn khi cập nhật dựa trên giá trị trước đó
setTimeout(() => setCount(current => current + 1), 1000);

Đừng tắt cảnh báo dependency của ESLint chỉ để hết lỗi. Cảnh báo thường cho biết mô hình dữ liệu đang chưa rõ: có thể cần functional update, chuyển logic vào effect, đưa hàm ra ngoài component hoặc ổn định callback đúng cách.

11. Checklist chọn hook trong dự án React Native

  • State đơn giản, độc lập: dùng useState.
  • State có nhiều hành động, nhiều nhánh: dùng useReducer.
  • Dữ liệu dùng ở nhiều tầng component: cân nhắc useContext.
  • Phép tính nặng hoặc object cần tham chiếu ổn định: cân nhắc useMemo.
  • Callback truyền vào component đã memo hóa: cân nhắc useCallback.
  • Timer, native handle, giá trị trước đó: dùng useRef.
  • Cập nhật layout/header trước khi hiển thị: dùng useLayoutEffect.
  • Cha cần gọi focus/open/clear ở component con: dùng useImperativeHandle có giới hạn.
  • Giữ nhập liệu phản hồi nhanh khi UI phía dưới nặng: cân nhắc useTransition hoặc useDeferredValue.
  • Subscribe store nằm ngoài React: dùng useSyncExternalStore hoặc hook chính thức của thư viện.
  • Logic lặp lại ở nhiều nơi: tách custom hook.

Kết luận

Hook nâng cao không làm ứng dụng tốt hơn chỉ vì chúng “nâng cao”. Giá trị thật nằm ở việc chọn đúng công cụ: reducer giúp state dễ dự đoán, context giảm truyền props, memoization giải quyết điểm nghẽn đã đo được, ref lưu dữ liệu không phục vụ render, còn custom hook tạo ra ngôn ngữ riêng cho nghiệp vụ của ứng dụng.

Một lộ trình luyện tập hợp lý là lấy màn hình gọi API đã có, chuyển trạng thái tải sang useReducer, tách logic thành custom hook, sau đó dùng Profiler kiểm tra danh sách trước và sau khi áp dụng React.memo, useMemo hoặc useCallback. Đây là cách biến kiến thức hook thành kỹ năng thiết kế component thực tế.

Đọc tiếp: Gọi API trong React Native: loading, error và empty state.

SummarizeShare234
admin

admin

Related Stories

Ảnh minh họa bài phỏng vấn Senior React Native

Tổng hợp 50 câu hỏi/trả lời phỏng vấn Senior React Native

by admin
27/07/2026
0

50 câu hỏi phỏng vấn Senior React Native bằng tiếng Việt về technical leadership, architecture, performance, release strategy, observability, security và mentoring.

Ảnh minh họa bài phỏng vấn Middle React Native

Tổng hợp 50 câu hỏi/trả lời phỏng vấn Middle React Native

by admin
27/07/2026
0

50 câu hỏi phỏng vấn Middle React Native bằng tiếng Việt về kiến trúc app, state management, performance, native build, testing, CI/CD và release.

Ảnh minh họa bài phỏng vấn Junior React Native

Tổng hợp 50 câu hỏi/trả lời phỏng vấn Junior React Native

by admin
27/07/2026
0

50 câu hỏi phỏng vấn Junior React Native bằng tiếng Việt, bao gồm component, props, state, hooks, layout, API, navigation, debug và tình huống thực tế.

Lập trình viên chuẩn bị phỏng vấn fresher React Native với laptop, điện thoại và thẻ câu hỏi

Tổng hợp 50 câu hỏi/trả lời phỏng vấn Fresher React Native

by admin
27/07/2026
0

Tổng hợp 50 câu hỏi phỏng vấn Fresher React Native kèm câu trả lời ngắn gọn, dễ hiểu, chia theo nhóm kiến thức Core, Props/State/Hooks, UI, FlatList, Form,...

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • Các hook nâng cao trong React Native: dùng đúng để code gọn và tối ưu hiệu năng
  • Tổng hợp 50 câu hỏi/trả lời phỏng vấn Senior React Native
  • Tổng hợp 50 câu hỏi/trả lời phỏng vấn Middle React Native

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.