State-இல் Arrays-ஐ update செய்தல்
Arrays JavaScript-இல் mutable; ஆனால் அவற்றை state-இல் store செய்யும்போது immutable போல treat செய்ய வேண்டும். Objects போலவே, state-இல் store செய்யப்பட்ட array-ஐ update செய்ய விரும்பும்போது, புதிய array ஒன்றை create செய்ய வேண்டும் (அல்லது existing array-ன் copy உருவாக்க வேண்டும்), பின்னர் அந்த புதிய array-ஐ use செய்ய state set செய்ய வேண்டும்.
நீங்கள் கற்றுக்கொள்ள போவது
- React state-இல் array-க்குள் items-ஐ add, remove, அல்லது change செய்வது எப்படி
- Array-க்குள் உள்ள object-ஐ update செய்வது எப்படி
- Immer மூலம் array copying-ஐ குறைவாக repetitive ஆக்குவது எப்படி
Mutation இல்லாமல் arrays-ஐ update செய்தல்
JavaScript-இல் arrays என்பது மற்றொரு வகை object. Objects போலவே, React state-இல் உள்ள arrays-ஐ read-only ஆக treat செய்ய வேண்டும். இதன் பொருள் arr[0] = 'bird' போல array-க்குள் items reassign செய்யக்கூடாது; மேலும் push() மற்றும் pop() போன்ற array-ஐ mutate செய்யும் methods use செய்யக்கூடாது.
அதற்கு பதிலாக, array update செய்யும் ஒவ்வொரு முறையும் உங்கள் state setting function-க்கு புதிய array pass செய்ய வேண்டும். அதற்கு, உங்கள் state-இல் உள்ள original array-இலிருந்து filter() மற்றும் map() போன்ற non-mutating methods call செய்து புதிய array create செய்யலாம். பின்னர் resulting new array-க்கு உங்கள் state set செய்யலாம்.
பொதுவான array operations-க்கான reference table இதோ. React state-க்குள் arrays-ஐ கையாளும்போது, இடது column-இல் உள்ள methods-ஐ தவிர்த்து, வலது column-இல் உள்ள methods-ஐ விரும்பி use செய்ய வேண்டும்:
| தவிர்க்கவும் (array-ஐ mutate செய்கிறது) | விரும்பவும் (புதிய array return செய்கிறது) | |
|---|---|---|
| சேர்த்தல் | push, unshift | concat, [...arr] spread syntax (example) |
| நீக்குதல் | pop, shift, splice | filter, slice (example) |
| மாற்றுதல் | splice, arr[i] = ... assignment | map (example) |
| sort செய்தல் | reverse, sort | முதலில் array-ஐ copy செய்யவும் (example) |
மாற்றாக, இரு columns-இலிருந்தும் methods use செய்ய அனுமதிக்கும் Immer use செய்யலாம்.
Array-க்கு சேர்த்தல்
push() array-ஐ mutate செய்யும்; அதை நீங்கள் விரும்பமாட்டீர்கள்:
import { useState } from 'react'; let nextId = 0; export default function List() { const [name, setName] = useState(''); const [artists, setArtists] = useState([]); return ( <> <h1>ஊக்கமளிக்கும் சிற்பிகள்:</h1> <input value={name} onChange={e => setName(e.target.value)} /> <button onClick={() => { artists.push({ id: nextId++, name: name, }); }}>சேர்</button> <ul> {artists.map(artist => ( <li key={artist.id}>{artist.name}</li> ))} </ul> </> ); }
அதற்கு பதிலாக, existing items மற்றும் இறுதியில் புதிய item ஒன்றைக் கொண்ட புதிய array create செய்யுங்கள். இதை செய்ய பல வழிகள் உள்ளன; ஆனால் நேரடியானது ... array spread syntax use செய்வது:
setArtists( // State-ஐ replace செய்க
[ // புதிய array-ஆல்
...artists, // பழைய items அனைத்தையும் கொண்டது
{ id: nextId++, name: name } // மேலும் இறுதியில் ஒரு புதிய item
]
);இப்போது இது சரியாக வேலை செய்கிறது:
import { useState } from 'react'; let nextId = 0; export default function List() { const [name, setName] = useState(''); const [artists, setArtists] = useState([]); return ( <> <h1>ஊக்கமளிக்கும் சிற்பிகள்:</h1> <input value={name} onChange={e => setName(e.target.value)} /> <button onClick={() => { setArtists([ ...artists, { id: nextId++, name: name } ]); }}>சேர்</button> <ul> {artists.map(artist => ( <li key={artist.id}>{artist.name}</li> ))} </ul> </> ); }
Array spread syntax, original ...artists-க்கு முன் item-ஐ வைத்து prepend செய்யவும் அனுமதிக்கிறது:
setArtists([
{ id: nextId++, name: name },
...artists // பழைய items-ஐ இறுதியில் வைக்கவும்
]);இந்த முறையில், array இறுதியில் add செய்வதன் மூலம் push() செய்யும் வேலையையும், array தொடக்கத்தில் add செய்வதன் மூலம் unshift() செய்யும் வேலையையும் spread செய்ய முடியும். மேலுள்ள sandbox-இல் முயற்சிக்கவும்!
Array-இலிருந்து நீக்குதல்
Array-இலிருந்து item ஒன்றை remove செய்ய நேரடியான வழி அதை filter out செய்வது. வேறு வார்த்தைகளில், அந்த item இல்லாத புதிய array ஒன்றை நீங்கள் produce செய்வீர்கள். இதை செய்ய filter method use செய்யுங்கள், உதாரணமாக:
import { useState } from 'react'; let initialArtists = [ { id: 0, name: 'Marta Colvin Andrade' }, { id: 1, name: 'Lamidi Olonade Fakeye'}, { id: 2, name: 'Louise Nevelson'}, ]; export default function List() { const [artists, setArtists] = useState( initialArtists ); return ( <> <h1>ஊக்கமளிக்கும் சிற்பிகள்:</h1> <ul> {artists.map(artist => ( <li key={artist.id}> {artist.name}{' '} <button onClick={() => { setArtists( artists.filter(a => a.id !== artist.id ) ); }}> நீக்கு </button> </li> ))} </ul> </> ); }
“நீக்கு” button-ஐ சில முறை click செய்து, அதன் click handler-ஐ பாருங்கள்.
setArtists(
artists.filter(a => a.id !== artist.id)
);இங்கே, artists.filter(a => a.id !== artist.id) என்பது “artist.id-இலிருந்து வேறுபட்ட IDs கொண்ட artists மட்டும் உள்ள array create செய்” என்று அர்த்தம். வேறு வார்த்தைகளில், ஒவ்வொரு artist-ன் “நீக்கு” button அந்த artist-ஐ array-இலிருந்து filter out செய்து, resulting array உடன் re-render request செய்யும். filter original array-ஐ modify செய்யாது என்பதை கவனிக்கவும்.
Array-ஐ transform செய்தல்
Array-இல் சில அல்லது எல்லா items-ஐ change செய்ய விரும்பினால், புதிய array create செய்ய map() use செய்யலாம். map-க்கு pass செய்யும் function, ஒவ்வொரு item-க்கும் அதன் data அல்லது index (அல்லது இரண்டும்) அடிப்படையில் என்ன செய்ய வேண்டும் என்பதை decide செய்யலாம்.
இந்த example-இல், ஒரு array இரண்டு circles மற்றும் ஒரு square-ன் coordinates வைத்திருக்கிறது. Button அழுத்தும்போது, circles மட்டும் 50 pixels கீழே நகர்கின்றன. இது map() பயன்படுத்தி data-வின் புதிய array produce செய்வதன் மூலம் நடக்கிறது:
import { useState } from 'react'; let initialShapes = [ { id: 0, type: 'circle', x: 50, y: 100 }, { id: 1, type: 'square', x: 150, y: 100 }, { id: 2, type: 'circle', x: 250, y: 100 }, ]; export default function ShapeEditor() { const [shapes, setShapes] = useState( initialShapes ); function handleClick() { const nextShapes = shapes.map(shape => { if (shape.type === 'square') { // No change return shape; } else { // Return a new circle 50px below return { ...shape, y: shape.y + 50, }; } }); // Re-render with the new array setShapes(nextShapes); } return ( <> <button onClick={handleClick}> Circles-ஐ கீழே நகர்த்து! </button> {shapes.map(shape => ( <div key={shape.id} style={{ background: 'purple', position: 'absolute', left: shape.x, top: shape.y, borderRadius: shape.type === 'circle' ? '50%' : '', width: 20, height: 20, }} /> ))} </> ); }
Array-இல் items-ஐ replace செய்தல்
Array-இல் ஒன்று அல்லது அதற்கு மேற்பட்ட items-ஐ replace செய்ய விரும்புவது மிகவும் common. arr[0] = 'bird' போன்ற assignments original array-ஐ mutate செய்கின்றன; எனவே இதற்கும் map use செய்ய வேண்டும்.
Item ஒன்றை replace செய்ய, map மூலம் புதிய array create செய்யுங்கள். உங்கள் map call-க்குள், இரண்டாவது argument ஆக item index கிடைக்கும். Original item-ஐ (முதல் argument) return செய்ய வேண்டுமா அல்லது வேறு ஏதாவது return செய்ய வேண்டுமா என்பதை decide செய்ய அதைப் பயன்படுத்துங்கள்:
import { useState } from 'react'; let initialCounters = [ 0, 0, 0 ]; export default function CounterList() { const [counters, setCounters] = useState( initialCounters ); function handleIncrementClick(index) { const nextCounters = counters.map((c, i) => { if (i === index) { // Increment the clicked counter return c + 1; } else { // The rest haven't changed return c; } }); setCounters(nextCounters); } return ( <ul> {counters.map((counter, i) => ( <li key={i}> {counter} <button onClick={() => { handleIncrementClick(i); }}>+1</button> </li> ))} </ul> ); }
Array-க்குள் insert செய்தல்
சில நேரங்களில், தொடக்கமோ முடிவோ அல்லாத குறிப்பிட்ட position-இல் item ஒன்றை insert செய்ய விரும்பலாம். இதை செய்ய, slice() method உடன் ... array spread syntax-ஐ use செய்யலாம். slice() method array-ன் ஒரு “slice”-ஐ cut செய்ய அனுமதிக்கிறது. Item insert செய்ய, insertion point-க்கு முன் உள்ள slice-ஐ spread செய்து, பின்னர் new item-ஐ, அதன் பிறகு original array-ன் மீதியை சேர்த்த array create செய்வீர்கள்.
இந்த example-இல், Insert button எப்போதும் index 1-இல் insert செய்கிறது:
import { useState } from 'react'; let nextId = 3; const initialArtists = [ { id: 0, name: 'Marta Colvin Andrade' }, { id: 1, name: 'Lamidi Olonade Fakeye'}, { id: 2, name: 'Louise Nevelson'}, ]; export default function List() { const [name, setName] = useState(''); const [artists, setArtists] = useState( initialArtists ); function handleClick() { const insertAt = 1; // எந்த index-ஆகவும் இருக்கலாம் const nextArtists = [ // Items before the insertion point: ...artists.slice(0, insertAt), // New item: { id: nextId++, name: name }, // Items after the insertion point: ...artists.slice(insertAt) ]; setArtists(nextArtists); setName(''); } return ( <> <h1>ஊக்கமளிக்கும் சிற்பிகள்:</h1> <input value={name} onChange={e => setName(e.target.value)} /> <button onClick={handleClick}> Insert செய் </button> <ul> {artists.map(artist => ( <li key={artist.id}>{artist.name}</li> ))} </ul> </> ); }
Array-க்கு மற்ற changes செய்தல்
Spread syntax மற்றும் map()/filter() போன்ற non-mutating methods மட்டும் கொண்டு செய்ய முடியாத சில விஷயங்கள் உள்ளன. உதாரணமாக, array-ஐ reverse அல்லது sort செய்ய விரும்பலாம். JavaScript reverse() மற்றும் sort() methods original array-ஐ mutate செய்கின்றன; எனவே அவற்றை நேரடியாக use செய்ய முடியாது.
ஆனால், முதலில் array-ஐ copy செய்து, பிறகு அதில் changes செய்யலாம்.
உதாரணமாக:
import { useState } from 'react'; const initialList = [ { id: 0, title: 'Big Bellies' }, { id: 1, title: 'Lunar Landscape' }, { id: 2, title: 'Terracotta Army' }, ]; export default function List() { const [list, setList] = useState(initialList); function handleClick() { const nextList = [...list]; nextList.reverse(); setList(nextList); } return ( <> <button onClick={handleClick}> Reverse செய் </button> <ul> {list.map(artwork => ( <li key={artwork.id}>{artwork.title}</li> ))} </ul> </> ); }
இங்கே, முதலில் original array-ன் copy create செய்ய [...list] spread syntax use செய்கிறீர்கள். இப்போது copy இருப்பதால், nextList.reverse() அல்லது nextList.sort() போன்ற mutating methods use செய்யலாம்; அல்லது nextList[0] = "something" மூலம் individual items assign செய்யவும் முடியும்.
ஆனால், array-ஐ copy செய்தாலும், அதற்குள் உள்ள existing items-ஐ நேரடியாக mutate செய்ய முடியாது. காரணம் copying shallow—புதிய array original array-இல் உள்ள அதே items-ஐ கொண்டிருக்கும். எனவே copied array-க்குள் உள்ள object-ஐ modify செய்தால், existing state-ஐ mutate செய்கிறீர்கள். உதாரணமாக, இத்தகைய code பிரச்சினை.
const nextList = [...list];
nextList[0].seen = true; // பிரச்சினை: list[0]-ஐ mutate செய்கிறது
setList(nextList);nextList மற்றும் list இரண்டு வேறுபட்ட arrays என்றாலும், nextList[0] மற்றும் list[0] அதே object-ஐ point செய்கின்றன. எனவே nextList[0].seen மாற்றும்போது, list[0].seen-ஐயும் மாற்றுகிறீர்கள். இது state mutation; அதை தவிர்க்க வேண்டும்! Nested JavaScript objects update செய்வது போலவே, mutate செய்வதற்கு பதிலாக மாற்ற விரும்பும் individual items-ஐ copy செய்வதன் மூலம் இந்த issue-ஐ solve செய்யலாம். இதோ எப்படி:
Arrays-க்குள் objects update செய்தல்
Objects உண்மையில் arrays-க்குள் “inside” இல்லை. Code-இல் அவை “inside” இருப்பது போல தோன்றலாம்; ஆனால் array-இல் உள்ள ஒவ்வொரு object-உம் array “point” செய்யும் தனி value. அதனால்தான் list[0] போன்ற nested fields மாற்றும்போது கவனமாக இருக்க வேண்டும். மற்றொருவரின் artwork list அதே array element-ஐ point செய்யலாம்!
Nested state update செய்யும்போது, update செய்ய விரும்பும் point-இலிருந்து top level வரை copies create செய்ய வேண்டும். இது எப்படி வேலை செய்கிறது என்று பார்ப்போம்.
இந்த example-இல், இரண்டு தனி artwork lists ஒரே initial state கொண்டுள்ளன. அவை isolated ஆக இருக்க வேண்டும்; ஆனால் mutation காரணமாக அவற்றின் state தவறுதலாக shared ஆகிறது, ஒரு list-இல் box check செய்வது மற்ற list-ஐ பாதிக்கிறது:
import { useState } from 'react'; let nextId = 3; const initialList = [ { id: 0, title: 'Big Bellies', seen: false }, { id: 1, title: 'Lunar Landscape', seen: false }, { id: 2, title: 'Terracotta Army', seen: true }, ]; export default function BucketList() { const [myList, setMyList] = useState(initialList); const [yourList, setYourList] = useState( initialList ); function handleToggleMyList(artworkId, nextSeen) { const myNextList = [...myList]; const artwork = myNextList.find( a => a.id === artworkId ); artwork.seen = nextSeen; setMyList(myNextList); } function handleToggleYourList(artworkId, nextSeen) { const yourNextList = [...yourList]; const artwork = yourNextList.find( a => a.id === artworkId ); artwork.seen = nextSeen; setYourList(yourNextList); } return ( <> <h1>கலை Bucket List</h1> <h2>நான் பார்க்க வேண்டிய art list:</h2> <ItemList artworks={myList} onToggle={handleToggleMyList} /> <h2>நீங்கள் பார்க்க வேண்டிய art list:</h2> <ItemList artworks={yourList} onToggle={handleToggleYourList} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }
பிரச்சினை இத்தகைய code-இல் உள்ளது:
const myNextList = [...myList];
const artwork = myNextList.find(a => a.id === artworkId);
artwork.seen = nextSeen; // பிரச்சினை: existing item-ஐ mutate செய்கிறது
setMyList(myNextList);myNextList array தானாகவே புதியது என்றாலும், items தாமாகவே original myList array-இல் இருந்தவையே. எனவே artwork.seen மாற்றுவது original artwork item-ஐ மாற்றுகிறது. அந்த artwork item yourList-இலும் உள்ளது; அதுவே bug-க்கு காரணம். இத்தகைய bugs பற்றி சிந்திப்பது கடினமாக இருக்கலாம்; ஆனால் state mutate செய்வதைத் தவிர்த்தால் அவை மறைந்துவிடும்.
Mutation இல்லாமல் பழைய item-ஐ அதன் updated version-ஆக substitute செய்ய map use செய்யலாம்.
setMyList(myList.map(artwork => {
if (artwork.id === artworkId) {
// Create a *new* object with changes
return { ...artwork, seen: nextSeen };
} else {
// No changes
return artwork;
}
}));இங்கே, ... என்பது object copy create செய்ய use செய்யப்படும் object spread syntax.
இந்த approach உடன், existing state items எதுவும் mutate செய்யப்படாது; bug fix ஆகும்:
import { useState } from 'react'; let nextId = 3; const initialList = [ { id: 0, title: 'Big Bellies', seen: false }, { id: 1, title: 'Lunar Landscape', seen: false }, { id: 2, title: 'Terracotta Army', seen: true }, ]; export default function BucketList() { const [myList, setMyList] = useState(initialList); const [yourList, setYourList] = useState( initialList ); function handleToggleMyList(artworkId, nextSeen) { setMyList(myList.map(artwork => { if (artwork.id === artworkId) { // Create a *new* object with changes return { ...artwork, seen: nextSeen }; } else { // No changes return artwork; } })); } function handleToggleYourList(artworkId, nextSeen) { setYourList(yourList.map(artwork => { if (artwork.id === artworkId) { // Create a *new* object with changes return { ...artwork, seen: nextSeen }; } else { // No changes return artwork; } })); } return ( <> <h1>கலை Bucket List</h1> <h2>நான் பார்க்க வேண்டிய art list:</h2> <ItemList artworks={myList} onToggle={handleToggleMyList} /> <h2>நீங்கள் பார்க்க வேண்டிய art list:</h2> <ItemList artworks={yourList} onToggle={handleToggleYourList} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }
பொதுவாக, நீங்கள் இப்போதுதான் create செய்த objects-ஐ மட்டுமே mutate செய்ய வேண்டும். புதிய artwork insert செய்தால் அதை mutate செய்யலாம்; ஆனால் ஏற்கனவே state-இல் உள்ள ஒன்றை கையாளும்போது copy உருவாக்க வேண்டும்.
Immer மூலம் concise update logic எழுதுதல்
Mutation இல்லாமல் nested arrays update செய்வது சிறிது repetitive ஆகலாம். Objects போலவே:
- பொதுவாக, state-ஐ இரண்டு levels-ஐ விட ஆழமாக update செய்ய வேண்டிய அவசியம் இருக்கக்கூடாது. உங்கள் state objects மிக deep ஆக இருந்தால், அவை flat ஆக இருக்க வேறு விதமாக restructure செய்ய விரும்பலாம்.
- State structure-ஐ மாற்ற விரும்பவில்லை என்றால், convenient ஆனால் mutating syntax-ஐ பயன்படுத்தி எழுத அனுமதித்து copies produce செய்வதை உங்களுக்காக handle செய்யும் Immer-ஐ use செய்ய விரும்பலாம்.
கலை Bucket List example Immer உடன் rewrite செய்யப்பட்டு இதோ:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Immer உடன் artwork.seen = nextSeen போன்ற mutation இப்போது சரி என்பதை கவனியுங்கள்:
updateMyTodos(draft => {
const artwork = draft.find(a => a.id === artworkId);
artwork.seen = nextSeen;
});காரணம், நீங்கள் original state-ஐ mutate செய்யவில்லை; Immer provide செய்யும் special draft object-ஐ mutate செய்கிறீர்கள். அதேபோல், draft-ன் content-க்கு push() மற்றும் pop() போன்ற mutating methods apply செய்யலாம்.
Behind the scenes, நீங்கள் draft-க்கு செய்த changes அடிப்படையில் Immer எப்போதும் next state-ஐ scratch-இலிருந்து construct செய்கிறது. இதனால் state-ஐ ஒருபோதும் mutate செய்யாமல் உங்கள் event handlers மிகவும் concise ஆக இருக்கும்.
Recap
- Arrays-ஐ state-இல் வைக்கலாம்; ஆனால் அவற்றை மாற்ற முடியாது.
- Array-ஐ mutate செய்வதற்கு பதிலாக, அதன் புதிய version create செய்து state-ஐ அதற்கு update செய்யுங்கள்.
- புதிய items கொண்ட arrays create செய்ய
[...arr, newItem]array spread syntax use செய்யலாம். - Filter செய்யப்பட்ட அல்லது transformed items கொண்ட புதிய arrays create செய்ய
filter()மற்றும்map()use செய்யலாம். - உங்கள் code concise ஆக இருக்க Immer use செய்யலாம்.
Challenge 1 of 4: Shopping cart-இல் item ஒன்றை update செய்தல்
”+” அழுத்தும்போது corresponding number அதிகரிக்க handleIncreaseClick logic-ஐ நிரப்புங்கள்:
import { useState } from 'react'; const initialProducts = [{ id: 0, name: 'Baklava', count: 1, }, { id: 1, name: 'Cheese', count: 5, }, { id: 2, name: 'Spaghetti', count: 2, }]; export default function ShoppingCart() { const [ products, setProducts ] = useState(initialProducts) function handleIncreaseClick(productId) { } return ( <ul> {products.map(product => ( <li key={product.id}> {product.name} {' '} (<b>{product.count}</b>) <button onClick={() => { handleIncreaseClick(product.id); }}> + </button> </li> ))} </ul> ); }