웹에서 필요한 데이터를 다루는 방법
들어가며
사용자가 쇼핑몰에서 옷을 주문할 때, 원하는 옷을 장바구니에 담고 결제를 하려고 보니 다른 옷이 더 마음에 들어 장바구니에 담은 옷을 빼고, 새로운 옷을 담아 결제를 한다. 이 상황에서 장바구니에 담기는 옷은 바뀌고 사용자가 결제 해야 할 금액도 즉각적으로 변경된다. 이를 우리는 장바구니의 상태가 변한다고 말할 수 있으며, 결제 페이지에 변경된 장바구니의 상태를 전달해야 한다.
간단한 예시
- state (나이, 사는 곳, 직장 등 살면서 변할 수 있는 값 ) : 컴포넌트 사용 중 컴포넌트 내부에서 변할 수 있는 값
- Props (고향) : 외부로부터 전달받은 값
state는 아래에서도 자세히 서술했지만 대략 이런 느낌이다. (아래 참고)
이 컴포넌트가 가지는 State는 On/Off 여부이다. 아래와 같이 구현될 수 있겠다.
{ isOn: true }
{ isOn: false }
state는 현재 숫자 값이다. 아래와 같이 구현될 수 있겠다.
{ count : 0}
{ count : 3 }
{ count : 6 }
props
정의 및 특징
- 컴포넌트의 속성(property) , 즉 웹 애플리케이션에서 해당 컴포넌트가 가진 속성이다.
- props는 '태어난 곳'처럼 변하지 않는 외부로부터 전달받은 값이다.
- 부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값이다.
-> 클래스 컴포넌트뿐만 아니라 함수형 컴포넌트에서도 사용할 수 있다.
-> React 컴포넌트는 JavaScript 함수와 클래스로, props를 함수의 전달인자(arguments)처럼 전달받아 이를 기반으로 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환한다. 따라서, 컴포넌트가 최초 렌더링 될 때 화면에 출력하고자 하는 데이터를 담은 초깃값으로 사용할 수 있다.
ES6 class 문법으로도 컴포넌트를 만들 수 있고, 이를 클래스 컴포넌트라고 한다. 이와 대조해서 함수 문법을 사용하여 만든 컴포넌트를 말 그대로 함수 컴포넌트라고 부른다. Hook이 나오기 전에는 state는 클래스 컴포넌트에만 다룰 수 있었으나, React 16.8 버전에 Hook이 나오면서 함수 컴포넌트도 state를 다룰 수 있게 되었다.
- 객체 형태이다. props로 어떤 타입의 값도 넣어 전달할 수 있도록 props는 객체의 형태를 가진다.
- props는 읽기 전용이다. props는 고향처럼 외부로부터 전달받아 변하지 않는 값이다. 그래서 props는 함부로 변경될 수 없는 읽기 전용(read-only) 객체이다. 함부로 변경되지 않아야 하기 때문이다.
읽기 전용 객체가 아니라면 props를 전달받은 하위 컴포넌트 내에서 props를 직접 수정 시 props를 전달한 상위 컴포넌트의 값에 영향을 미칠 수 있게 된다. 즉, 개발자가 의도하지 않은 side effect가 생기게 되고 이는 React의 단방향, 하향식 데이터 흐름 원칙(React is all about one-way data flow down the component hierarchy)에 위배된다.
* 정리하자면 props는 immutable한 데이터이며, 부모에서 자식으로 혹은 구성 요소 자체에서 데이터를 전달하는 데 사용된다.
How to use props
props를 사용하는 방법은 아래와 같이 3단계 순서로 나눌 수 있다.
- 하위 컴포넌트에 전달하고자 하는 값(data)과 속성을 정의한다.
- props를 이용하여 정의된 값과 속성을 전달한다.
- 전달받은 props를 렌더링한다.
예시 (아래에서 하나씩 설명)
const App = () => {
const itemOne = "React를";
const itemTwo = "배우고 있습니다.";
return (
<div className="App">
<Learn text={itemOne} />
<Learn text={itemTwo} />
</div>
);
};
const Learn = (props) => {
return <div className="Learn">
<p>{props.text}</p>
</div>;
};
export default App;
차례대로 해보자.
Step 1 .하위 컴포넌트에 전달하고자 하는 값(data)과 속성을 정의한다.
props를 사용하기 위해 우선 <Parent> 와 <Child> 라는 컴포넌트를 선언하고, <Parent> 컴포넌트 안에 <Child> 컴포넌트를 작성한다.
function Parent() {
return (
<div className="parent">
<h1>I'm the parent</h1>
<Child /> // 🟣 Child 컴포넌트가 Parent 컴포넌트 안에 들어가 있다.
</div>
);
};
function Child() { // 🟣 Child 컴포넌트
return (
<div className="child"></div>
);
};
컴포넌트를 만들었으니 이제 전달하고자 하는 속성을 정의해 본다.
HTML에서는 속성과 값을 할당하는 방법이 다음과 같다. <a> 요소의 href 속성에 "www.codestates.com" 라는 값을 주었다.
<a href="www.codestates.com">Click me to visit Code States</a>
React에서 JSX 속성 및 값을 할당하는 방법도 이와 유사하다. 다만, 전달하고자 하는 값을 중괄호 {}를 이용하여 감싸주면 된다. 위 방법을 이용하여 text라는 속성을 선언하고, 이 속성에 "I'm the eldest child"라는 문자열 값을 할당하여 컴포넌트에 전달한다.
<Child attribute={value} />
<Child text={"I'm the eldest child"} />
Step 2. props를 이용하여 정의된 값과 속성을 전달한다.
<Parent> 컴포넌트에서 전달한 "I'm the eldest child"라는 문자열을 <Child> 컴포넌트에서 받아오는 방법은 간단하다. 함수에 인자를 전달하듯이 React 컴포넌트에 props를 전달하면 되고, 이 props가 필요한 모든 데이터를 가지고 오게 된다.
function Child(props) {
return (
<div className="child"></div> 🟣 // 아직 끝이 아님
);
};
Step 3. 전달받은 props를 렌더링한다.
props를 전달받았으니, 마지막으로 이 props를 렌더링하려면 JSX 안에 직접 불러서 사용하면 된다.
- 위에서 언급했듯이, props는 객체다.
- 이 객체의 { key : value }는 <Parent> 컴포넌트에서 정의한 { attribute : value }의 형태를 띠게 된다.
- 따라서 JavaScript에서 객체의 value에 접근할 때 dot notation을 사용하는 것과 동일하게 props의 value 또한 dot notation으로 접근할 수 있다.
아래와 같이 props.text를 JSX에 중괄호와 함께 작성하면 잘 작동한다.
function Child(props) {
return (
<div className="child">
<p>{props.text}</p>
</div>
);
};
Step 4. 완성
import "./styles.css";
function Parent() {
return (
<div className="parent">
<h1>I'm the parent</h1>
<Child text={"I'm the eldest child"} />
<Child text={"1"} />
<Child text={"2"} />
</div>
);
}
function Child(props) {
// console 을 열어 props 의 형태를 직접 확인
console.log("props : ", props);
return (
<div className="child">
<p>{props.text}</p>
</div>
);
}
export default Parent;
props.children
props를 전달하는 또 다른 방법으로 ***여는 태그와 닫는 태그의 사이***에 value를 넣어 전달하는 방법이 있다. 이 경우rprops.children을 이용하면 해당 value에 접근하여 사용할 수 있다. 모든 컴포넌트에서 children prop을 이용해서 진행할 수 있다. props.children은 props의 특별한 속성으로, 컴포넌트의 여는 태그와 닫는 태그 사이(HTML content 자리)의 내용을 포함한다.
(자세한 내용은 공식 문서를 참고)
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>
);
};
props을 사용하는 예시
Hello 컴포넌트에서 name이라는 props를 전달하는 방법
* 자식 component
const Hello = (props) => <div>{props.name}</div>
* 부모 component
function Say() { return ( <Hello name="walli" /> ) } |
const Say = () => <Hello name={"walli"} /> |
function Say() { const name = "walli"; return <Hello name={name} />; } |
function Say() { const props = { name: "walli" }; return <Hello {...props} />; } |
함수 선언식 {} 없이도 된다. |
함수 표현식 | name 이라는 변수에 값을 할당하여 전달하는 방식 |
props 라는 변수에 값을 할당 후, spread syntax를 이용하여 전달했다. {name:"wailli"}를 전달받는다. * 만약 props 자체를 전달, 즉 <Hello {props} /> 라고 쓰게 되면 SyntaxError: /src/App.js: Unexpected token, expected "..." 에러가 발생하면서 "..." 를 활용하길 권장한다. * 혹은 별도의 props의 이름을 지정해서 전달해야 한다. |
틀린 예) const Say = () => <Hello props={name: "walli"} /> |
Syntax Error가 난다. props의 이름은 사용될 context가 아닌 컴포넌트 자체의 관점에서 짓는 것을 권장한다. |
state
애플리케이션의 "상태"
state는 컴포넌트 내부에서 변할 수 있는 값이다. 실제 애플리케이션의 쇼핑몰 장바구니에서는 무엇이 "상태"라고 할 수 있을까?
사용자는 구매할 물건과 당장은 구매하지 않을 물건을 체크박스에 체크하여 구분 짓는다. 이를 장바구니 내에서의 상태로 구분해 본다면 check 된 상태와 check 되지 않은 상태이다.
체크박스를 코드로 구현해 보면 아래와 같다. 아래 예시에서는 단순히 체크된 상태에 따라 보이는 글씨가 달라지지만, 이를 쇼핑몰에 적용하면 체크 여부에 따라 구매할 물건의 개수나 구매 금액이 변경되고, 이에 따라 사용자의 화면 또한 달라진다. 이처럼 컴포넌트 내에서 변할 수 있는 값, 즉 상태는 React state로 다뤄야 한다.
예시 (아래에 하나씩 설명)
import React, { useState } from "react";
import "./styles.css";
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>
);
}
export default CheckboxExample;
State hook, useState
useState 사용법
React에서는 state 를 다루는 방법 중 하나로 useState 라는 특별한 함수를 제공한다. useState는 state를 함수 컴포넌트 안에서 사용할 수 있게 해준다. 이 함수의 사용 방법과 작동 방식을 위의 체크박스 예로 들어 살펴보겠다.
useState 를 이용하기 위해서는 React로부터 useState 를 불러와야 한다. import 키워드로 useState 를 불러온다
import { useState } from "react";
이후 useState 를 컴포넌트 안에서 호출해 준다. useState 를 호출한다는 것은 "state" 라는 변수를 선언하는 것과 같으며, 이 변수의 이름은 아무 이름으로 지어도 된다. 일반적인 변수는 함수가 끝날 때 사라지지만, state 변수는 React에 의해 함수가 끝나도 사라지지 않는다. 문법적으로 보면 아래 예시의 isChecked, setIsChecked 는 useState 의 리턴값을 구조 분해 할당한 변수이다
function CheckboxExample() {
// 🟣 새로운 state 변수를 선언하고, 변수명은 isChecked 라고 지었다.
const [isChecked, setIsChecked] = useState(false);
}
useState 를 호출하면 배열을 반환하는데, 배열의 0번째 요소는 현재 state 변수이고, 1번째 요소는 이 변수를 갱신할 수 있는 함수이다. useState 의 인자로 넘겨주는 값은 state의 초깃값이다.
function CheckboxExample() {
// 🟣 1번 코드를 풀어쓰면
const [isChecked, setIsChecked] = useState(false); // 1번
//...
// 🟣 2번 코드와 같다.
const stateHookArray = useState(false); // 2번
const isChecked = stateHookArray[0];
const setIsChecked = stateHookArray[1];
}
[정리]
0. useState를 사용해 새로운 state를 선언할 때, 주로 배열([])로 구조 분해 할당하여 사용한다.
1. useState의 전달인자는 state의 초깃값이다. (useState의 전달인자는 하나만 전달할 수 있다.)
2. 다시 말해서 useState() 의 전달인자는 useState 가 반환하는 배열의 첫 번째 요소에 넘겨주는 초기 상태 값이다.
3. useState가 반환하는 배열의 두 번째 요소는 첫 번째 요소인 state를 갱신할 수 있는 함수이다.
[대괄호가 의미하는 것은?]
공식 문서 자바스크립트 문법의 "배열 구조 분해"라는 특별한 방법으로 변수를 선언했다.
0이나 1로 배열에 접근하는 것보다, 구조 분해 할당으로 변수 이름을 정해주는 것이 더 가독성이 좋다.
의사 코드
const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);
실제 코드
- isChecked : state를 저장하는 변수
- setIsChecked : state isChecked 를 변경하는 함수
- useState : state hook
- false : state 초깃값
function CheckboxExample() {
const [isChecked, setIsChecked] = useState(false);
// const [state 저장 변수, state 갱신 함수] = useState(state 초깃값);
이 state 변수에 저장된 값을 사용하려면 JSX 엘리먼트 안에 직접 불러서 사용하면 된다. 여기서는 isChecked 가 boolean 값을 가지기 때문에 true or false 여부에 따라 다른 결과가 보이도록 삼항연산자를 사용한다.
<span>{isChecked ? "Checked!!" : "Unchecked"}</span>
state 갱신하기
- state를 갱신하려면 state 변수를 갱신할 수 있는 함수인 setIsChecked 를 호출한다.
- 이번 예시의 경우, input[type=checkbox] JSX 엘리먼트의 값 변경에 따라서 isChecked 가 변경되어야 한다. 브라우저에서 (checked로 값이 변경되었다면, React의 state인 isChecked 값도 변경되어야 함)
- input[type=checkbox] 엘리먼트의 값이 변경되면 onChange 이벤트가 발생하고, 이벤트 핸들러 함수가 작동되는 패턴은 DOM을 유효성 검사에서 input[type=text] 엘리먼트의 값이 변경될 때, 이벤트 핸들러 함수를 작동시키는 패턴을 생각하면 쉽다.
- React도 마찬가지로 사용자가 체크박스 값을 변경하면 onChange 이벤트가 이벤트 핸들러 함수인 handleChecked 를 호출하고, 이 함수가 setIsChecked 를 호출하게 된다. setIsChecked 가 호출되면 호출된 결과에 따라 isChecked 변수가 갱신되며, React는 새로운 isChecked 변수를 CheckboxExample 컴포넌트에 넘겨 해당 컴포넌트를 다시 렌더링 한다.
[handleChecked을 부른 다음에 setIsChecked를 불러줘야 하는 이유 (아래에도 자세히 설명이 나온다.)]
아래를 보면 6번 줄에 이미 setIsChecked가 선언되고 값이 할당되었기 때문에
8번처럼 함수선언을 할 수 없다. (당연한 말이긴 하다..) 새로운 함수 안에 setIsChecked를 넣어서 호출해야 한다.
Cannot assign to read only property 'message' of object 'SyntaxError: /src/App.js: Identifier 'setIsChecked' has already been declared. (8:9)
6 | let [isChecked, setIsChecked] = useState(false);
7 |
> 8 | function setIsChecked(event) {
| ^
9 | (event.target.checked);
10 | };
11 |'
완성
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 컴포넌트는 state가 변경되면 새롭게 호출되고, 리렌더링된다.
- 아래 예시의 체크박스를 눌러보시면, 누를 때마다 콘솔에 "rerendered?" 가 찍히는 것을 확인할 수 있다. 즉, 컴포넌트의 상태가 변경될 때마다 새롭게 호출되고, 리렌더링 된다.
- React state는 상태 변경 함수 호출로 변경해야 한다. 강제로 변경을 시도하면 안 된다. 상태 변경 함수 사용은 React와 개발자의 약속이기 때문에 강제로 변경을 시도하면, 리렌더링이 되지 않는다거나, state가 제대로 변경되지 않는다.
- 예시 : state.push(1);, state[1] = 2;, state = 'wrong state'; (쓰면 안됨!!!!!)
'FE > React' 카테고리의 다른 글
[React] React 데이터 흐름 (0) | 2023.03.24 |
---|---|
[React] React의 이벤트 처리(Event handling) : state 다양한 응용 (0) | 2023.03.24 |
[React] React로 Router 기능을 담은 SPA 구현해보기 + useNavigate (0) | 2023.03.23 |
[React] React Router로 간단한 메뉴 페이지 구현하기 (0) | 2023.03.23 |
[React] React SPA (0) | 2023.03.23 |