아래 코드에서 const에 관해서 로직이 이해가 안된다.
뭔가 이상함 기준이 없음 상수가 바뀜
해결하려면 공식문서 참고해서 원리를 이해해야할듯함
initialGameBoardr가 const 상수형인데
handleSelect로 setGameBoard(newGameBoard); 하고나서
initialGameBoardr을 map으로 다시 돌면 수정되어있음;;
어째서? 너무 대놓고 상수인데 어떻게 수정되는거지?
그 원리를 모르겠음
src05useState02불변변수03실습4_reset추가_XXX
import React, { useState } from 'react';
import './GameBoarder.css';
const initialGameBoard = [
[null, null, null],
[null, null, null],
[null, null, null]
];
function GameBoarder(props) {
const [gameBoard, setGameBoard] = useState(initialGameBoard);
function handleSelect(rowIndex, colIndex){
var newGameBoard = [...gameBoard];
newGameBoard[rowIndex][colIndex] ="X";
setGameBoard(newGameBoard);
}
function handleReset(){
// const resetBoard = [
// [null, null, null],
// [null, null, null],
// [null, null, null]
// ]
// setGameBoard(resetBoard);
const newGameBoard2 = [...gameBoard];
gameBoard.map((r, ri)=>{
r.map((c, ci)=>{
gameBoard[ri][ci] = null;
})
})
setGameBoard(newGameBoard2);
}
console.log("App 호출");
return (
<div>
<ol id="game-board">
{initialGameBoard.map((row, rowIndex) => (
<li key={rowIndex}>
<ol>
{row.map((col, colIndex) => {
console.log(col, rowIndex, colIndex); //col은 데이터임
return (
<li key={colIndex}>
<button
disabled={col != null}
onClick={() => handleSelect(rowIndex, colIndex)}
>
{col}
</button>
</li>
);
})}
</ol>
</li>
))}
</ol>
<button onClick={handleReset}>초기화</button>
</div>
);
}
export default GameBoarder;
이에 대한 채찍피디의 명석한 답변>
Q. 여기서 initialGameBoard 가 실제로 수정되는거야?
A.
네, 이 코드에서 initialGameBoard는 실제로 수정되고 있습니다. 문제는 handleReset 함수 내에서 발생합니다. 이 함수에서는 gameBoard 배열의 모든 요소를 순회하며 각 요소를 null로 설정하고 있습니다. 하지만, 여기서 중요한 점은 newGameBoard2 = [...gameBoard]라는 구문이 gameBoard 배열의 얕은 복사(shallow copy)를 생성한다는 것입니다. 이는 gameBoard 배열의 최상위 레벨은 새로운 배열로 복사되지만, 배열 내부의 배열(즉, 게임 보드의 각 행)은 여전히 원본 gameBoard 배열 내부의 배열과 동일한 참조를 가리킨다는 것을 의미합니다.
따라서, handleReset 함수 내부에서 gameBoard[ri][ci] = null로 설정하면, 이 변경사항은 newGameBoard2 뿐만 아니라 원본 gameBoard 배열과 initialGameBoard 배열에도 반영됩니다. 왜냐하면, 이들 모두가 내부적으로 동일한 배열 객체들을 참조하기 때문입니다.
이를 해결하기 위해서는 handleReset 함수에서 깊은 복사(deep copy)를 수행해야 합니다. 가장 간단한 방법은 배열의 각 요소(여기서는 각 행)도 새로운 배열로 복사하는 것입니다. 예를 들어, 다음과 같이 수정할 수 있습니다:
function handleReset() {
const newGameBoard = gameBoard.map(row => row.map(() => null));
setGameBoard(newGameBoard);
}
이 변경을 통해, handleReset 함수는 gameBoard의 상태를 안전하게 초기 상태로 재설정할 수 있으며, 이는 initialGameBoard나 다른 상태에 영향을 미치지 않습니다.
한마디로 상수가 바뀔 수 있었던 이유는 상수 자체의 주소값은 바뀔 수 없지만 내부의 값은 바뀔 수 있기 때문이다...
그리고 객체는 새로 생성했지만 얕은 복사(shallow copy 맞나?)로 값을 복사하였기 때문에 내부값으로 갖는 배열이나 객체들의 주소값은 변하지 않는다. 그러므로 값의 변경이 공유된다... 증말로 신기하고 방통하다!
재작성 코드
import React, { useState } from 'react';
function App(props) {
const [list, setList] = useState([
[null, null, null],
[null, null, null],
[null, null, null]
]);
function toggle(i, i2){
// console.log(i, i2);
list[i][i2]=(list[i][i2]==null)?"x":null;
const list2 = [...list]; //일부러 새로운 주소에 얕은 복사로 값만 복사해오기 위해 rest연산자를 이용하는거임
//list2[i][i2]="x"; //복사된 배열객체에서 내부값을 수정하든 복사하기 전에 수정하든 같은 결과를 냄
setList(list2) //참조값을 변경하면서 변경을 인식하게끔 하여 랜더링이 작동되게함
}
function reset(){
const list = ([
[null, null, null],
[null, null, null],
[null, null, null]
]); //함수 내부에서 list로 로컬변수를 생성해였기 떄문에 함수스코프 밖의 list와 다른 참조값을 갖는다.
// 따라서 불변객체 관리에 사용이 가능
// 헷갈리면 그냥 list2로 다르게 해도 되지만 하는 김에 짚고 가려고 일부러 같게 했다.
setList(list)
}
return (
<div>
<table border={1}>
{list.map((v, i)=>{
return(
<tr key={i}>
{v.map((v2, i2)=>{
return(
<td key={i2}>
<button onClick={()=>toggle(i, i2)}>{v2}</button>
</td>
)
})}
</tr>
)
})}
</table>
<button onClick={reset}>초기화</button>
</div>
);
}
export default App;
json은 중복된 키값이 존재할 경우 가장 뒤쪽의 값으로 덮어쓰는 로직을 갖는다.
xxx이름의 json객체에 name값을 수정하고 싶다?
setXxx({...JSON, name:"새로운값"})
속성값 동적 바인딩하는 법 다시 익히기 (input의 name과 객체의 속성명을 일치시키면 아래와 같이 코드 작성이 가능하다.)
setUserid({...inputs, [event.target.name]:event.target.value})
자바스크립트에서 JSON 객체(또는 보다 정확하게 말하자면, 리터럴 객체) 내에서 키 값으로 변수를 사용하려면, ES6(ECMAScript 2015)에서 도입된 계산된 속성명(computed property names) 문법을 사용할 수 있습니다. 이를 통해 객체 리터럴 내부에서 대괄호([])를 사용하여 변수의 값을 속성 이름으로 활용할 수 있습니다. 이 방법은 동적으로 객체의 키를 생성하거나 할당할 때 유용합니다.
src_05_useState03_two_way_binding_input태그사용2_passwd추가
import React, { useState } from 'react';
function App(props) {
const [inputs, setUserid] = useState({userid:"", passwd:""});
function handleSubmit(event){
event.preventDefault();
}
function handleChange(event){
setUserid({...inputs, [event.target.name]:event.target.value})
console.log("handleChange: ", inputs.userid);
}
console.log("app호출: ", inputs );
return (
<div>
<h1>input 태그</h1>
<form onSubmit={handleSubmit} action="target.html">
아이디: <input type='text' name='userid' value={inputs.userid} onChange={handleChange}></input>
비밀번호: <input type='password' name='passwd' value={inputs.passwd} onChange={handleChange}></input>
<input type='submit' value="로그인"></input>
</form>
</div>
);
}
export default App;
import React, { useState } from 'react';
function App(props) {
const [num, setNum] = useState(0);
const [step, setStep] = useState(1);
function cal(e){
setNum((e.target.innerText=="+")?(num+step):((num<step)?(num):(num-step)));
}
return (
<div>
<h1>state실습</h1>
step:
<select onChange={(e)=>setStep(parseInt(e.target.value))}>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select><br/>
step:{step}<br/>
num:{num}<br/>
<button onClick={(e)=>cal(e)}>+</button>
<button onClick={(e)=>cal(e)}>-</button>
</div>
);
}
export default App;

타켓 태그의 속성을 구조분해할당으로 사용
const {name, value} = e.target;
src05_useState03_two_way_binding_input태그사용3실습6
MemberListFun.js
import React, { useState } from 'react';
import MemberFun from './MemberFun';
function MemberListFun(props) {
const [members, setMembers] = useState([]);
const [member, setMember] = useState({
username: '',
age: '',
address: ''
})
function memberChange(e){
const {name, value} = e.target;
console.log(name, " : ", value);
setMember({...member, [name]:value});
}
return (
<div>
<form onSubmit={(e)=>{
e.preventDefault();
setMembers([...members, member])
}}>
이름<input type='text' name="username" onKeyUp={(e)=>memberChange(e)}></input><br/>
나이<input type='text' name="age" onKeyUp={(e)=>memberChange(e)}></input><br/>
주소<input type='text' name="address" onKeyUp={(e)=>memberChange(e)}></input><br/>
<button>저장</button><br/>
</form>
<table border={1}>
<tbody>
{members.map((v, i)=>{
return (
<MemberFun key={i} idx={i} v={v}></MemberFun>
)
})}
</tbody>
</table>
</div>
);
}
export default MemberListFun;
MemberFun.js
import React from 'react';
function MemberFun(props) {
const {i, v} = props;
return (
<tr>
<td>{v.username}</td>
<td>{v.age}</td>
<td>{v.address}</td>
</tr>
);
}
export default MemberFun;
다시 구현. 구현 실패함
주요 구문들
const [inputs, setInputs] = useState({
memberData:[],
username:'',
age:'',
address:''
});
const {memberData, username, age, address} =inputs;
filter메서드 사용
자식에서 부모 메서드 전달받아 사용
구현성공
import React, { useState } from 'react';
import MemberFun from './MemberFun';
function MemberListFun(props) {
const [inputs, setInputs] = useState({
memberData:[],
username:'',
age:'',
address:''
});
const {memberData, username, age, address} =inputs;
function inputChange(event){
//console.log(event.target.name, " : ", event.target.value);
setInputs({...inputs, [event.target.name]:event.target.value});
}
function submitHandle(event){
event.preventDefault();
var user = {
username,
age,
address
};
// console.log(user);
setInputs({...inputs, memberData:[...memberData, user]})
// 그러니까 이게 그 inputs객체에 들어있는 memberData에 username,age,address를 객체 형태로 저장해야함
//setInputs({...inputs, memberData:memberData.push(user)});
}
// 삭제함수
const handleDelete = (idx) => {
setInputs({
...inputs,
memberData: memberData.filter((v, i)=>i!=idx)
// filter : 지정된 함수의 결과 값을 true로 만드는 원소들로만 구성된 별도의 배열을 반환한다.
// memberData 배열에서 index값이 idx인 값을 제거
})
}
console.log(memberData);
return (
<div>
<form onSubmit={submitHandle}>
이름<input type='text' onKeyUp={inputChange} name="username"/> <br/>
나이<input type='text' onKeyUp={inputChange} name="age"/> <br/>
주소<input type='text' onKeyUp={inputChange} name="address"/> <br/>
<button>저장</button>
</form>
<MemberFun memberData={memberData} onDelete={handleDelete}></MemberFun>
</div>
);
}
export default MemberListFun;
import React from 'react';
function MemberFun(props) {
console.log("자식", props);
const {memberData, onDelete} = props;
// console.log(memberData);
return (
<div>
<table border={1}>
{memberData.map((v, i)=>{
return (
<tr key={i}>
<td>{v.username}</td>
<td>{v.age}</td>
<td>{v.address}</td>
<td><button onClick={()=>onDelete(i)}>삭제</button></td>
</tr>
);
})}
</table>
</div>
);
}
export default MemberFun;
import React, { useState } from 'react';
export function test1(props){
const {msg, isEditing} = props;
return(
<div>
<h1>{msg}</h1>
<input type="text" disabled={!isEditing}></input>
<button>{(isEditing)?"Edit":"Save"}</button>
</div>
)
}
function App(props) {
const [isEditing, setIsEditing] = useState(true);
if(isEditing){
var tag = <span>홍길동</span>
}else{
var tag = <input/>
}
return (
<div>
{/* <test1 msg="조건부 랜더링 구현법 1:if문 이용" isEditing={isEditing}></test1> */}
<h1>조건부 랜더링 구현법 1:if문 이용</h1>
{tag}
<button onClick={()=>{setIsEditing(!isEditing)}}>{(isEditing)?"Edit":"Save"}</button>
<h1>조건부 랜더링 구현법 2: 3항연산자 이용</h1>
{isEditing?<span>홍길동</span> : <input/>}
<button onClick={()=>{setIsEditing(!isEditing)}}>{(isEditing)?"Edit":"Save"}</button>
<h1>조건부 랜더링 구현법 3: 3항연산자 이용</h1>
{isEditing && <span>홍길동</span>}
{!isEditing && <input/>}
<button onClick={()=>{setIsEditing(!isEditing)}}>{(isEditing)?"Edit":"Save"}</button>
</div>
);
}
export default App;
import React, { useState } from 'react';
function Player(props) {
const [enterdPlayerName, setEnterdPlayerName] = useState("");
const [submitted, setSubmitted] = useState(false);
function handleChange(e){
setSubmitted(false);
setEnterdPlayerName(e.target.value);
}
function handleClick(){
setSubmitted(true);
}
console.log(true);
return (
<div>
<h1>환영합니다 {(submitted)?enterdPlayerName:"unknown entity"}</h1>
<input type='text' onChange={handleChange} value={enterdPlayerName}/>
<button onClick={handleClick}>저장</button>
</div>
);
// const [username, setUsername] = useState("unknown entity")
// return (
// <div>
// <h1>환영합니다 {username}</h1>
// <form onSubmit={(e)=>{e.preventDefault(); setUsername(e.target[0].value)}}>
// <input type='text'/>
// <button>저장</button>
// </form>
// </div>
// );
}
export default Player;
useState는 뷰가 계속 재랜더링되는 반면
일반적으로 생성된 DOM 노드 또는 컴포넌트에 직접 접근할 때 주로 사용되고
current 속성을 이용한다.
state와 다르게 값을 변경해도 재랜더링 되지 않기 때문에 화면에 보여주기 위한
정보를 저장하는 용도로는 적합하지 않다.
import React, { useRef, useState } from 'react';
function App(props) {
const playerName = useRef();
//태그 중 ref={playerName}을 참조함
// State와 다르게 값으 변경해도 재랜더링되지 않는다.
// current 속성을 사용한다.
const [enteredPlayerName, setEnterdPlayerName] = useState(null);
//저장 버튼을 클릭해을 때만 재랜더링 됨
function handleClick(){
setEnterdPlayerName(playerName.current.value);
playerName.current.value="";
}
return (
<div>
<h1>환영합니다 {enteredPlayerName??"unknown entity"}</h1>
<input type='text' ref={playerName}/>
{/* 3) useRef와 연동 */}
<button onClick={handleClick}>저장</button>
</div>
);
}
export default App;
부모 컴포넌트에서 자식 컴포넌트 접근시 ref 속성으로 이용해야 되는데 ref는 일반
Props 로 처리되지 않기 때문에 forwardRef()를 사용하여 자식 컴포넌트를 감싸야 된다.
이렇게 ref 속성을 이용하여 자식 컴포넌트를 참조할 때 사용된다.
forwardRef 함수내에서 부모에서 호출할 자식함수는 useImperativeHandle()로 감싸야
부모에게 노출시킬 수 있다.
자식에서 함수 등록
import React, { forwardRef, useImperativeHandle } from 'react';
//forwardRef로 함수를 감싼다.
//일반함수는 안됨
const Box = forwardRef((props, ref) =>{
//반드시 props와 ref 따로 받음
useImperativeHandle(ref, ()=>({ open }));// ({open})에서 ()필수
const open = () => {
console.log("box.oepn()========");
};
return (
<div>
<hr></hr>
box component
<hr></hr>
</div>
);
})
export default Box;
부모에서 useRef로 자식 함수 사용
import React, { useRef } from 'react';
import Box from './components/Box';
function App(props) {
const boxRef = useRef(); // 1번 useRef생성
function handleEvent(){
console.log("handleEvent");
boxRef.current.open(); // 자식 box의 메소드 호출 가능
}
return (
<div>
<hr/>
<button onClick={handleEvent}>자식의 open호출</button>
<Box ref={boxRef}></Box>
// 2번 : useRef 참조
<hr/>
</div>
);
}
export default App;