3.1 상태값과 속성값으로 관리하는 UI(User Interface) 데이터
- UI(User Interface) 데이터를 컴포넌트 내부에서 관리되는 상태값과 부모 컴포넌트에서 내려주는 속성값으로 관리한다.
- 리액트를 사용하기 위해서는 결국 상태값과 속성값을 이용해서 구현해야 한다.
- UI(User Interface) 데이터가 변경되면 화면을 다시 나타내야 한다.
- UI 데이터가 변동될 때마다 돔 요소들을 직접 수정해야하며 수정하다 보면 코드가 섞이게 되고 복잡해지게 된다.
- 리액트는 화면의 모든 코드를 컴포넌트 함수에 선언형으로 작성하여 UI 데이터 변경을 감지하여 컴포넌트 함수를 이용해 화면을 자동으로 갱신해준다.
3.1.1 리액트를 사용한 코드의 특징
<html>
<body>
<div class="todo">
<h3>할 일 목록</h3>
<ul class="list"></ul>
<input class="desc" type="text" />
<button onclick="onAdd()">추가</button>
<button onclick="onSaveToServer()">서버에 저장</button>>
</div>
<script>
let currentId = 1;
const todoList = [];
function onAdd() {
const inputEl = document.querySelector('.todo .desc');
const todo = { id: currentId, desc: inputEl.value };
todoList.push(todo);
currentId += 1;
const elemList = document.querySelector('.todo .list');
const liEl = makeTodoElement(todo);
elemList.appendChild(liEl);
}
</script>
</body>
</html>
- 초기 화면을 구현하고 todolist 배열에 할일 목록을 추가하고 저장하는 부분이다.
- 로직과 UI 코드가 복잡하게 얽혀있고 가독성이 낮다.
function Mycomponent() {
const [desc, setDesc] = useState("");
const [currentId, setCurrentId] = useState(1);
const [todoList, setTodoList] = useState([]);
function onAdd() {
const todo = { id: currentId, desc };
setCurrentId(currentId + 1);
setTodoList([...todoList, todo]); // ...todoList 는 리스트 전체의 값을 의미함
}
function onSaveToServer() {
// todoList 전송
}
return (
<div class="todo">
<h3>할 일 목록</h3>
<ul>
{todoList.map(todo => (
<li key={todo.id}>
<span>{todo.desc}</span>
<button data-id={todo.id} onClick={onDelete}>
삭제
</button>
</li>
))}
</ul>
</div>
)
}
- 같은 코드를 리액트를 사용하여 변경한 것이다.
- 상태값이 수정되면 컴포넌트 함수를 실행하여 화면을 갱신한다.
- 앞서 사용한 HTML 코드를 명령형 프로그래밍, React를 이용한 코드를 선언형 프로그래밍이라 부른다.
- 명령형 프로그래밍 : 무엇을 어떻게 진행할 것인지에 가깝다. => "저 자리가 비었네요 저기가서 앉을게요."
- 선언형 프로그래밍 : 무엇을 할 것인가에 가깝다. => "앉을 자리를 부탁할께요."
- 명령형 프로그래밍의 경우 돔 환경이 아닌 곳에서 사용하기 힘들다.
- 선언형 프로그래밍의 경우 다양한 방식과 추상화 단계가 높기 때문에 사용방법이 다양하다.
3.1.2 컴포넌트의 속성값과 상태값
- 상태값 : 해당 컴포넌트가 관리하는 데이터
- 속성값 : 부모 컴포넌트로부터 전달받는 데이터
- 리액트는 속성값과 상태값으로 관리되며 속성값과 상태값으로 관리하지 않으면 UI 데이터가 변경돼도 화면이 갱신되지 않을 수 있다.
let color ="red";
function MyComponent() {
function onClick() {
color = "blue";
}
return (
<button style={{ backgroundColor: color }} on Click={onClick}>
좋아요
</button>
);
}
- color 데이터는 배경의 색을 나타내며 초기 의도한 대로 색이 잘 나오지만 버튼을 클릭하면 color 데이터는 파란색으로 변하지만 UI데이터가 변경된 것을 감지하지 못해서 그대로 빨간색 화면이 나타난다.
- 이는 상태값을 이용하지 않았기 때문에 변경을 감지하지 못한 것이다.
import React, { useState } from "react";
function MyComponent() {
const [color, setColor] = useState("red");
function onClick() {
setColor("blue");
}
return (
<button style={{ backgroundColor: color }} on Click={onClick}>
좋아요
</button>
);
}
- 상태값을 추가할 때는 'useState' 훅을 사용한다.
- 'useState'의 인자는 초기 값을 의미하며 배열을 반환해준다. (첫 번째 원소는 상태값, 두 번째 원소는 상태값 변경 함수)
- 리액트는 상태값 변경 함수가 호출되면 상태값을 변경하고 해당 컴포넌트를 다시 렌더링 한다.
- 속성값은 부모 컴포넌트가 전달해 주는 데이터이며 대부분 UI 데이터를 포함하게 된다.
function Title(props) {
return <p>{props.title}</p>;
}
- 부모 컴포넌트로 부터 title의 속성값을 받고 부모 컴포넌트가 렌더링될 때마다 같이 렌더링 되기때문에 title 속성값의 변경 사항이 바로 화면에 보인다.
- 상태값과 속성값을 통해서 UI 데이터를 관리하는 것이 리액트의 핵심 기술이다.
- 만약 특정한 속성값이 변경될 때만 렌더링 되길 원하면 "React.memo"를 이용할 수 있다.
- "React.memo(Title)" 이처럼 Title 컴포넌트가 선언될 경우 컴포넌트 속성값이 변경되는 경우에만 렌더링 된다.
- 같은 컴포넌트라도 자신만의 상태값이 존재하게 된다.
function APP() {
return (
<div>
<MyComponent />
<MyComponent />
</div>
);
}
- 두개의 "MyComponent"가 존재하지만 각자 다른 상태값들을 가지고 있고 컴포넌트는 같더라도 다른 것이다.
- 속성값의 경우 불변(immutable) 변수지만 상태값은 변하는(mutable) 변수이다.
- 자식에게 전달되는 컴포넌트는 부모 컴포넌트에서 관리하기 떄문에 수정할 수 없도록 막혀있다.
- 상태값은 직접 수정이 가능하지만 화면이 갱신되거나 하지는 않는다. (??)
3.1.3 컴포넌트 함수의 반환값
- 우리가 작성한 컴포넌트와 HTML에 정의된 거의 모든 태그 사용가능
- 문자열과 숫자를 반환 가능
- 배열 반환 가능 (각 요소들은 key 값을 가지고 있어야한다. => 구분하기 위해서)
- 프래그먼트(fragment)사용하면 배열과 key 값을 부여하지 않아도 배열로 반환
- 프래그먼트를 바벨을 사용하여 <React.fragment> 가 아닌 <> 로 축약하여 표현
- null or boolean을 반환하면 아무것도 렌더링하지 않음
- 리액트 포털(portal)을 이용하면 컴포넌트의 현재 위치와 관련없이 특정 돔 요소에 렌더링 가능 (ReactDOM.createPortal( 넣을내용, 위치 );)