학습목표
- state, props의 개념에 대해서 이해하고, 실제 프로젝트에 바르게 적용할 수 있다.
- React 함수 컴포넌트(React Function Component)에서 state hook을 이용하여 state를 정의 및 변경할 수 있다.
- React 컴포넌트(React Component)에 props를 전달할 수 있다.
- 이벤트 핸들러 함수를 만들고 React에서 이용할 수 있다.
- 실제 웹 애플리케이션의 컴포넌트를 보고 어떤 데이터가 state이고 props에 적합한지 판단할 수 있다.
- 실제 웹 애플리케이션 개발 시 적합한 state와 props의 위치를 스스로 정할 수 있다.
- React의 단방향 데이터 흐름(One-way data flow)에 대해 자신의 언어로 설명할 수 있다.
- JSX 문법의 기본과 컴포넌트 기반 개발에 대해서 숙지한다.
- React Router DOM으로 React에서 SPA(Single-Page Application)을 구현할 수 있다.
- state hook을 이용하여, 컴포넌트에서 데이터를 변화시킬 수 있다.
- props를 이용하여, 부모 컴포넌트의 데이터를 자식 컴포넌트로 전달할 수 있다.
- 바람직한 컴포넌트 구조와 state와 props의 위치에 대해 고민한다.
어떤것이 Props와 State에 적합할까?
이름, 성별, 나이, 현재 사는 곳, 취업 여부, 결혼/연애 여부
Props : 이름, 성별
State : 나이, 현재 사는 곳, 취업 여부, 결혼/연애 여부
다음 컴포넌트가 가지는 State는?
on/off 여부이다.
{isOn : true}
{isOn : false}
다음 컴호넌트가 가지는 State는?
현재 숫자 값
{count:0}
{count:3}
{count:6}
즉, 외부의 영향을 받지 않고, 내부(컴포넌트 안)에서 변화하는 값이 State 이다.
컴포넌트의 속성(property)을 의미한다.
props는 외부로부터 전달받은 값으로, 웹 애플리케이션에서 해당 컴포넌트가 가진 속성에 해당한다.
부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값이다.
React 컴포넌트는 JavaScript 함수와 클래스로, props를 함수의 전달인자(argument)처럼 전달받아 이를 기반으로 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환한다. 따라서 컴포넌트가 최초 렌더링 될 때 화면에 출력하고자 하는 데이터를 담은 초깃값으로 사용할 수 있다.
객체 형태이다. props로 어떤 타입의 값도 넣어 전달할 수 있도록 props는 함부로 변경될 수 없는 읽기 전용(read-only)객체 이다. props는 성별이나 이름처럼 외부로부터 전달받아 변하지 않는 값이다. 그래서 props는 함부로 변경될 수 없는 읽기 전용(read-only)객체 이다.
읽기 전용 객체가 아니라면 props를 전달받은 하위 컴포넌트 내에서 props를 직접 수정 시 props를 전달한 상위 컴포넌트 값에 영향을 미칠 수 있게 된다. 즉 개발자가 의 도하지 않은 side effect가 생성되고, 이는 React 의 단방향, 하향식 데이터 흐름 원칙에 위배된다.
props를 사용하는 방법은 아래와 같이 3단계 순서로 나눌 수 있다.
- 하위 컴포넌트에 전달하고자 하는 값(data)과 속성을 정의한다.
- props를 이용하여 정의된 값과 속성을 전달한다.
- 전달받은 props를 렌더링 한다.
<Parent>
, <Child>
컴포넌트 선언
function Parent() {
return (
<div className="parent">
<h1>I'm the parent</h1>
<Child />
</div>
);
};
function Child() {
return (
<div className="child"></div>
);
};
다음으로 전달하고자 하는 속성을 정의해보자. HTML에서 속성과 값을 할당하는 방법과 같다.
코드에서는 <a>
요소의 <href>
속성에 "www.eugenius1st.com"
이라는 값을 주었다.
<a href ="www.eugenius1st.com">Click me to visit </a>
React에서 속성 및 값을 할당하는 방법도 이와 유사하다. 다만 전달하고자 하는 값을 중괄호 {}
를 이용하여 감싸주면 된다.
React 에서 JSX 속성 및 값을 할당하는 방법
<Child attribute = {value} />
위 방법을 이용하여 <Child />
컴포넌트에 전달하고 <Parent>
컴포넌트에 전달한 문자열을 <Child>
에서 받아보자.
<Child text={"I'm the one"} />
function Child(props){
return(
<div className="child"></div>
) ;
}
props를 전달받았으니, 마지막으로 props를 렌더링해보자. props를 렌더링 하려면 JSX안에 직접 불러서 사용하면 된다. 이 객체의 {key:value}는 <Parent>
컴포넌트에서 정의한 {attribute:value}
의 형태를 띠게 된다.
따라서 JavaScript 에서 객체의 value에 접근할 때 dot notation을 사용하는 것과 동일하게 props의 value또한 dot notation으로 접근할 수 있다.
function Child(props){
return(
<div className="child">
<p>{props.text}</p>
</div>
);
};
여는 태그와 닫는 태그 사이에 value를 넣어 전달하는 방법이 있다
function Parent() {
return (
<div className="parent">
<h1>I'm the parent</h1>
<Child>I'm the eldest child</Child>
</div>
);
};
function Child(props) {
return (
<div className="child">
<p>{props.children}</p>
</div>
);
};
코드 불러오기
import { useState } from "react";
useState를 호출하면 배열을 반환하는데, 배열의 0번째 요소는 현재 state변수이고, 1번째 요소는 이 변수를 갱신할 수 있는 함수이다. useState의 인지로 넘겨주는 값은 state의 초깃값이다
onst [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);
function CheckboxExample() {
const [isChecked, setIsChecked] = useState(false);
const handleChecked = (event) => {
setIsChecked(event.target.checked);
};
return (
<div className="App">
<input type="checkbox" checked={isChecked} onChange={handleChecked} />
<span>{isChecked ? "Checked!!" : "Unchecked"}</span>
</div>
);
}
React 이벤트 처리 방식은 DOM의 이벤트 처리 방식과 유사하다. 단, 몇가지 문법 차이가 있다.
예를들어 HTML 에서 이벤트 처리 방식이 아래와 같다면
<button onclick="handleEvent()">Event</button>
React의 이벤트 처리 방식은 아래와 같다
<button onClick={handleEvent}>Event</button>
응용법 : onChange
이벤트가 발생하면 e.target.value
를 통해 이벤트 객체에 담겨있는 값을 읽어올 수 있다. 이벤트가 발생하면 함수가 작동하며, 새로운 setState
를 통해 새로운 state
로 갱신할 수 있다.
function NameForm() {
const [name, setName] = useState("");
const handleChange = (e) => {
setName(e.target.value);
}
return (
<div>
<input type="text" value ={name} onChange={handleChange}></input>
<h1>{name}</h1>
</div>
)
};
onClick
이벤트는 클릭이라는 행동을 하였을 때 발생하는 이벤트 이다. 예시를 보면 버튼 클릭 시 alert 통해 팝업이 되는 코드이다.
function NameForm() {
const [name, setName] = useState("");
const handleChange = (e) => {
setName(e.target.value);
}
return (
<div>
<input type="text" value={name} onChange={handleChange}></input>
<button onClick={alert(name)}>Button</button>
<h1>{name}</h1>
</div>
);
};
위 처럼 handleChange
함수를 전달 할 수 있지만, 아래처럼 arrow function을 사용하여 함수를 정의하여 해당 컴포넌트가 가진 state에 함수들이 접근할 수도 있다.
// 함수 정의하기
return (
<div>
...
<button onClick={() => alert(name)}>Button</button>
...
</div>
);
};
// 함수 자체를 전달하기
const handleClick = () => {
alert(name);
};
return (
<div>
...
<button onClick={handleClick}>Button</button>
...
</div>
);
};
select tag
는 사용자가 drop down 목록을 열어 그 중 한가지 옵션을 선택하면, 선택한 옵션이 state 변수에 갱신된다. useState
가 어떠한 상태를 갱신하도록 설정되어 있는지 확인한 뒤, 주석에 따라 select tag
가 정상적으로 작동할 수 있도록 코드를 완성해보자
pop up
역시 open 과 close를 통해 관리할 수 있다. 이 예제 또한 useState
를 확인하여 버튼 클릭에 따라 하단의 이미지 와 같이 pop up 이 open/close 되도록 코드를 완성해보자
리액트의 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 점이다.
그림과 같이 프로토타입을 전달받았다고 해보자.
페이지를 만들기 이전에, 컴포넌트를 먼저 만들고 조립한다.
즉, 상향식(bottom-up)으로 앱을 만든다. 이것은 테스트가 쉽고 확장성이 좋다는 장점이 있다.
컴포넌트 디자인은 그림과 같은 형태로 나눈다. 이유는 단일 책임에 따른 구분이다. 하나의 컴포넌트는 한가지 일만 한다.
이를 트리구조로 나타내면 다음과 같다.
데이터를 어디 둘지 정해보면,
컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 인자(argument) 혹은 속성(attribute)처럼 전달받을 수 있다.
데이터를 전달하는 주체는 부모 컴포넌트가 된다. 이는 데이터 흐름이 하향식(top-down)임을 의미한다.
이 원칙은 단방향 데이터 흐름(one-way data flow)이라는 키워드가 React를 대표하는 설명중 하나일 정도로 중요하다. 또한 컴포넌트는 props 를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못한다.
상태가 특정 컴포넌트에서만 유의미 하다면, 특정 컴포넌트에만 두면 되니 어렵지 않지만, 하나의 상태를 기반으로 두 컴포넌트가 영향을 받는다면 이때는 공통 소유 컴포넌트를 찾아 그 곳에 상태를 위치시켜야 한다.
즉, 두개의 자식 컴포넌트가 하나의 상태에 접군히과 할 때는 두 자식의 공통 부모 컴포넌트에 상태를 위치해야 한다.