
React는 위에서 아래로, 상위 컴포넌트에서 하위 컴포넌트로 흐르는 단방향식(또는 하향식) 데이터 흐름을 따른다.
종종 하위 컴포넌트에서 상위컴포넌트의 State를 변경해야 하는 경우가 생긴다. 이런 경우 상태 끌어올리기를 하는데, 리액트는 하향식 데이터 흐름을 따르는데 어떻게 이런 일을 할 수 있는 것일까?
상위컴포넌트의 State 변경 함수를 하위 컴포넌트의 props를 통해 내려주고,
하위 컴포넌트에서 State 변경 함수를 실행하면 상위 컴포넌트의 State를 변경할 수 있게 된다. 이것을 State(상태) 끌어올리기라고 한다.
import React, { useState } from "react";
function ParentComponent() {
// State 생성
const [value, setValue] = useState("날 바꿔줘!");
// State 변경 함수를 실행하는 함수
const handleChangeValue = (변경할 값) => {
setValue("보여줄게 완전히 달라진 값");
};
return (
<div>
<div>값은 {value} 입니다</div>
// ChildeComponent의 속성과 값을 정의
<ChildComponent handleButtonClick={hadleChageValue} />
</div>
);
}
// State변경함수를 props로 내려줌
// const props = {handleButtonClick: handeChageValue함수}
function ChildComponent(props) {
const handleClick = () => {
props.handleButtonClick(변경할 값)
// 실행시 상태끌어올리기로 인해 State 변경됨
};
// 버튼 클릭시 handleClick 실행
return <button onClick={handleClick}>값 변경</button>;
}
함수 내부의 구현이 함수 외부에 영향을 미치는 경우 해당 함수는 Side Effect가 있다고 한다. React에서는 컴포넌트 내에서 fetch를 이용한 API 요청으로 리소스를 받아오거나, 이벤트를 활용해 DOM을 직접 조작할 때 Side Effect가 발생했다고 한다.
Side Effect가 없는 함수를 의미한다. 순수 함수는 함수의 입력값만이 함수의 결과에 영향을 주고, 입력값을 수정하지 않는 함수이다. 순수 함수는 입력값이 있을 때 항상 같은 값이 리턴됨을 보장하기 때문에 출력값을 예상할 수 있다.
리액트의 함수 컴포넌트의 입력값은 props이고, 어떤 Side Effect도 없으며 순수 함수로 작동한다.
function PureFunc({ a, b, c }) {
return (
<div>
<div>{a}</div>
<div>{b}</div>
<div>{c}</div>
</div>
)
}
하지만 보통 리액트를 이용해 애플리케이션을 만들 때, AJAX 요청이 필요하거나 LocalStorage, 타이머와 같은 리액트와 상관없는 API를 사용하는데, 리액트의 입장에서는 이 것들은 모두 Side Effect이다. 그렇기 때문에 리액트는 Side Effect를 다루기 위한 Effect Hook을 제공한다.
React 컴포넌트에서의 Side Effect
- 타이머 사용 (setTimeout)
- 데이터 가져오기 (fetch API, LocalStorage)

useEffect의 가장 기본 형태는 주어진 조건이 없을 때이다. useEffet(함수)와 같이 조건이 없는 기본형태일 때는 컴포넌트가 재렌더링 될 때마다 실행된다.
useEffect(함수, [종속성1, 종속성2, ...])의 첫번째 인자는 함수, 두번째 인자는 언제 실행될지 조건을 담은 배열이다. 이때 조건은 boolean 형태의 표현식이 아닌, 어떤 값의 변경이 일어날 때를 의미한다. 따라서 배열에는 이 어떤 값이 들어간다. 이러한 배열을 종속성 배열(Dependency Array)이라고 부른다. 종속성이 여러개일 경우, 그 중 하나가 변할 때 첫번째 인자인 함수가 실행된다.
조건이 있는 경우
처음 렌더링 될때 콜백이 실행되고 값의 변경이 생길때마다 실행된다.
종속성 배열이 빈 배열일 경우
컴포넌트가 처음 생성될 때, 딱 1번만 effect 함수가 실행 된다.
Hook을 쓸 때 주의할 점
- 최상위에서만 Hook을 호출합니다.
- React 함수 내에서 Hook을 호출합니다.
import { useEffect, useState } from "react";
const [proverbs, setProverbs] = useState([]);
const [filter, setFilter] = useState("");
function App(){
// filter 스테이트가 변경될 때 마다 useEffect 실행
useEffect(() => {
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
});
}, [filter])
return (
<div className="App">
// 요청 URI에 따라 명언을 보여주는 JSX 코드
</div>
);
}

요청에 대한 응답이 느릴 경우를 고려해 위와 같은 로딩 화면이 필요하다. 늘 즉각적인 응답을 기대하기는 어렵기 때문에 로딩 화면은 필수적이다.
로딩 화면은 응답 여부에 따라 보여주기 때문에 useState와 삼항연산자를 이용한 조건문 표현식을 이용하여 구현할 수 있다.
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
fetch 요청의 전후로 setIsLoading을 설정해주면, 리소스를 받아오기 전까지는 isLoading이 true이므로 로딩화면을 보여주고, 리소스를 받아온 후에는 setIsLoading에 의해 isLoading이 false가 되므로 보여주고자 하는 화면을 보여주게된다.
useEffect(() => {
setIsLoading(true);
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
setIsLoading(false);
});
}, [filter]);
