🔸 React는 단방향 데이터 흐름을 가짐.
🔸 하지만 하위컴포넌트에서 발생한 상태변경 함수가 상위컴포넌트의 상태를 변경시키는 경우가 존재할 수 있는데 이를 역방향 데이터 흐름이라고 함.
예를 들어, 하위컴포넌트인 NewTweetForm에서 클릭 이벤트가, 부모의 상태(전체 트윗목록 존재)를 바꿔야하는 상황. 이때 state끌어올리기를 이용
🔸 이러한 역방향 데이터 흐름은 state 끌어올리기를 통해 상태를 변경시키는 함수(handler)를 하위 컴포넌트에 props로 전달해서 해결할 수 있음.
🔸 상위 컴포넌트의 "상태를 변경하는 함수"
그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행하는 것.
- 상태변경함수
handleChangeValue
를 props를 이용해 하위 컴포넌트에 전달.function ParentComponent() { const [value, setValue] = useState("날 바꿔줘!"); const handleChangeValue = () => { setValue("보여줄게 완전히 달라진 값"); }; return ( <div> <div>값은 {value} 입니다</div> <ChildComponent handleButtonClick={handleChangeValue} /> </div> ); }
<ChildComponent>
는 고차 함수가 인자로 받은 함수를 실행하듯, props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 됨."상태 변경 함수"
는 버튼이 클릭할 때 실행되기를 원하므로, 해당 부분에 콜백 함수를 실행.function ChildComponent({ handleButtonClick }) { const handleClick = () => { handleButtonClick() } return ( <button onClick={handleClick}>값 변경</button> ) }
- 설정할 값을 콜백 함수의 인자로 넘기는 경우
function ParentComponent() { const [value, setValue] = useState("날 바꿔줘!"); const handleChangeValue = (newValue) => { setValue(newValue); }; // ...생략... } function ChildComponent({ handleButtonClick }) { const handleClick = () => { handleButtonClick('넘겨줄게 자식이 원하는 값') } return ( <button onClick={handleClick}>값 변경</button> ) }
🔸 함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 현상.
🔸 React에서는 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오거나 이벤트를 활용해 DOM 직접 조작할 때 Side Effect가 발생했다고 말함.
예시)
let foo = 'hello'; function bar() { foo = 'world'; } bar(); // bar는 Side Effect를 발생시킴
🔸 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수.
🔸 입력으로 전달된 값(원본)을 수정하지 않음 (Immutable)
🔸 네트워크 요청과 같은 Side Effect가 없으며, 어떠한 전달 인자가 주어질 경우, 항상 똑같은 값이 리턴됨을 보장.
🔸 예측 가능한 함수.
💡
Math.random()
은 순수 함수가 아님.
Math.random()
은 0 이상 1 미만의 난수를 반환해 호출할 때마다 다른 난수가 반환되기 때문에 동일한 입력에 대해 항상 동일한 출력을 반환하지 않음.- 따라서 이러한 불확실성 때문에
Math.random()
은 순수 함수의 조건을 만족하지 않음.
💡 어떤 함수가 fetch API를 이용해 AJAX 요청을 할 때, 이 함수는 순수 함수가 아님.
- 이유는 AJAX 요청은 외부의 상태나 데이터에 의존하며, 요청의 결과는 시간이 지남에 따라 달라지기 때문.
- 순수 함수는 입력에만 의존하고 외부의 상태를 변경하지 않으며, 항상 동일한 입력에 대해 동일한 출력을 반환해야 함. 그러나 AJAX 요청을 처리하는 함수는 외부 리소스에 의존하고, 요청의 결과는 서버의 응답 속도나 네트워크 상황에 따라 달라짐.
- 따라서 AJAX 요청을 처리하는 함수는 외부 상태에 영향을 주거나 예측할 수 없는 결과를 반환할 수 있기 때문에 순수 함수의 조건을 만족하지 않음.
🔸 React의 함수 컴포넌트는, props가 입력으로, JSX Element가 출력으로 나감. 여기에는 그 어떤 Side Effect도 없으며, 순수 함수로 작동.
🔸 하지만 보통 React 애플리케이션을 작성할 때에는, AJAX 요청이 필요하거나, LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우가 발생할 수 있음. 이는 React의 입장에서는 전부 Side Effect. React는 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공.
🔸React 컴포넌트에서의 Side Effect
🔸 useEffect
: 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook
🔸 다음과 같은 상황에서 실행됨.
🔸 최상위에서만 Hook 호출.
🔸 반복문, 조건문 혹은 중첩된 함수 내에서 Hook 호출 금지. (최상위 레벨에서 호출할 때만 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것을 보장함)
🔸 React 함수 내에서만 Hook을 호출.
🔸 공식 문서
useEffect(함수, [종속성1, 종속성2, ...])
🔸 useEffect
의 두 번째 인자는 배열이며 이 배열은 조건을 담고 있음. 배열 내의 종속성1, 또는 종속성2의 값이 변할 때, 첫 번째 인자의 함수
가 실행.
🔸 조건은 boolean 형태의 표현식이 아닌, 어떤 값의 변경이 일어날 때를 의미.
🔸 따라서 해당 배열엔 어떤 값의 목록이 들어감. 이 배열을 종속성 배열이라고 부름.
useEffect(함수, [])
🔸 종속성 목록에 아무런 종속성도 없다면(빈 배열[]
로 둘 경우), 컴포넌트가 처음 생성될 때만 함수
가 실행.
🔸 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용.
useEffect(함수)
🔸 아무것도 넣지 않을 때(기본 형태), 컴포넌트가 처음 생성되거나, props, 상태(state)가 업데이트될 때 effect 함수
가 실행.
🔸 목록 내 필터링을 구현하기 위해서는 다음과 같은 두 가지 접근이 존재.
1. 컴포넌트 내에서 필터링 (CSR) : 외부 API에서 데이터를 최초 1회 받아오고, filter 함수를 사용. HTTP 요청 빈도를 줄일 수 있음. 클라이언트의 메모리 상에 많은 데이터를 갖게 됨.
2. 컴포넌트 외부에서 필터링 (SSR) : 컴포넌트 외부로 API 요청을 할 때, 필터링한 결과를 받아오는 방법 (보통, 서버에 매번 검색어와 함께 요청하는 경우가 이에 해당). 검색어가 바뀔 때마다, 외부 API를 호출. HTTP 요청 빈도가 늘어남. 서버가 필터링을 처리하므로 서버가 부담을 가짐.
- fetch API를 이용해 서버에 요청
useEffect(() => { fetch(`http://서버주소/proverbs?q=${filter}`) .then(resp => resp.json()) .then(result => { setProverbs(result); }); }, [filter]);
filter
가 변경될 때마다 외부 API를 받아옴.
🔸 모든 네트워크 요청이 항상 즉각적인 응답을 가져다주는 것은 아님. 외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)의 구현은 필수적.
🔸useState
로 로딩의 상태 처리를 하고, fetch 요청 전후로 로딩 상태 변경 함수(true
/false
)를 설명해주어 보다 나은 UX를 구현.
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정합니다
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
🔸 웹 브라우저에 데이터를 저장할 수 있는 웹 스토리지 기술로 브라우저 세션을 종료하고 다시 열어도 데이터가 유지될 수 있음. 키-값
쌍으로 데이터를 저장, 데이터는 모두 문자열 형태로 저장.
🔸 localStorage.setItem("key", "value")
: 로컬 스토리지에 데이터 저장
🔸 localStorage.getItem("key")
: key
의 value
리턴
🔸 localStorage.removeItem('key')
: key
삭제
🔸 localStorage.clear()
: 로컬스토리지의 모든 데이터 삭제