1. Props
컴포넌트의 속성(property)를 의미한다. 변하지 않는 외부로부터 전달받은 값이며 웹 애플리케이션에서 해당 컴포넌트가 가진 속성들의 객체이다.
1) 특징
- 부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값이다.
props를 함수의 전달인자처럼 전달받아서 이를 기반으로 화면에 어떻게 표시되는지를 기술하는 리액트 엘리먼트를 반환한다. 컴포넌트가 최초로 렌더링 될때 화면에 출력하려는 데이터를 담은 초기값으로도 사용할 수 있다. - 객체 형태이며, 어떠한 타입의 값도 전달할 수 있다.
- 읽기 전용이다.
외부로부터 전달받는 변하지 않는 값이어야 함으로 함부로 변경되지 않도록 읽기 전용(read-only) 객체이다.
만약 읽기 전용 객체가 아닐경우 props가 의도치 않게 수정되면서 상위 컴포넌트들에 영향을 미칠 수 있다.
=> 의도치 않은 side effect 들이 발생하고 리액트의 단방향/하향식 데이터 흐름 원칙에 위배된다.
2) 사용방법
- 하위 컴포넌트에 전달하고자 하는 값(data)와 속성을 정의한다. 값은 중괄호를 사용해 감싸준다.
- 함수에 인자를 전달하듯이 컴포넌트에 props를 전달함으로 정의된 값과 속성을 전달한다.
- 하위 컴포넌트에서 전달받은 props를 렌더링한다. 객체의 value에 접근하듯이 dot notation을 사용하면 된다.
import Hello from "./Hello";
// 상위 컴포넌트
const App = () => {
return (
<div>
<Hello name="Harry" />
// 속성 = "값" 형태로 전달하고싶은 props의 값을 넣어준다
</div>
);
}
// 하위 컴포넌트
const Hello = (props) => {
return <div>Hello. My name is {props.name}. </div>
}
props는 객체 형태로 전달되기 때문에 비구조화 할당으로 작성하면 훨씬 코드를 간결하게 작성할 수 있다.
아래 코드는 위의 예시와 같은 결과가 출력된다.
const Hello = ({name}) => {
return <div>Hello. My name is {name}. </div>
}
(1) defaultProps
상위 컴포넌트에서 속성값을 전달받지 못하면 해당 값은 undefined가 된다.
이처럼 props의 값이 전달되지 않았을때 defaultProps 속성을 사용해 값이 없을때 나타낼 기본값을 정해줄 수 있다.
// props의 값이 전달되지 않았을때 나타낼 기본값
Hello.defaultProps = {
name: '이름없음'
}
(2) props.children
여는 태그와 닫는 태그 사이에 value를 넣어서 props를 전달하거나 컴포넌트 태그 사이에 넣은 값을 조회할때 사용한다.
<Wrapper>
<User />
</Wrapper>
// 위와 같은 구조일때 User 컴포넌트의 내용이 보여지게 하기 위해서
// Wrapper.js에서 아래처럼 사용
const Wrapper = ({children}) => {
return <div>{children}</div>
}
// 또는 값만 넣어서 사용할수도 있음
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> // I'm the eldest child
</div>
);
};
2. State
컴포넌트 내부에서 변할 수 있는 값이다.
1) useState
리액트에서 제공하는 state를 다룰 수 있는 함수이다. 상태 유지값과 그 값을 갱신하는 함수를 반환한다.
(1) 사용방법
불러오기
import { useState } from "react";
useState를 컴포넌트 안에서 호출한다. 일반적인 변수는 함수가 끝날때 사라지지만 state 변수는 함수가 끝나도 사라지지 않는다. useState의 기본 형태는 아래와 같다.
const [state 저장 변수, state 갱신 함수] = useState(초기값);
useState를 호출하면 배열을 반환한다. 배열의 0번째 요소는 현재 state변수이고, 1번째 요소는 현재 state 변수를 갱신할 수 있는 함수이다. useState의 인자로는 state의 초기값을 넘겨준다.
원래는 이렇게 작성해야 하지만,
const msgState = useState('');
const msg = msgState[0];
const setMsg = msgState[1];
구조분해 할당을 통해 아래처럼 간단하게 사용할 수 있다.
const [msg, setMsg] = useState('');
state 변수에 저장된 값은 JSX 엘리먼트 내부에 직접 불러서 사용할 수 있다.
state 갱신하기
setState 함수를 불러서 인자로 변경될 값을 넣어주면 된다.
import React, { useState } from 'react';
function MsgForm() {
const [msg, setMsg] = useState('');
const onChange = (e) => {
setMsg(e.target.value); // input에 입력되는 값으로 msg의 상태가 갱신된다.
};
const onReset = () => {
setText(''); // 버튼이 클릭되면 msg를 빈 문자열로 초기화시킨다
};
return (
<div>
<input onChange={onChange} value={msg} />
<button onClick={onReset}>초기화</button>
<div>
<b>값: {msg}</b>
</div>
</div>
);
}
(2) 주의점
리액트 컴포넌트는 state가 변경되면 새롭게 호출되고 리렌더링된다.
단, state를 변경할때는 꼭 setState, 즉 상태 변경 함수를 통해 변경해야만 한다.
state를 setState가 아닌 다른 방식으로 강제로 변경하려고 할 경우 리렌더링이 되지 않거나 state가 제대로 변경되지 않는등의 부작용이 발생할 수 있다.
3. 이벤트 핸들링
- 이벤트를 설정할 요소에 on이벤트이름={실행하고싶은함수} 형태로 설정해준다.
- 소문자 대신 카멜 케이스 사용한다.
- JSX 이용해서 문자열이 아닌 함수로 이벤트 핸들러를 전달한다.
// !주의! : 함수 형태로 넣어줄것. 함수를 실행하면 안된다.
<button onClick={alertEvent()}>Event</button> // X
<button onClick={alertEvent}>Event</button> // O
이벤트는 함수형태로 넣어주되 함수를 실행하면 안되는데, 이렇게 하면 컴포넌트가 렌더링 되는 시점에서 함수가 호출되어버리고 함수가 호출되는 것이 아닌 함수를 호출한 결과가 적용되어 이벤트가 제대로 적용되지 않는다.
따라서 이벤트에 함수를 전달할때는 리턴문 안에서 함수를 정의하거나, 리턴문 외부에서 함수를 정의하고 이벤트에 함수 자체를 전달해야 한다.
// 함수 정의
return (
<button onClick={() => alert('이벤트 발생')}>Event</button>
)
// 함수 자체 전달
const alertEvent = () => {
alert('이벤트 발생')
}
return (
<button onClick={alertEvent}>Event</button>
)
4. Controlled Component
input, textarea, select와 같은 폼 요소들은 일반적으로 사용자의 입력을 기반으로 태그 스스로가 자신의 state를 관리하고 업데이트 한다. 하지만 React에서는 state가 일반적으로 컴포넌트의 state 속성에 기반해 setState() 함수를 통해서 업데이트 된다. 이것은 React의 설계 원칙인 '신뢰 가능한 단일 소스(Single Source of Truth)'와는 다르다.
따라서 React에서 해당 요소들의 state를 제어할 수 있도록 만들어야 한다. 이러한 방식을 제어 컴포넌트(Controlled Component)라고 한다.
예제를 통해 설명하자면 아래와 같다.
import React, { useState } from 'react';
function MsgForm() {
const [msg, setMsg] = useState('');
const onChange = (e) => {
setMsg(e.target.value); // input에 입력되는 값으로 msg의 상태가 갱신된다.
};
const onReset = () => {
setText(''); // 버튼이 클릭되면 msg를 빈 문자열로 초기화시킨다
};
return (
<div>
<input onChange={onChange} value={msg} />
<button onClick={onReset}>초기화</button>
<div>
<b>값: {msg}</b>
</div>
</div>
);
}
input에 사용자가 내용을 입력하면 onChange 이벤트가 발생한다.
onChange 이벤트를 통해 setMsg 함수가 실행되고 state 변수인 msg의 상태가 업데이트된다.
그리고 업데이트된 msg는 렌더링을 통해 input의 value 속성에 들어가게 된다.
5. React의 데이터 흐름
컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 인자나 속성처럼 전달받는다.
데이터를 전달하는 주체는 부모(상위) 컴포넌트임으로 React의 데이터 흐름은 하향식(top-down)이라는 것을 알 수 있다.
리액트는 단방향 데이터 흐름(One-way data flow)를 따름으로 컴포넌트는 props를 통해 전달받은 데이터가 어디에서 왔는지 모른다. 그래서 개발을 하기 전에 각 컴포넌트에서 필요한 데이터가 무엇인지 먼저 정의해야 한다.
모든 데이터를 상태로 정할 필요는 없다. 상태는 가능한 최소화 하는것이 좋다.
해당 데이터를 상태로 둘지 결정하기 전에 아래의 질문들을 생각해보아야 한다.
- 이 데이터는 부모로부터 props를 통해 전달되나? => state X
- 이 데이터는 시간이 지나도 변하지 않는가? => state X
- 이 데이터는 컴포넌트 안의 다른 state나 props로 계산이 가능한가? => state X
state는 특정 컴포넌트에서만 사용될 경우 특정 컴포넌트에만 두면 된다.
하지만 하나의 state에 여러개의 컴포넌트가 영향을 받을 경우, 해당하는 컴포넌트들의 공통 부모 컴포넌트를 찾아 거기에 state를 두어야 한다.
*****************
요약정리 & 일일회고
state는 컴포넌트 내부에서 사용되는 변경이 가능한 값이다. useState Hook을 사용해 관리할 수 있다.
props는 컴포넌트 외부에서 전달되는 변경이 불가능한 값이다. 상위 컴포넌트에서 하위 컴포넌트로만 전달할 수 있다. 객체형태로 전달된다.
useState는 리액트에서 import를 통해 불러와서 사용할 수 있다. useState를 호출하게 되면 객체형태를 반환하는데, 0번째 요소는 현재 state를 나타내는 변수이고 1번째 요소는 state의 상태를 변경할 수 있는 함수가 들어있다. 이는 구조분해 할당을 통해 더 간결하게 사용할 수 있다. setState가 아닌 다른 방식으로 state를 변경하려고 하면 리렌더링이 되지 않거나 오류가 발생하기 때문에 state는 반드시 setState로 변경해주어야 한다.
props는 부모 컴포넌트에서 원하는 속성명과 값을 속성명 = 값 형태로 지정해 주고 자식 컴포넌트에서 props 객체를 함수의 인수처럼 넣어줌으로 받아 올 수 있다. props는 객체형태임으로 구조분해 할당으로 속성들을 가져오면 훨씬 간결하게 코드를 작성할 수 있다. 기본값이 필요할 경우 defaultProps 속성을 사용할 수 있고 컴포넌트의 여는태그와 닫는 태그 사이에 값을 넣어 props.children으로 조회하고 가져올수도 있다.
리액트의 이벤트 핸들러들은 on이벤트이름={함수} 형태로 설정할 수 있다. 주의할 점은 함수 자체를 가져오거나 리턴문 안에서 함수를 선언해야 하고, 중괄호 안에서 함수를 호출해주면 안된다는 것이다. 함수를 호출할 경우 렌더링시에 함수가 실행된 결과값을 받아오게 됨으로 원하는대로 이벤트를 조작할 수 없게 된다.
컴포넌트를 활용할때 변하지 않는 값은 props로 변해야 하는 값은 state로 판단할 수 있다.
state를 설정할 경우, 이 값을 부모 컴포넌트에서 props로 받아올 수 있는지, 시간이 지나도 변하는 값이 아닌지, 컴포넌트 안의 다른 state나 props로 계산이 가능한지 확인 한 후에 설정하는 것이 좋다. state는 가급적 적게 설정하는것이 좋기 때문이다.
리액트는 단방향 데이터 흐름을 따름으로 위에서 아래로 데이터가 전달된다. props가 부모에서 자식컴포넌트로만 전달이 가능한 것을 보면 알 수 있다.
****
Props, State, 이벤트 핸들링, controlled component, 데이터 흐름에 대해 공부했다.
어제 조금 남아서 미리 예습을 했더니 더 쉽게 알 수 있었다.
그래도 예제를 만들어보고 연습을 했더니 이해도 숙지도 조금 빠르게 된거같다.
props가 위에서 아래로만 전달되는 것을 꼭 기억해야겠다.
또 무엇을 state로 설정해야 하는지, 무엇을 props로 전달해야 하는지는 아직 잘 모르겠다.
두가지를 판단하기 전에 많이 생각해봐야겠다.
'study > TIL' 카테고리의 다른 글
23.01.31 - REST API (2) | 2023.01.31 |
---|---|
23.01.30 - 네트워크 기초 지식 (0) | 2023.01.30 |
23.01.25 - React SPA, React Router (2) | 2023.01.25 |
23.01.20 - React 기초 (0) | 2023.01.20 |
23.01.19 - fetch, axios (0) | 2023.01.19 |