지금까지 다룬 Props는 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터로써, 부모 컴포넌트로부터 전달받은 데이텅니 Props는 자식 컴포넌트에서 변경할 수 없었다.
이제부터 다룰 State는 Porps와는 다르게 한 컴포넌트 안에서 유동적인 데이터를 다룰 때 사용되며, 컴포넌트 안에서 데이터를 변경할 수 있다.
컴포넌트 안에서 State로 데이터를 다루기 위해서는 useState라는 리액트 훅(Hook)을 사용해야한다.
App.tsx 수정
import React, {useState} from 'react';
컴포넌트 안에서 동적으로 변경할 데이터인 할 일 데이터를 다음과 같이 선언한다.
// const [변수명, Set함수명] = useState(데이터 초기값);
const [toDo, setToDo] = useState('');
App.tsx
function App() {
const [toDo, setToDo] = useState('');
return (
<Container>
<Contents>
<ToDoItem label="Todo" onDelete={()=>alert('삭제')}/>
<InputContainer>
<Input onChange={(text => setToDo(text))}/>
<Button label="추가" onClick={()=> alert(todo)}/>
</InputContainer>
</Contents>
</Container>
);
}
useState를 사용하여 할당받은 변수는 불변값이다. 따라서 해당 값은 직접 수정하는것이 불가능하며 해당 값을 변경하기 위해서는 반드시 Set함수를 사용해야 한다.
결과
할일 리스트 만들기 (목록 만들기)
App.tsx
const ToDoListContainer = Styled.div`
min-width: 350px;
height: 400px;
overflow-y: scroll;
border: 1px solid #BDBDBD;
margin-bottom: 20px;
`;
function App() {
const [toDo, setToDo] = useState('');
const [toDoList, setToDoList] = useState<string[]>([]);
const addToDo = (): void => {
if(toDo){
setToDoList([...toDoList,toDo]);
setToDo('');
}
}
const deleteToDo = (index: number): void => {
let list = [...toDoList];
list.splice(index,1);
setToDoList(list);
}
return (
<Container>
<Contents>
<ToDoListContainer>
{
toDoList.map((item, index) => (
<ToDoItem key={item} label={item} onDelete={()=>deleteToDo(index)}/>
))
}
</ToDoListContainer>
<InputContainer>
<Input placeholder="할일을 적어주세요" value={toDo} onChange={(text => setToDo(text))}/>
<Button label="추가" onClick={addToDo}/>
</InputContainer>
</Contents>
</Container>
);
}
useState를 사용하여 할 일 목록 데이터의 State를 정의
const [toDoList, setToDoList] = useState<string[]>([]);
// const [변수명, Set함수명] = useState<데이터 타입>(데이터 초기값);
초기값을 빈 배열을 추가하면 이 배열에 어떤 변수 타입이 들어갈지 타입스크립트는 알 수가 없다. 그러므로 타입스크립트는 보통 데이터의 초기값을 보고 타입을 추론한다. 이 같은경우 타입스크립트의 제네릭을 통해 데이터를 명시적으로 지정하여 해결할수 있다.
const addToDo = (): void => {
if(toDo){
setToDoList([...toDoList,toDo]);
setToDo('');
}
}
toDoList 변수는 불변값이므로 setToDoList 를 통해 새로운 변수를 만든다.
setToDo는 기존의 입력창에 남아있는 내용을 지운다. 맨 아래 value 부분에서 다시 다룰것이다.
const deleteToDo = (index: number): void => {
let list = [...toDoList];
list.splice(index,1);
setToDoList(list);
}
deleteToDo 또한 비슷하다. 기존 데이터를 복사한후 지운후 setToDoList를 통해 새로운 변수를 집어넣는다.
<ToDoListContainer>
{
toDoList.map((item, index) => (
<ToDoItem key={item} label={item} onDelete={()=>deleteToDo(index)}/>
))
}
</ToDoListContainer>
리액트에서는 map과 같은 반복문을 사용하여 반복적으로 동일한 컴포넌트를 화면에 표시하는 경우 key를 항상 사용해야한다.
리액트에서는 데이터 변경이 발생하면 이를 감지하고 가상 돔을 활용하여 변경된 부분을 계산하고 실제 화면을 갱신한다. 반복적으로 동일한 컴포넌트를 사용하게 되면 리액트에서는 해당 컴포넌트가 어떤 데이터와 연결되어 있는지 정확히 인지할 수가 없다. 이렇게 인식할 수 없는 경우 toDoList 에 해당하는 모든 컴포넌트를 지우고 전부 다시 그리게 된다.
이런 문제를 해결하기 위해 반복적으로 표시되는 동일한 컴포넌트에 key값을 설정하여 특정 데이터와 컴포넌트를 연결하게 된다.
<Input placeholder="할일을 적어주세요" value={toDo} onChange={(text => setToDo(text))}/>
value={toDo} 를 통해 새 데이터를 추가하기위해 빈 데이터로 초기화시킨다.
Input 컴포너트를 일부 추가한다.
interface Props {
...
readonly onChange?: (text: string) => void;
}
...
export const Input = ({placeholder, value, onChange} : Props) => {
return (
<InputBox placeholder={placeholder}
value={value}
onChange={(event => {
if(typeof onChange === 'function'){
onChange(event.target.value);
}
})}
/>
);
}
결과
'2022 > 리액트+TDD(完)' 카테고리의 다른 글
Props/ State - 6 (0) | 2022.01.09 |
---|---|
Props/ State - 5 (0) | 2022.01.09 |
Props / State - 3 (0) | 2022.01.08 |
Props / State - 2 (0) | 2022.01.05 |
Props / State - 1 (0) | 2022.01.05 |