React는 페이지 단위가 아닌 컴포넌트 단위로 개발된다. 그리고 하나의 컴포넌트는 하나의 일만 한다. React의 데이터는 하향식(top-down)으로 부모 컴포넌트에서 자식 컴포넌트로 전달되는데, 이를 가리켜 "React는 단방향 데이터 흐름(One-way data flow)을 따른다"고 이야기한다. 이는 React의 가장 큰 특성 중 하나이다.
- 부모로부터 props를 통해 전달되는가?
- 시간이 지나도 변하지 않는가?
- 컴포넌트 안의 다른 state나 props를 가지고 계산이 가능한가?
위의 세 질문에 하나라도 Yes가 나온다면 state가 아니다. state는 최소한으로 사용하기 !!!
React의 데이터는 위에서 설명했듯이 하향식의 단방향으로 흐르지만, 가끔 하위 컴포넌트의 이벤트나 상태가 상위 컴포넌트에 영향을 미치는 경우가 있다. 이는 단방향 데이터 흐름의 특성을 거스르는 일이다. 때문에 React는 이 특성을 지키기 위해 한 가지 방법을 고안해냈다. 바로 상위 컴포넌트의 '상태를 변경하는 함수' 자체를 하위 컴포넌트의 props로 전달하고 하위 컴포넌트가 이 함수를 실행하는 것이다. 이를 "상태 끌어올리기"라고 부른다.
위에서 설명했듯이 상위 컴포넌트에서 정의한 상태 변경 함수를 하위 컴포넌트에 인자로 전달하고, 하위 컴포넌트에서 실행시켜 상위 컴포넌트의 상태를 변화시키기 때문에 두 컴포넌트는 엄연히 다르다. 아래는 상태 끌어올리기의 예시이다.
function ParentComponent() { const [value, setValue] = useState("날 바꿔줘!"); const handleChangeValue = () => { setValue("보여줄게 완전히 달라진 값"); }; return ( <div> <div>값은 {value} 입니다</div> <ChildComponent handleButtonClick={handleChangeValue} /> </div> ); } function ChildComponent({ handleButtonClick }) { const handleClick = () => { handleButtonClick() } return ( <button onClick={handleClick}>값 변경</button> ) }
Side effect(부수 효과)는 함수 내부의 어떤 구현이 함수 외부에 영향을 미치는 것을 이야기한다.
side effect가 일어나지 않는 함수, 즉 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수를 Pure Function(순수 함수)라고 한다. 순수 함수는 입력으로 전달 된 갑을 수정해서도 안된다.
React에서의 Side effect는 타이머(setTimeout)와 데이터 가져오기(fetch API, localStorage)가 있다.
Yes ! useEffect 함수는 side effect를 실행할 수 있게 하는 Hook이다. 실행되는 조건은 다음과 같다.
- 컴포넌트 생성 후 처음 화면에 렌더링
- 컴포넌트에 새로운 props가 전달되며 렌더링
- 컴포넌트에 상태(state)가 바뀌며 렌더링
즉, 컴포넌트가 새롭게 렌더링 될 때 Effect Hook이 실행된다는 것을 알 수 있다.
❗ Hook 사용 시 주의할 점 ❗
- 최상위에서 호출하기
- React 함수 내에서 호출하기
useEffect의 기본 형식은 다음과 같다.
useEffect(함수, [종속성1, 종속성2, ...])
useEffect의 두번째 인자를 종속성 배열(dependency array)이라고 부른다. 종속성 배열에는 어떤 값의 변경이 일어나는 조건이 담겨있다. '어떤 값'의 목록이라고 할 수 있다. 즉 배열 내의 어떤 값이 변했을 때 effect 함수가 실행된다.
AJAX 요청 시 외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)을 구현해 놓아야 한다. 여기서도 상태 처리가 사용된다.
const [isLoading, setIsLoading] = useState(true); return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
상태를 설정한 후 아래처럼 useEffect에서 사용할 수 있다.
useEffect(() => { setIsLoading(true); fetch(`http://서버주소/proverbs?q=${filter}`) .then(resp => resp.json()) .then(result => { setProverbs(result); setIsLoading(false); }); }, [filter]);