1. React 데이터 흐름
1) React의 데이터 흐름 복습
(1) 컴포넌트 단위 개발
리액트는 페이지 단위가 아닌 컴포넌트 단위로 개발한다.
컴포넌트들은 단일 책임 원칙에 따라 하나의 컴포넌트가 하나의 일만 하도록 디자인한다.
그리고 만들어진 컴포넌트들을 조합해 페이지를 조립한다.
=> 즉, 상향식(bottom-up)으로 앱을 만든다. 테스트가 쉽고 확장성이 좋다는 장점이 있다.
(2) 데이터의 하향식(top-bottom) 흐름
컴포넌트는 외부에서 props를 이용해 데이터를 전달인자(arguments) 또는 속성(attributes)처럼 전달받을 수 있다.
컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 모른다.
=> 즉, 데이터를 전달하는 주체는 부모(상위) 컴포넌트이다. 데이터 흐름은 위에서 아래로, 하향식임을 알수있다.
(3) state와 props
코드를 작성하기 전에 애플리케이션에서 필요한 데이터가 무엇인지 정의해야 한다.
변하는 값 = > 상태(state)
변하지 않는 값 => props
✋ 데이터를 state로 둘때 생각해볼점
부모로부터 props를 통해 전달되는지 => state X
시간이 지나도 변하지 않는 값인지 => state X
컴포넌트 안의 다른 state나 props를 가지고 계산이 가능한지 => state X
(4) state의 위치
특정 컴포넌트에서만 유의미함 => 특정 컴포넌트에 둠
여러개의 컴포넌트가 하나의 상태에 영향을 받음 => 공통되는 상위 컴포넌트에 위치시킴
=> 여러개의 자식 컴포넌트가 하나의 상태에 접근할때는 공통 부모 컴포넌트에 데이터가 위치해야한다.
그러나 예를 들어 검색버튼을 눌렀을때 데이터 목록이 변하는 등, 하위 컴포넌트에서 발생시킨 이벤트로 상위 컴포넌트의 state를 변경시켜야 할때가 있다. 이때는 state 끌어올리기(Lifting state up)을 사용해야 한다. 즉, 역방향 데이터 흐름으로 부모 컴포넌트에서의 상태를 하위 컴포넌트에 의해 변경시키는 것이다.
2) state 끌어올리기(Lifting state up)
위에서 설명했듯이 상위 컴포넌트의 상태 변경 함수 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행함으로서 상위 컴포넌트의 상태가 변경되는것이 state 끌어올리기이다.
상위 컴포넌트에서 상태 변경 함수 자체를 하위 컴포넌트에 props로 전달해준다.
하위 컴포넌트는 해당 함수를 고차함수가 인자로 받은 함수를 실행하듯이 props로 전달받은 함수를 컴포넌트 내에서 실행한다. 필요할 경우 설정할 값을 콜백함수의 인자로 넘기기도 한다.
// 부모 컴포넌트
const Tweets = () => {
const [tweetList, setTweetList] = useState(data);
const deleteTweet = (id) => {
const deleteList = tweetList.filter((tweet) => tweet.id !== id);
setTweetList(deleteList); // 하위 컴포넌트에서 인자를 받아서 state 변경
};
return (
<div className="tweets">
// ...
<ul className="tweet-list">
{tweetList.map((tweet) => {
return (
<Tweet
tweet={tweet}
key={tweet.id}
deleteTweet={deleteTweet} // props로 하위 컴포넌트에 이벤트 전달
/>
);
})}
</ul>
</div>
);
}
// 하위 컴포넌트
const Tweet = ({ tweet, deleteTweet }) => {
// ...
return (
<li>
// ...
<div className="tweet-content">
<div className="tweet-info">
<span className="tweet-username">{tweet.username}</span>
<span className="tweet-date">{parseDate(tweet.createdAt)}</span>
<button onClick={() => deleteTweet(tweet.id)}>delete</button>
// 버튼이 클릭되면 deleteTweet 함수 호출하며 tweet.id를 인자로 넘김
</div>
</div>
</li>
);
};
2. Effect Hook
1) Side Effect
함수 내에서 어떤 구현이 외부에 영향을 끼치는 경우를 말한다.
React에서는 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오거나 이벤트를 활용해 DOM을 직접적으로 조작할때 Side Effect가 발생했다고 한다.
(1) Pure Function (순수 함수)
오직 함수의 입력(매개변수)만이 함수의 결과에 영향을 주는 함수.
함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치는 경우 순수 함수라고 부를 수 없다.
즉 순수 함수의 출력값에 영향을 미치는 작업들은 모두 Side Effect라고 할 수 있다.
또한 순수함수는 입력으로 전달된 값을 수정하지 않는다. 어떠한 전달 인자가 주어질 경우 항상 똑같은 값이 리턴된다.
=> 예측이 가능하다.
- Math.random()은 순수 함수가 아니다. 왜?
: Math.random()은 호출될때마다 0과 1 사이의 난수를 발생시킴으로 호출될때마다 똑같은 값이 리턴되지 않는다.
- 어떤 함수가 fetch API를 이용해 AJAX 요청을 한다. 이 함수는 순수 함수가 아니다. 왜?
: fetch는 특정한 url 에 요청을 해서 값을 받아온다. 따라서 네트워크 상황 등에 따라서 성공 또는 실패를 반환함으로 언제나 같은 결과가 반환되지 않는다.
(2) React의 함수 컴포넌트
React의 함수 컴포넌트는 props가 입력, JSX Element가 출력.
이 상태에서는 어떠한 side Effect도 없고 순수 함수로 작동한다.
하지만 AJAX 요청이 필요하거나 LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용해야하는 경우가 있다.
=> 이는 React 입장에서 볼때 모두 Side Effect이다. 그래서 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공한다.
만약 훅을 사용하지 않고 네트워크 요청을 하게 된다면 그동안 페이지가 멈추거나 깜빡일수있다.
2) Effect Hook 기본
(1) useEffect
useEffect(함수);
컴포넌트 내에서 Side Effect를 실행할 수 있게 하는 Hook.
useEffect의 첫번째 인자는 함수이고, 이 함수를 통해 side effect를 실행하면 된다.
(2) useEffect가 실행될때
- 컴포넌트 생성 후 처음 화면에 렌더링(표시) 될때
- 컴포넌트에 새로운 props가 전달되며 렌더링 될때
- 컴포넌트에 상태(state)가 바뀌며 렌더링 될때
(3) Hook 사용시 주의할 점
- 최상위에서만 Hook을 호출한다 (반복문이나 조건문, 혹은 중첩된 함수 내에서 호출해서는 안된다. )
=> react가 useState와 useEffect가 여러번 호출될때도 hook의 상태를 올바로 유지할 수 있도록 해준다. - React 함수 내에서 Hook을 호출한다
일반적인 자바스크립트 함수에서 호출해서는 안된다.
리액트 함수 컴포넌트, 또는 커스텀 훅에서 hook을 호출할 수 있다.
3) Effect Hook 조건부 실행
(1) 조건부 effect 발생 (dependency array)
useEffect(함수, [deps])
// 예시
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
useEffect의 두번째 인자는 배열이다. 이 배열은 조건을 담고있다. 조건은 어떤 값의 변경이 일어날때를 의미한다. 종속성 배열이라고 부르고 이 값에는 어떠한 값의 목록이 들어간다.
배열 내의 값이 변할때 첫번째 인자의 함수가 실행된다.
즉 배열 내의 어떤 값이 변할때에만 effect가 발생하는 함수가 실행되는 것이다.
(2) 단 한번만 실행되는 Effect 함수
종속성 배열목록(deps)에 아무런 종속성도 없다면 어떻게 될까?
- 종속성 배열을 빈 배열로 둘때
- 컴포넌트가 처음 생성될때만 effect 함수가 실행된다.
예) 처음 한번 외부 api를 통해 리소스를 받아오고 더이상 api 호출이 필요하지 않을때
- 컴포넌트가 처음 생성될때만 effect 함수가 실행된다.
- 아무것도 넣지 않을때
- useEffect가 컴포넌트가 처음 생성되거나 / props가 업데이트 되거나 / state가 업데이트 될때 실행된다.
4) 컴포넌트 내에서 Ajax 요청하기
예시) 목록 내에서 필터링 하기
1. 컴포넌트 내에서 필터링한다 => 전체 목록 데이터를 불러오고 목록을 검색어로 filter하는 방법
장점 : http 요청의 빈도 줄일수있음
단점 : 브라우저(클라이언트)의 메모리 상에 많은 데이터를 갖게 됨으로 클라이언트의 부담이 늘어난다
2. 컴포넌트 외부에서 필터링 => 컴포넌트 외부로 api 요청을 할때 필터링한 결과를 받아오는 방법
(서버에 매번 검색어와 함께 요청하는 경우가 이에 해당함)
장점 : 클라이언트가 필터링 구현을 생각하지 않아도 됨
단점 : 빈번한 http 요청이 일어나게 되며 서버가 필터링을 처리함으로 서버가 부담을 가져간다
AJAX 요청 보내기
useEffect(() => {
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
});
}, [filter]);
AJAX 요청이 매우 느릴경우
모든 네트워크 요청이 항상 즉각적인 응답을 가져다 주지는 않는다.
외부 api 접속이 느릴 경우를 고려하여 로딩화면 (loading indicator)의 구현은 필수적이다.
상태 처리를 해서 로딩화면을 구현할 수 있다.
fetch 요청의 전후로 로딩화면을 설정해주고 더 나은 UX를 구현할수있다.
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정합니다
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
useEffect(() => {
setIsLoading(true);
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
setIsLoading(false);
});
}, [filter]);
********************************************
요약 & 일일 회고
- 리액트의 데이터 흐름은 위에서 아래로 흐르는 하향식이다. 부모컴포넌트에서 하위 컴포넌트로 props를 내려주고, 각 컴포넌트는 props를 받아쓸수는 있지만 위로 전달할수는 없다. 또한 컴포넌트는 props가 어디에서 왔는지 알수없다.
- 특정 컴포넌트에서만 state가 필요할 경우 해당 컴포넌트에 state를 두면 되지만, 여러개의 컴포넌트에서 동일한 state에 영향을 받을 경우 해당되는 컴포넌트들을 포함하는 상위 컴포넌트에 state를 두어야 한다.
- state 끌어올리기는 하위 컴포넌트의 이벤트가 상위 컴포넌트의 상태를 변경시켜야 할때 사용한다. 상위 컴포넌트에서 하위 컴포넌트에 상태 변경 함수를 props로 내려주고, 하위 컴포넌트는 해당 함수를 이벤트 발생때 호출시킨다. 필요하다면 인자를 넘겨줄수도 있다.
- side effect는 함수 내부의 코드가 외부에 영향을 끼치는 경우를 말한다. 리액트에서는 fetch 등을 이용해 API를 가져오거나 DOM을 직접적으로 조작할때 side effect가 발생했다고 여겨진다. Ajax를 사용해아하거나 localStorage를 사용하거나, 또는 타이머 API 등을 사용해야할때가 예시이다.
- 리액트에서 side effect를 사용해야할때는 useEffect 를 사용할 수 있다. useEffect의 첫번째 인자는 함수로 이 안에 side effect를 실행시킬 코드를 작성하면 된다. 두번째 인자는 배열이 들어가는데, 이 배열의 값이 들어갈 경우 해당 값이 변경될때마다 useEffect가 실행된다. 이 배열을 빈 배열로 두면 컴포넌트가 처음 렌더링 될때 한번만 실행이 되고, 배열을 넣지 않으면 컴포넌트가 렌더링 되거나, props가 업데이트 되거나, state가 업데이트 될때 함수가 실행된다.
- 네트워크를 요청할때 항상 응답이 빠르지 않음으로 응답 대기중일경우 로딩 인디케이터를 사용해 더 나은 사용자 경험을 유도할 수 있다. fetch 요청의 전후로 로딩 인디케이터가 나타나게 할 수 있다.
***
오늘은 그래도 알던 부분이라 괜찮았다!
지금 약간... 번아웃 아닌 번아웃이 와서 공부가 잘 안되지만, 그래도 매일 하던 TIL 작성은 최대한 해보려고 한다.
나중에 수정하더라도 어쨌든 오늘 힘들어도 정리했다는걸 생각하면 그래도 기분이 괜찮을것같다.
그리고 뭔가 배울것같긴 했지만, 리액트 과제 혼자 만들기를 해보면서 사용했던 것들이라 신기했다.
과제를.. 얼른 정리해서 올려야... 더 말할수있을텐데 ㅎ
이번주 안에는 꼭 올리기로...ㅠ.ㅠ
********************************************
참고 링크
https://ko.reactjs.org/docs/hooks-reference.html#useeffect
'study > TIL' 카테고리의 다른 글
230207 - CORS, HTTP 서버 복습, Express, middleware (0) | 2023.02.07 |
---|---|
230206 - CORS (0) | 2023.02.06 |
230201 - REST API 복습, Postman 사용하기 (0) | 2023.02.01 |
23.01.31 - REST API (2) | 2023.01.31 |
23.01.30 - 네트워크 기초 지식 (0) | 2023.01.30 |