React: Membangun Antarmuka Pengguna · 8/9
Berbagi state: context dan reducer
Kapan prop drilling berhenti memadai, dan cara memindahkan state tanpa library tambahan.
Baca 28 menit
Setelah pelajaran ini kamu bisa
- Mengenali kapan prop drilling sudah jadi masalah
- Membagikan nilai dengan context, dan mengetahui biayanya
- Menulis sebuah reducer, dan menjelaskan kenapa ia mudah diuji
- Memilih antara `useState` dan `useReducer`
Prop drilling adalah mengirim sebuah nilai melewati component yang tidak memakainya, hanya untuk mencapai satu yang memakainya. Dua atau tiga tingkat itu wajar dan eksplisit. Enam tingkat melewati component yang mengabaikannya itu tanda masalah.
Context
import { createContext, useContext, useState } from "react";
interface ThemeValue {
theme: "light" | "dark";
setTheme: (t: "light" | "dark") => void;
}
// undefined as the default lets us detect "used outside a provider".
const ThemeContext = createContext<ThemeValue | undefined>(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<"light" | "dark">("light");
// React 19: the context itself is the provider. No .Provider needed.
return <ThemeContext value={{ theme, setTheme }}>{children}</ThemeContext>;
}
// A custom hook so consumers never touch the context object directly.
export function useTheme() {
const value = useContext(ThemeContext);
if (value === undefined) {
throw new Error("useTheme must be used inside a ThemeProvider");
}
return value;
}undefined misterius saat ada yang lupa provider-nya.Reducer
Reducer adalah pure function: (stateSaatIni, action) => stateBaru. Semua logika pembaruannya berada di satu tempat, dideskripsikan sebagai data, dan karena ia pure maka sangat mudah diuji — tanpa perlu React.
Reducer sungguhan, berjalan sungguhan — ini JavaScript biasa, jadi tidak ada yang disimulasikan di sini.
Hasil
Tekan Jalankan untuk melihat hasilnya.
Ini jalan di browser kamu, di dalam sandbox. Apa pun yang kamu tulis di sini tidak bisa merusak situs.
import { useReducer } from "react";
function BookingForm() {
const [state, dispatch] = useReducer(bookingReducer, initialState);
return (
<>
<p>{state.guests.length} guests, {state.nights} nights</p>
<button onClick={() => dispatch({ type: "guest_added", name: "Ana" })}>
Add guest
</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</>
);
}| Pakai | Saat |
|---|---|
useState | Satu atau dua nilai independen, pembaruan sederhana |
useReducer | Beberapa nilai yang berubah bersama, atau ada aturan di antaranya |
useReducer | State berikutnya bergantung pada yang sekarang dengan cara yang tidak sederhana |
useReducer | Kamu ingin logika pembaruannya bisa diuji tanpa render |
Tugas praktik
Ambil bookingReducer dari runner-nya dan tulis file test untuknya — tanpa React, tanpa rendering, cukup expect(reducer(state, action)). Cakup: menambah saat sudah di batas, membatasi jumlah malam yang sangat besar, menghapus orang yang tidak ada, dan memastikan state input-nya tidak pernah dimutasi. Test terakhir itulah yang akan menangkap bug di masa depan.
Hasil yang diharapkan
Empat test yang lolos tanpa pernah me-render apa pun.