React의 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 점이다 !
먼저 컴포넌트를 만들고, 페이지를 조립 해나간다.
즉, 상향식(bottom-up)으로 앱을 만든다. 이것의 가장 큰 장점은 테스트가 쉽고 확장성이 좋다.
하나의 컴포넌트는 한가지 일만 한다.
데이터를 전달하는 주체는 부모 컴포넌트
! : 데이터 흐름이 하향식(top-down)이다.
-> 단방향 데이터 흐름(one-way data flow)
상태는 최소화하는 것이 가장 좋다.
상태가 많아질수록 애플리케이션은 복잡해지기 때문이다.
어떤 데이터를 상태로 두어야 하는지 여부는 다음 세가지 질문을 통해 판단하자.
부모 컴포넌트에서의 상태가 하위 컴포넌트에 의해 변하는 경우 -> State 끌어올리기(Lifting state up)
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다
마치 콜백 함수를 사용하는 방법과 비슷하다.
Twittler React - Lifting State Up Hooks
Hook은 클래스 컴포넌트를 작성하지 않아도 state와 같은 특징들을 사용할 수 있다.
Effect Hook을 사용하면 함수 컴포넌트에서 side effect를 수행할 수 있다.
React 컴포넌트가 화면에 렌더링 된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 흔히 Side Effect라고 말한다.
앞서 배운 React의 함수 컴포넌트는, props가 입력으로, JSX Element가 출력으로 나간다. 여기에는 그 어떤 Side Effect도 없으며, 순수 함수로 작동된다.
하지만 보통 React 애플리케이션을 작성할 때에는, AJAX 요청이 필요하거나, 어떤 데이터를 가져오기 위해서 외부 API
를 호출하는 경우 가 발생할 수 있다. 이는 전부 Side Effect 이다.
왜냐면 이것은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문이다.
React는 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공한다.
Effect Hook, 즉 useEffect
는 함수 컴포넌트 내에서 이런 Side Effects를 수행할 수 있게 해준다.
useEffect
Hook을 기존의 클래스 컴포넌트를 사용해봤다면 생명주기 함수(lifeCycle methods)와 같은 효과를 낸다고 생각하면 된다.
componenetDidMount
와componentDidUpdate
,componentWillUnmount
가 합쳐진 것으로 생각하자.
useEffect는 단어 그대로 함수 컴포넌트에서 사이드 효과들(Side Effects)을 실행하는 것이다.
컴포넌트나 state에 변화가 생길 때 호출되는 함수라고 생각하자.
두 개의 인자를 받는데 첫번째 인자는 변경시 호출할 콜백 함수고, 두번째 인자는 상태 변경을 감지할 state를 설정한다.
state를 별도로 설정하지 않으면 componentDidUpdate
, componentDidMount
랑 동일한 역할을 하게 된다.
useEffect
를 컴포넌트 내부에 둠으로써 effect를 통해 count
state 변수(또는 어떤 prop에도)에 접근할 수 있게 된다.const UseEffectExample = () => {
const [value, setValue] = useState('initial value');
useEffect(() => {
console.log('렌더링!');
});
return (
<div>
<p>{value}</p>
<input onChange={(e) => {setTest(e.target.value)}} />
</div>
);
};
해당 컴포넌트가 렌더링 될 때마다 useEffect
함수가 실행되면서 콘솔에 로그를 남긴다.
기존의 state처럼, state의 값이 변경될 경우 렌더링을 시도하기 때문에 input에 타자를 칠 때마다 로그가 찍히게 된다.
만약 state가 여러개이고, useEffect
함수가 그 state들이 변경될 때마다 호출된다면 매우 비효율적이다.
이런 상황을 위해서 useEffect
는 두번째 매개변수 인자를 받을 수 있다.
useEffect(() => {
console.log('렌더링 !');
},[value]);
이렇게 배열 형태 로 특정 state를 넘기게 되면, 해당 state의 값이 변경될 때만 useEffect
함수가 호출되게 된다.
이제 여러 개의 state에 대해서 개별적인 동작을 실행시켜 보자.
useEffect(() => {
console.log('value1 state에 대해서만 호출해 !');
},[value]);
useEffect(() => {
console.log('value2 state에 대해서만 호출해 !');
},[value2]);
처음 렌더링 될 때 한번만 실행하고 싶은 경우에는 매개변수를 빈 배열로 넘겨주면 된다.
useEffect(() => {
console.log('첫 렌더링 호출 !');
},[]);
이렇게 할 경우 최초 한번만 호출된다. 이후의 어떤 렌더링에도 재호출 되지 않는다.
const NoteApp = () => {
const [notes, setNotes] = useState([]);
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
// 1
useEffect(() => {
console.log('load data')
const notesData = JSON.parse(localStorage.getItem('notes'));
if (notesData) {
setNotes(notesData)
}
}, []);
// 2
useEffect(() => {
console.log('update notes');
const toJson = JSON.stringify(notes);
localStorage.setItem('notes', toJson)
}, [notes]);
// 3
useEffect(() => {
console.log('useEffect called')
});