리액트의 특징 중 하나는 데이터는 위에서 아래로
하향식(top-down) 흐름
을 갖는다는 것이다. 데이터를 전달하는 주체는 부모컴포넌트이고, 컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못한다. 이러한단방향 데이터 흐름(one-way data flow)
키워드는 React를 대표하는 설명이 되기도 하며 중요하다.
yes! 🙌
어떤state
가 두 컴포넌트에 영향을 준다면, 공통 소유 컴포넌트(공통 부모!)를 찾아 그 곳에 상태를 위치치켜준다.
state
는 언제 쓰지?
- 부모로부터
props
를 통해 전달되나? 🙅♀️- 시간이 지나도 변하지 않나? 🙅♀️
- 컴포넌트 안의 다른
state
나props
를 가지고 계산이 가능한가? 🙅♀️위 경우가 아닐 때 사용한다.👌
단방향 데이터 흐름 원칙에 따르면, 자식 컴포넌트는 부모컴포넌트를 바꿀 수 없다. 하지만 자식컴포넌트에서 버튼을 클릭하고, 이 버튼클릭으로 부모 컴포넌트의 상태를 바꾸고 싶다면? 이러한 문제를 부모 컴포넌트의 상태변경함수를 하위로 전달해서 실행시키는 방법(이것은 마치
콜백함수
!),State 끌어올리기(Lifting state up)로 해결할 수 있다.그냥 뭉뚱그려 같이 쓰면 안되나? 라고 생각할 수도 있겠지만, 리액트는 컴포넌트 단위로 시작해 페이지를 조립해 나가는 상향식으로 앱을 만들고, 이 컴포넌트는 각각 단일 책임 원칙으로 하나의 일만 하도록 되어있다. 그렇게 해야 테스트가 쉽고 확장성, 유지 보수하기가 좋기 때문이다. 이렇게 컴포넌트 단위로 쪼개기 때문에 위와 같은 일이 빈번히 발생할 수 있다.
yes! 👍 위의 예시를 만들어볼까?
import React, { useState } from "react"; export default function ParentComponent() { const [value, setValue] = useState("초깃값을 바꿔봅시다."); const handleChangeValue = (newValue) => { // 상태변경함수 setValue(newValue); }; return ( <div> <div>현재 값은 {value} 입니다.</div> <ChildComponent handleButtonClick = {handleChangeValue}/> // 상태변경함수 전달 </div> ); }
우선 위와 같이 부모 컴포넌트에서
state
를 위치시키고, 상태변경함수handleChangeValue
를 만들어준다. 그리고 리턴에서,handleButtonClick
으로 이 상태변경함수를 전달해 준다.function ChildComponent({handleButtonClick}) { const handleClick = () => { handleButtonClick("자식 컴포넌트에서 값을 바꿨습니다!") }; return <button onClick={handleClick}>값 변경</button>; }
그리고 자식 컴포넌트에서 위 상태변경함수를
props
로 받아 클릭이벤트가 발생하면 이 함수를 콜백해 값을 인자로 넣어 실행시킬 수 있다.
함수 내의 구현이 외부에 영향을 끼치는 경우를 말한다.
React 컴포넌트에서
Side Effect
- 타이머 사용하기 :
setTimeout
- 데이터 가져오기 :
fetch API
,localStorage
yes! 👏
비동기 요청의 대표적인 예로 맨 아래의fetch
를 처리할 수 있다.
useEffect(함수, [조건1, 조건2, ...])
: useEffect의 첫번째 인자는 실행할 함수, 두번째 인자는 종속성 배열(dependency array)이다. 종속성 배열은 조건으로 넣은 값의 변경이 일어나야 함수를 실행한다는 조건인데, 이 조건들의 목록을 담고 있다. 쉽게 말해 이 조건값이 바뀌어야 함수가 실행된다는 것이다. (여러state
를 넣을 수 있다.)
useEffect(함수)
: 기본형태로, 조건이 없는 useEffect는 컴포넌트가 처음 생성되거나, props가 업데이트되거나, 상태(state)가 업데이트될 때 함수가 실행된다.useEffect(함수, [])
: 빈배열을 조건으로 갖는 경우, 컴포넌트가 처음 생성될때만 effect 함수가 실행된다. (예. 처음 딱 한 번, 외부 API로 리소스를 받아온 뒤 이후로 API 호출이 필요하지 않을 때)처음 조금 어리둥절 할 수 있는데, 상태가 하나만 있다면 조건을 걸지 않아도 된다. 하지만 만약 상태가 하나 이상이라면? 조건값을 걸어주지 않으면 모든 상태가 바뀔 때마다 이 useEffect 함수가 돌아갈 것이다. (이건 너무 불필요하다!)
export default function App() { const [text, setText] = useState("") const [count, setCount] = useState(0); useEffect(() => { console.log("언제 effect 함수가 불릴까?"); }, [count]); return ( <div> <label>text: </label> <input onChange={(e) => setText(e.target.value)} value={text}></input> <button onClick={() => setCount(count + 1)}>카운터 값: {count}</button> </div> ); }
만약 위의
useEffect
에서 조건[count]
를 걸어주지 않는다면,input
의text
내용이 바뀔 때에도,button
이 눌릴 때에도 모두effect 함수
가 돌아가 콘솔에 계속 찍힐 것이다. 하지만[count]
를 조건으로 걸어준다면, 오직count
의 상태가 바뀔 때에만 실행되는 것을 볼 수 있다.
네트워크 요청과 데이터 처리 과정이 길 경우, 로딩화면은 필수적이다!
(그냥 빈 화면을 덩그러니 쳐다보고 있는 자신을 생각해 보자. 너무 답답해 🤦♀️)const [isLoading, setIsLoading] = useState(false) // 생략... return {isLoading ? <로딩화면 컴포넌트 /> : <div>로딩 완료 화면</div>}
이렇게 위와 같이 상태에 따라 로딩화면을 보여줄 지, 완료된 화면을 보여줄 지 만들어줄 수 있다.
useEffect(() => { setIsLoading(true); // 로딩화면을 렌더링하고, fetch(url) .then(response => response.json()) .then(result => { 상태변경함수(result); // 서버에서 데이터를 요청하고 받아오면 상태를 변경시킨다 setIsLoading(false); // 이후 로딩 완료 화면을 렌더링하도록 로딩상태를 변경시킨다 }); }, [url]); // 조건에 url을 넣어 url이 바뀔 때마다 실행한다
혹은 이렇게 시간이 오래 걸릴 수 있는 fetch 요청 전후로 로딩화면 상태를 변화시켜 주면서 더 나은 사용자 경험을 줄 수도 있다.