Memilih Struktur State
Menata struktur state dengan baik dapat membuat perbedaan antara komponen yang mudah dimodifikasi dan di-debug, dan komponen yang menjadi sumber kesalahan yang konstan. Berikut adalah beberapa tips yang harus Anda pertimbangkan saat menata struktur state.
Anda akan mempelajari
- Kapan harus menggunakan satu variabel state vs beberapa variabel state
- Apa yang harus dihindari ketika mengatur state
- Bagaimana cara mengatasi masalah umum dengan struktur state
Prinsip-prinsip untuk menata state
Ketika Anda menulis komponen yang memegang beberapa state, Anda harus membuat pilihan tentang berapa banyak variabel state yang harus digunakan dan bagaimana bentuk datanya. Meskipun mungkin saja menulis program yang benar dengan struktur state yang kurang optimal, ada beberapa prinsip yang dapat membimbing Anda untuk membuat pilihan yang lebih baik:
- Kelompokkan state yang terkait. Jika Anda selalu memperbarui dua atau lebih variabel state secara bersamaan, pertimbangkan untuk menggabungkannya menjadi satu variabel state tunggal.
- Hindari kontradiksi dalam state. Saat state diorganisir sedemikian rupa sehingga beberapa bagian state dapat saling bertentangan dan “tidak sependapat” satu sama lain, maka ini bisa meninggalkan celah untuk kesalahan. Coba hindari hal ini.
- Hindari state yang redundan. Jika Anda dapat menghitung beberapa informasi dari prop komponen atau variabel state yang sudah ada selama rendering, maka Anda tidak perlu memasukkan informasi tersebut ke dalam state komponen tersebut.
- Hindari duplikasi dalam state. Ketika data yang sama terduplikasi antara beberapa variabel state, atau dalam objek bertingkat, maka akan sulit menjaga sinkronisasi antara mereka. Kurangi duplikasi ketika memungkinkan.
- Hindari state yang sangat bertingkat. State dengan hierarkis yang dalam sangat tidak mudah untuk diperbarui. Saat memungkinkan, lebih baik menata state dengan datar.
Tujuan di balik prinsip-prinsip ini adalah membuat state mudah diperbarui tanpa memperkenalkan kesalahan. Menghapus data yang redundan dan duplikat dari state membantu memastikan bahwa semua bagian state tetap sinkron. Ini mirip dengan bagaimana seorang insinyur database mungkin ingin menormalisasi struktur database untuk mengurangi kemungkinan bug. Untuk mem-parafrase Albert Einstein, “Jadikan state Anda sesederhana mungkin - tetapi jangan terlalu sederhana.”
Sekarang mari kita lihat bagaimana prinsip-prinsip tersebut diterapkan dalam tindakan.
Mengelompokkan state terkait.
Anda mungkin kadang-kadang tidak yakin antara menggunakan satu variabel state atau beberapa variabel state.
Haruskah Anda melakukan hal ini?
const [x, setX] = useState(0);
const [y, setY] = useState(0);
atau ini?
const [position, setPosition] = useState({ x: 0, y: 0 });
Teknisnya, Anda dapat menggunakan kedua pendekatan ini. Namun, jika dua variabel state selalu berubah bersama-sama, mungkin ide yang baik untuk menggabungkannya menjadi satu variabel state. Dengan begitu, Anda tidak akan lupa untuk selalu menjaga keduanya selalu sinkron, seperti dalam contoh ini di mana menggerakkan kursor memperbarui kedua koordinat titik merah:
import { useState } from 'react'; export default function MovingDot() { const [position, setPosition] = useState({ x: 0, y: 0 }); return ( <div onPointerMove={e => { setPosition({ x: e.clientX, y: e.clientY }); }} style={{ position: 'relative', width: '100vw', height: '100vh', }}> <div style={{ position: 'absolute', backgroundColor: 'red', borderRadius: '50%', transform: `translate(${position.x}px, ${position.y}px)`, left: -10, top: -10, width: 20, height: 20, }} /> </div> ) }
Ada kasus lain di mana Anda akan mengelompokkan data ke dalam objek atau senarai ketika Anda tidak tahu berapa banyak bagian dari state yang Anda butuhkan. Sebagai contoh, ini berguna ketika Anda memiliki formulir di mana pengguna dapat menambahkan bidang kustom.
Hindari kontradiksi dalam state
Berikut adalah formulir umpan balik hotel dengan variabel state isSending
dan isSent
:
import { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); const [isSent, setIsSent] = useState(false); async function handleSubmit(e) { e.preventDefault(); setIsSending(true); await sendMessage(text); setIsSending(false); setIsSent(true); } if (isSent) { return <h1>Thanks for feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>How was your stay at The Prancing Pony?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Send </button> {isSending && <p>Sending...</p>} </form> ); } // Pretend to send a message. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Saat kode ini dijalankan, kode masih memungkinkan terjadinya keadaan “tidak mungkin”. Contohnya, jika kita lupa memanggil setIsSent
dan setIsSending
bersama-sama, kita dapat berakhir dalam situasi di mana kedua isSending
dan isSent
bernilai true
pada saat yang sama. Semakin kompleks komponen Anda, semakin sulit untuk memahami apa yang terjadi
Sejak isSending
dan isSent
seharusnya tidak pernah bernilai true
pada saat yang sama, lebih baik menggantinya dengan satu variabel state status
yang dapat mengambil salah satu dari tiga status yang valid: 'typing'
(initial), 'sending'
, and 'sent'
:
import { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [status, setStatus] = useState('typing'); async function handleSubmit(e) { e.preventDefault(); setStatus('sending'); await sendMessage(text); setStatus('sent'); } const isSending = status === 'sending'; const isSent = status === 'sent'; if (isSent) { return <h1>Thanks for feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>How was your stay at The Prancing Pony?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Send </button> {isSending && <p>Sending...</p>} </form> ); } // Berpura-puralah mengirim pesan. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Anda masih bisa mendeklarasikan beberapa konstanta untuk keterbacaan:
const isSending = status === 'sending';
const isSent = status === 'sent';
Tetapi itu bukan variabel state, jadi Anda tidak perlu khawatir tentang kesalahan sinkronisasi antar variabel.
Hindari state yang redundan
Jika Anda dapat menghitung beberapa informasi dari prop komponen atau variabel state yang sudah ada selama me-render, Anda tidak harus meletakkan informasi tersebut ke dalam state komponen tersebut.
Sebagai contoh, ambil formulir ini. Ini berfungsi, tetapi dapatkah Anda menemukan keadaan yang redundan di dalamnya?
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> <h2>Let’s check you in</h2> <label> First name:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Last name:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Your ticket will be issued to: <b>{fullName}</b> </p> </> ); }
Formulir ini mempunya 3 variabel state: firstName
, lastName
, dan fullName
. Namun, fullName
adalah redundan. Y Anda selalu dapat menghitung fullName
dari firstName
dan lastName
selama render, sehingga hapus dari keadaan.
Ini adalah bagaimana Anda dapat melakukannya:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> <h2>Let’s check you in</h2> <label> First name:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Last name:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Your ticket will be issued to: <b>{fullName}</b> </p> </> ); }
Di sini, fullName
bukan merupakan sebuah variabel state. Sebaliknya, nilai fullName
dihitung saat render:
const fullName = firstName + ' ' + lastName;
Sebagai hasilnya, pengontrol perubahan tidak perlu melakukan apa pun khusus untuk memperbarui fullName
. Ketika Anda memanggil setFirstName
atau setLastName
, Anda memicu render ulang, dan kemudian fullName
berikutnya akan dihitung dari data terbaru.
Pendalaman
Contoh umum code yang memiliki state yang redundan seperti dibawah ini:
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor);
Di sini, sebuah variabel state color
diinisialisasi dengan prop messageColor
. Masalahnya adalah bahwa jika komponen induk memberikan nilai messageColor
berbeda (misalnya, 'red'
daripada 'blue'
), variabel state color
tidak akan diperbarui! State hanya di-inisialisasi selama render pertama.
Ini mengapa “menirukan” beberapa prop pada variabel state dapat menyebabkan kebingungan. Sebaliknya, gunakan prop messageColor
langsung dalam kode Anda. Jika Anda ingin memberinya nama yang lebih pendek, gunakan konstanta:
function Message({ messageColor }) {
const color = messageColor;
Dengan cara ini, state tidak akan keluar dari sinkron dengan prop yang dilewatkan dari komponen induk.
“Menggandakan” props ke dalam state hanya masuk akal ketika Anda ingin mengabaikan semua pembaruan untuk prop tertentu. Secara konvensional, awali nama prop dengan initial
atau default
untuk menjelaskan bahwa nilai baru prop tersebut diabaikan:
function Message({ initialColor }) {
// Variabel state `color` menyimpan nilai pertama kali dari `initialColor`.
// Perubahan lebih lanjut pada prop `initialColor` diabaikan.
const [color, setColor] = useState(initialColor);
Hindari duplikasi dalam state
Komponen daftar menu ini memungkinkan Anda memilih satu camilan dari beberapa pilihan:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); return ( <> <h2>What's your travel snack?</h2> <ul> {items.map(item => ( <li key={item.id}> {item.title} {' '} <button onClick={() => { setSelectedItem(item); }}>Choose</button> </li> ))} </ul> <p>You picked {selectedItem.title}.</p> </> ); }
Saat ini, komponen daftar menu ini menyimpan item yang dipilih sebagai objek dalam variabel state selectedItem
. Namun, hal ini tidak bagus: isi selectedItem
adalah objek yang sama dengan salah satu item dalam daftar items
. Ini berarti informasi tentang item itu sendiri diduplikasi di dua tempat.
Mengapa ini menjadi masalah? Mari kita buat setiap item dapat diedit:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> <h2>What's your travel snack?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedItem(item); }}>Choose</button> </li> ))} </ul> <p>You picked {selectedItem.title}.</p> </> ); }
Perhatikan bahwa jika Anda pertama kali mengklik “Pilih” pada item dan kemudian mengeditnya, input akan diperbarui tetapi label di bagian bawah tidak mencerminkan suntingan tersebut. Hal ini terjadi karena adanya duplikasi state, dan kamu lupa untuk memperbarui selectedItem
.
Meskipun Anda bisa memperbarui selectedItem
juga, perbaikan yang lebih mudah adalah menghilangkan duplikasi. Pada contoh ini, daripada menggunakan objek selectedItem
(yang menciptakan duplikasi dengan objek yang ada di dalam items
), Anda menyimpan selectedId
di dalam state, dan kemudian mendapatkan selectedItem
dengan mencari senarai items
untuk item dengan ID tersebut:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedId, setSelectedId] = useState(0); const selectedItem = items.find(item => item.id === selectedId ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> <h2>What's your travel snack?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedId(item.id); }}>Choose</button> </li> ))} </ul> <p>You picked {selectedItem.title}.</p> </> ); }
(Sebagai alternatif, Anda dapat menyimpan indeks yang dipilih di dalam state.)
State sebelumnya diduplikasi seperti ini:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedItem = {id: 0, title: 'pretzels'}
Tetapi setelah diubah menjadi seperti ini:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedId = 0
Duplikasi data sudah tidak ada lagi, dan hanya menyimpan state yang penting!
Sekarang jika Anda mengubah item yang dipilih, pesan di bawahnya akan segera diperbarui. Ini karena setItems
memicu render ulang, dan items.find(...)
akan menemukan item dengan judul yang diperbarui. Anda tidak perlu menyimpan item yang dipilih di state, karena hanya ID yang dipilih yang penting. Yang lain dapat dihitung selama render.
Hindari state yang sangat bertingkat
Bayangkan rencana perjalanan yang terdiri dari planet, benua, dan negara. Anda mungkin tergoda untuk mengatur state-nya menggunakan objek dan senarai yang bersarang, seperti contoh ini:
export const initialTravelPlan = { id: 0, title: '(Root)', childPlaces: [{ id: 1, title: 'Earth', childPlaces: [{ id: 2, title: 'Africa', childPlaces: [{ id: 3, title: 'Botswana', childPlaces: [] }, { id: 4, title: 'Egypt', childPlaces: [] }, { id: 5, title: 'Kenya', childPlaces: [] }, { id: 6, title: 'Madagascar', childPlaces: [] }, { id: 7, title: 'Morocco', childPlaces: [] }, { id: 8, title: 'Nigeria', childPlaces: [] }, { id: 9, title: 'South Africa', childPlaces: [] }] }, { id: 10, title: 'Americas', childPlaces: [{ id: 11, title: 'Argentina', childPlaces: [] }, { id: 12, title: 'Brazil', childPlaces: [] }, { id: 13, title: 'Barbados', childPlaces: [] }, { id: 14, title: 'Canada', childPlaces: [] }, { id: 15, title: 'Jamaica', childPlaces: [] }, { id: 16, title: 'Mexico', childPlaces: [] }, { id: 17, title: 'Trinidad and Tobago', childPlaces: [] }, { id: 18, title: 'Venezuela', childPlaces: [] }] }, { id: 19, title: 'Asia', childPlaces: [{ id: 20, title: 'China', childPlaces: [] }, { id: 21, title: 'India', childPlaces: [] }, { id: 22, title: 'Singapore', childPlaces: [] }, { id: 23, title: 'South Korea', childPlaces: [] }, { id: 24, title: 'Thailand', childPlaces: [] }, { id: 25, title: 'Vietnam', childPlaces: [] }] }, { id: 26, title: 'Europe', childPlaces: [{ id: 27, title: 'Croatia', childPlaces: [], }, { id: 28, title: 'France', childPlaces: [], }, { id: 29, title: 'Germany', childPlaces: [], }, { id: 30, title: 'Italy', childPlaces: [], }, { id: 31, title: 'Portugal', childPlaces: [], }, { id: 32, title: 'Spain', childPlaces: [], }, { id: 33, title: 'Turkey', childPlaces: [], }] }, { id: 34, title: 'Oceania', childPlaces: [{ id: 35, title: 'Australia', childPlaces: [], }, { id: 36, title: 'Bora Bora (French Polynesia)', childPlaces: [], }, { id: 37, title: 'Easter Island (Chile)', childPlaces: [], }, { id: 38, title: 'Fiji', childPlaces: [], }, { id: 39, title: 'Hawaii (the USA)', childPlaces: [], }, { id: 40, title: 'New Zealand', childPlaces: [], }, { id: 41, title: 'Vanuatu', childPlaces: [], }] }] }, { id: 42, title: 'Moon', childPlaces: [{ id: 43, title: 'Rheita', childPlaces: [] }, { id: 44, title: 'Piccolomini', childPlaces: [] }, { id: 45, title: 'Tycho', childPlaces: [] }] }, { id: 46, title: 'Mars', childPlaces: [{ id: 47, title: 'Corn Town', childPlaces: [] }, { id: 48, title: 'Green Hill', childPlaces: [] }] }] };
Sekarang katakanlah Anda ingin menambahkan tombol untuk menghapus tempat yang telah Anda kunjungi. Bagaimana cara melakukannya? Memperbarui state yang bertingkat melibatkan membuat salinan objek sepanjang jalan dari bagian yang berubah. Menghapus tempat yang sangat tertanam akan melibatkan menyalin seluruh rantai tempat induknya. Kode semacam itu bisa sangat panjang.
Jika state terlalu bersarang untuk diperbarui dengan mudah, pertimbangkan untuk membuatnya “datar”. Berikut adalah salah satu cara Anda dapat memperbarui struktur data ini. Alih-alih struktur seperti pohon di mana setiap tempat
memiliki sebuah senarai dari tempat anaknya, Anda dapat membuat setiap tempat memegang sebuah senarai dari ID tempat anaknya. Kemudian simpan pemetaan dari setiap ID tempat ke tempat yang sesuai.
Penataan data ini mungkin mengingatkan Anda pada tabel di basis data:
export const initialTravelPlan = { 0: { id: 0, title: '(Root)', childIds: [1, 42, 46], }, 1: { id: 1, title: 'Earth', childIds: [2, 10, 19, 26, 34] }, 2: { id: 2, title: 'Africa', childIds: [3, 4, 5, 6 , 7, 8, 9] }, 3: { id: 3, title: 'Botswana', childIds: [] }, 4: { id: 4, title: 'Egypt', childIds: [] }, 5: { id: 5, title: 'Kenya', childIds: [] }, 6: { id: 6, title: 'Madagascar', childIds: [] }, 7: { id: 7, title: 'Morocco', childIds: [] }, 8: { id: 8, title: 'Nigeria', childIds: [] }, 9: { id: 9, title: 'South Africa', childIds: [] }, 10: { id: 10, title: 'Americas', childIds: [11, 12, 13, 14, 15, 16, 17, 18], }, 11: { id: 11, title: 'Argentina', childIds: [] }, 12: { id: 12, title: 'Brazil', childIds: [] }, 13: { id: 13, title: 'Barbados', childIds: [] }, 14: { id: 14, title: 'Canada', childIds: [] }, 15: { id: 15, title: 'Jamaica', childIds: [] }, 16: { id: 16, title: 'Mexico', childIds: [] }, 17: { id: 17, title: 'Trinidad and Tobago', childIds: [] }, 18: { id: 18, title: 'Venezuela', childIds: [] }, 19: { id: 19, title: 'Asia', childIds: [20, 21, 22, 23, 24, 25], }, 20: { id: 20, title: 'China', childIds: [] }, 21: { id: 21, title: 'India', childIds: [] }, 22: { id: 22, title: 'Singapore', childIds: [] }, 23: { id: 23, title: 'South Korea', childIds: [] }, 24: { id: 24, title: 'Thailand', childIds: [] }, 25: { id: 25, title: 'Vietnam', childIds: [] }, 26: { id: 26, title: 'Europe', childIds: [27, 28, 29, 30, 31, 32, 33], }, 27: { id: 27, title: 'Croatia', childIds: [] }, 28: { id: 28, title: 'France', childIds: [] }, 29: { id: 29, title: 'Germany', childIds: [] }, 30: { id: 30, title: 'Italy', childIds: [] }, 31: { id: 31, title: 'Portugal', childIds: [] }, 32: { id: 32, title: 'Spain', childIds: [] }, 33: { id: 33, title: 'Turkey', childIds: [] }, 34: { id: 34, title: 'Oceania', childIds: [35, 36, 37, 38, 39, 40, 41], }, 35: { id: 35, title: 'Australia', childIds: [] }, 36: { id: 36, title: 'Bora Bora (French Polynesia)', childIds: [] }, 37: { id: 37, title: 'Easter Island (Chile)', childIds: [] }, 38: { id: 38, title: 'Fiji', childIds: [] }, 39: { id: 40, title: 'Hawaii (the USA)', childIds: [] }, 40: { id: 40, title: 'New Zealand', childIds: [] }, 41: { id: 41, title: 'Vanuatu', childIds: [] }, 42: { id: 42, title: 'Moon', childIds: [43, 44, 45] }, 43: { id: 43, title: 'Rheita', childIds: [] }, 44: { id: 44, title: 'Piccolomini', childIds: [] }, 45: { id: 45, title: 'Tycho', childIds: [] }, 46: { id: 46, title: 'Mars', childIds: [47, 48] }, 47: { id: 47, title: 'Corn Town', childIds: [] }, 48: { id: 48, title: 'Green Hill', childIds: [] } };
Sekarang karena state-nya “datar” (juga dikenal sebagai “dinormalisasi”), memperbarui item yang bersarang menjadi lebih mudah.
Untuk menghapus sebuah tempat sekarang, Anda hanya perlu memperbarui dua level state:
- Versi terbaru dari parent tempatnya harus menghapus ID yang dihapus dari senarai
childIds
. - Versi terbaru dari objek ”table” induk harus mencakup versi terbaru dari tempat parentnya.
Berikut adalah contoh bagaimana Anda bisa melakukannya:
import { useState } from 'react'; import { initialTravelPlan } from './places.js'; export default function TravelPlan() { const [plan, setPlan] = useState(initialTravelPlan); function handleComplete(parentId, childId) { const parent = plan[parentId]; // Buatlah versi baru dari induk tempat tersebut // Buatlah versi baru dari parent place yang tidak termasuk ID child ini const nextParent = { ...parent, childIds: parent.childIds .filter(id => id !== childId) }; // Perbarui objek state root... setPlan({ ...plan, // ...Perbarui objek state induk sehingga memiliki parent yang telah diperbarui. [parentId]: nextParent }); } const root = plan[0]; const planetIds = root.childIds; return ( <> <h2>Places to visit</h2> <ol> {planetIds.map(id => ( <PlaceTree key={id} id={id} parentId={0} placesById={plan} onComplete={handleComplete} /> ))} </ol> </> ); } function PlaceTree({ id, parentId, placesById, onComplete }) { const place = placesById[id]; const childIds = place.childIds; return ( <li> {place.title} <button onClick={() => { onComplete(parentId, id); }}> Complete </button> {childIds.length > 0 && <ol> {childIds.map(childId => ( <PlaceTree key={childId} id={childId} parentId={id} placesById={placesById} onComplete={onComplete} /> ))} </ol> } </li> ); }
Anda dapat menempatkan state sebanyak yang Anda inginkan, tetapi membuatnya menjadi “datar” dapat memecahkan banyak masalah. Ini membuat state lebih mudah diperbarui, dan membantu memastikan Anda tidak memiliki duplikasi di bagian yang berbeda dari objek bertingkat
Pendalaman
Idealnya, Anda juga harus menghapus item yang dihapus (dan anak-anaknya!) dari objek “table” untuk meningkatkan penggunaan memori. Versi ini melakukan itu. ini juga menggunakan Immer untuk membuat logika pembaruan lebih ringkas.
import { useImmer } from 'use-immer'; import { initialTravelPlan } from './places.js'; export default function TravelPlan() { const [plan, updatePlan] = useImmer(initialTravelPlan); function handleComplete(parentId, childId) { updatePlan(draft => { // Hapus ID anak dari tempat induknya. const parent = draft[parentId]; parent.childIds = parent.childIds .filter(id => id !== childId); // Melupakan tempat ini dan semua subtree-nya. deleteAllChildren(childId); function deleteAllChildren(id) { const place = draft[id]; place.childIds.forEach(deleteAllChildren); delete draft[id]; } }); } const root = plan[0]; const planetIds = root.childIds; return ( <> <h2>Places to visit</h2> <ol> {planetIds.map(id => ( <PlaceTree key={id} id={id} parentId={0} placesById={plan} onComplete={handleComplete} /> ))} </ol> </> ); } function PlaceTree({ id, parentId, placesById, onComplete }) { const place = placesById[id]; const childIds = place.childIds; return ( <li> {place.title} <button onClick={() => { onComplete(parentId, id); }}> Complete </button> {childIds.length > 0 && <ol> {childIds.map(childId => ( <PlaceTree key={childId} id={childId} parentId={id} placesById={placesById} onComplete={onComplete} /> ))} </ol> } </li> ); }
Kadang-kadang, Anda juga dapat mengurangi penempelan status dengan memindahkan beberapa penempelan status ke komponen anak. Ini bekerja dengan baik untuk status UI sementara yang tidak perlu disimpan, seperti apakah sebuah item di-hover.
Rekap
- Jika dua variabel state selalu diperbarui bersama, pertimbangkan untuk menggabungkannya menjadi satu.
- Pilih variabel state dengan hati-hati untuk menghindari menciptakan keadaan yang “mustahil”.
- Strukturkan state Anda sedemikian rupa sehingga mengurangi kemungkinan kesalahan saat memperbarui state.
- Hindari penggunaan state yang redundan dan duplikat sehingga tidak perlu menjaga sinkronisasi.
- Jangan memasukkan props ke dalam state kecuali Anda secara khusus ingin mencegah pembaruan.
- Untuk pola UI seperti pemilihan, simpan ID atau indeks dalam state daripada objek itu sendiri.
- Jika memperbarui state yang sangat berlapis-lapis menjadi rumit, coba datanya didatarkan.
Tantangan 1 dari 4: Sesuaikan komponen yang tidak terbarui
Komponen Clock
ini menerima dua prop: color
dan time
. Ketika Anda memilih warna yang berbeda pada kotak pilihan, Clock
menerima prop color
yang berbeda dari komponen induknya. Namun, warna yang ditampilkan tidak diperbarui. Mengapa? Perbaiki masalahnya.
import { useState } from 'react'; export default function Clock(props) { const [color, setColor] = useState(props.color); return ( <h1 style={{ color: color }}> {props.time} </h1> ); }