React
에서 의존성 배열은 useEffect
훅에서 사용되며, 올바르게 다루는 것이 중요합니다.useEffect
의 콜백 함수가 실행되는 조건을 결정합니다.빈 배열([])
로 설정하면, 컴포넌트가 처음 마운트될 때에만 콜백 함수가 실행됩니다.useCallback
을 사용하여 함수를 메모이제이션해야 합니다.의존성 배열을 제대로 이해하고 사용하면, 컴포넌트의 효율성과 정확성을 높일 수 있습니다.
useEffect
의 두 번째 인자로 전달되는 배열입니다.effect 함수
가 의존하는 값들을 포함하고 있어야 합니다.빈 배열([])
로 설정되면, effect 함수
는 컴포넌트가 처음 마운트될 때에만 실행됩니다.effect 함수가 실행
됩니다.effect 함수가 실행
됩니다.useEffect
는 리렌더링 이후에 의존성 배열을 검사하여 변경된 의존성이 있는 경우에만 effect를 실행
합니다.effect 함수
가 외부의 값
을 사용하고 있을 때는 해당 값들을 의존성 배열에 포함
시켜야 합니다.코드 예시:
import React, { useEffect, useState } from 'react';
function Component() {
const [count, setCount] = useState(0);
const effect = () => {
document.title = `You clicked ${count} times`;
};
useEffect(effect, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase Count</button>
</div>
);
}
위의 예시에서 effect 함수
는 count라는 외부의 값
을 사용하고 있습니다. 따라서 count를 의존성 배열에 포함
시켜야 합니다. 이렇게 설정하면 count가 변경될 때마다 effect 함수가 실행
되어 페이지의 타이틀이 업데이트됩니다.
function Component(){
const [count, setCount] = useState(0);
useEffect(()=>{
document.title = `you clikced ${count} times`
}, []];
}
위 코드의 문제점은 count
변수를 의존성 배열에 포함시키지 않았기 때문에, count
가 변경되어도 useEffect
가 다시 실행되지 않는다는 것입니다.
count
변수를 사용하여 document.title
을 업데이트하고자 합니다.useEffect
의 의존성 배열에 count가 포함되지 않았으므로
, count
가 변경되어도 useEffect
가 다시 실행되지 않습니다.document.title
은 초기 렌더링 시의 값인 "You clicked 0 times"
로 고정되어 업데이트되지 않습니다.올바른 방법은 아래와 같이 의존성 배열에 count
를 포함시켜주는 것입니다:
function Component() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase Count</button>
</div>
);
}
위 코드에서는 count
를 의존성 배열에 포함시켜주었으므로, count
가 변경될 때마다 useEffect
가 다시 실행되어 document.title
이 업데이트됩니다.
useEffect에서 버그가 발생하지 않게 의존성 배열을 잘 설정하는 방법은 아래의 원칙만 지켜주면 됩니다.
// bad
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalID = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(intervalID);
}, [count, setCount]);
return (
<div>
<h1>count:{count}</h1>
</div>
);
}
// good
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalID = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(intervalID);
}, [setCount]);
return (
<div>
<h1>count:{count}</h1>
</div>
);
}
위의 코드에서 "good" 함수 컴포넌트의 useEffect
부분이 "bad" 함수 컴포넌트의 useEffect
부분보다 성능 측면에서 더 좋은 이유는 다음과 같습니다:
"bad" 코드에서는 count
를 의존성 배열에 포함시켰기 때문에, count
가 변경될 때마다 useEffect
가 다시 실행됩니다. 그리고 useEffect
내에서 setCount(count + 1)
을 호출하면서 count
상태를 업데이트하고 있습니다.
setCount(count + 1)
은 count
상태의 현재 값을 기반으로 새로운 값을 설정하는데, 이때 count
는 useEffect
의 클로저
로 가져와서 사용되고 있습니다.
그러나 이렇게 상태를 업데이트할 때 현재 값을 사용하는 경우, count
값이 변경되어도 useEffect
가 다시 실행되지 않습니다. 왜냐하면 setCount
함수를 호출하는 시점에서 count
의 값은 이미 고정되어 있기 때문입니다.
따라서 "good" 코드에서는 setCount(prevCount => prevCount + 1)
을 사용하여 이전 상태 값을 가져와서 업데이트해주고 있습니다. 이 방식은 함수형 업데이트
라고도 불리며, setCount
함수에 콜백 함수를 전달
하여 이전 상태 값
을 가져올 수 있습니다.
이렇게 되면 useEffect
는 count
상태에 의존하지 않고, 오직 setCount
함수에만 의존성이 있게 됩니다. 그래서 setCount
함수가 변경되지 않는 이상 useEffect
는 다시 실행되지 않습니다.
따라서 "good" 코드는 count
값이 변경되어도 useEffect
를 다시 실행하지 않으므로, 불필요한 setInterval
의 중복 실행을 피할 수 있습니다. 이는 성능 측면에서 효율적입니다.
즉, "good" 코드에서는 함수형 업데이트를 사용하여 count
상태의 이전 값을 가져오므로써 useEffect
의 의존성을 setCount
함수로만 제한함으로써 불필요한 재실행을 피할 수 있습니다.
// bad
function Component(){
const [count, setCount] = useState(0);
useEffect(()=>{
document.title = `you clikced ${count} times`
}, []];
}
// good
function Component(){
const [count, setCount] = useState(0);
useEffect(()=>{
document.title = `you clikced ${count} times`
}, [count]];
}
BAD 코드
의 문제점:
useEffect
의 의존성 배열에 빈 배열 ([])
을 넣은 경우입니다.useEffect
가 실행됩니다.count
변수를 useEffect
내부에서 사용하고 있지만, count
를 의존성 배열에 추가하지 않았습니다.count
값이 변경되어도 useEffect
는 다시 실행되지 않고, document.title
이 업데이트되지 않습니다.GOOD 코드
의 개선점:
useEffect
의 의존성 배열에 count
를 넣은 경우입니다.count
변수를 useEffect
내부에서 사용하고 있으므로, count
를 의존성 배열에 추가해야 합니다.count
값이 변경될 때마다 useEffect
가 다시 실행되어 document.title
이 업데이트됩니다.count
값에 의존성을 설정하여 변경 사항이 있을 때마다 useEffect
를 실행할 수 있게 되었습니다.count
를 추가함으로써 useEffect
가 의도한 대로 동작하고, count
값의 변화에 따라 document.title
이 적절하게 업데이트됩니다.요약하면, GOOD 코드
에서는 useEffect
의 의존성 배열에 count
를 추가하여 count
값의 변화를 감지하고, 변화가 있을 때마다 useEffect
를 실행하여 document.title
을 업데이트할 수 있습니다. 이에 반해 BAD 코드
에서는 빈 배열을 사용하여 useEffect
가 컴포넌트 첫 번째 렌더링 이후에만 실행되어 count
값의 변화를 감지하지 못하고, 따라서 document.title
이 업데이트되지 않습니다.
function Component(){
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(prev => prev + 1);
}
useEffect(() => {
increaseCount();
}, []);
// 나머지 컴포넌트 로직
return (
<div>
<button onClick={increaseCount}>Increase</button>
<h1>Count: {count}</h1>
</div>
);
}
function Component(){
const [count, setCount] = useState(0);
useEffect(() => {
const increaseCount = () => {
setCount(prev => prev + 1);
};
increaseCount();
}, []);
}
코드에서는 increaseCount
함수를 useEffect
밖으로 이동시켜서 함수 컴포넌트 내에서 정의하고 사용합니다. 그리고 useEffect
내부에서 increaseCount
함수를 호출하여 초기값으로 설정합니다. 이렇게 하면 함수가 새롭게 생성되는 것을 방지하고, 의존성 배열에는 빈 배열([])
을 사용하여 한 번만 실행되도록 설정합니다.
// bad
function Component() {
const getUserAuth = () => {
localStorage.getItem("ACCESS_TOKEN");
};
useEffect(() => {
const token = getUserAuth();
// login....
}, []];
};
// good
function Component() {
useEffect(() => {
const token = getUserAuth();
// login....
}, [getUserAuth]];
};
const getUserAuth = () => {
localStorage.getItem("ACCESS_TOKEN");
};
함수
를 컴포넌트 밖으로 빼는 것
에는 몇 가지 이점이 있습니다:
코드 재사용성
: 함수를 컴포넌트 밖으로 빼면 해당 함수를 다른 컴포넌트에서도 사용할 수 있습니다. 함수를 재사용함으로써 코드의 중복을 줄이고 유지 보수성을 향상시킬 수 있습니다.
가독성
과 유지 보수성
: 함수를 컴포넌트 밖으로 빼면 컴포넌트 내부의 코드가 간결해집니다. 컴포넌트는 주로 UI와 관련된 로직에 집중해야 하므로, 컴포넌트 밖으로 이동한 함수는 컴포넌트의 의도를 명확히 표현하고 가독성을 높여줍니다. 또한, 함수를 컴포넌트 외부에 위치시킴으로써 컴포넌트 내부의 로직 변경 없이 함수를 수정하거나 대체할 수 있습니다.
성능 개선
: 함수를 컴포넌트 외부에 위치시키면 해당 함수는 컴포넌트가 렌더링될 때마다 재생성되지 않습니다. 따라서 함수 컴포넌트 내부에서 정의된 함수의 경우, 함수가 새롭게 생성되는 것을 방지하여 성능을 개선할 수 있습니다.
테스트 용이성
: 함수를 컴포넌트 외부에 위치시키면 해당 함수를 단독으로 테스트하기가 더 쉬워집니다. 컴포넌트와 분리되어 있으므로 함수에 대한 테스트를 작성할 때 컴포넌트의 다른 부분에 영향을 주지 않고 테스트할 수 있습니다.
따라서 함수를 컴포넌트 외부로 빼는 것은 코드의 재사용성
, 가독성
, 유지 보수성
, 성능 개선
, 테스트 용이성
등 다양한 이점을 제공합니다.
//BAD
function Component(){
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(prev => prev + 1);
}
useEffect(() => {
// do something 1
increaseCount();
}, []];
useEffect(() => {
// do something 2
increaseCount();
}, []];
}
//GOOD
function Component(){
const [count, setCount] = useState(0);
const increaseCount = useCallback(() => {
setCount(prev => prev + 1);
}, []);
useEffect(() => {
// do something 1
increaseCount();
}, [increaseCount]];
useEffect(() => {
// do something 2
increaseCount();
}, [increaseCount]];
}
해당 코드의 리팩토링은 useCallback
훅을 사용하여 increaseCount
함수를 캐싱하고 의존성 배열에 포함시켰습니다. 이를 통해 함수의 재생성을 방지하고 불필요한 useEffect
호출을 줄일 수 있습니다.
increaseCount
함수는 컴포넌트가 렌더링될 때마다 재생성되었습니다. 이로 인해 useEffect
의 의존성 배열이 빈 배열([])
로 설정되었음에도 불구하고, increaseCount
함수의 새로운 인스턴스가 생성되었기 때문에 useEffect
는 매번 실행되었습니다.increaseCount
함수가 동일한 인스턴스를 유지하면서 의존성 배열의 변경에 따라 조건적으로 실행되도록 개선해야 했습니다.useCallback
훅을 사용하여 increaseCount
함수를 캐싱합니다. 이로 인해 increaseCount
함수는 컴포넌트가 처음 렌더링될 때 한 번만 생성되고, 이후에는 동일한 함수 인스턴스를 재사용합니다.increaseCount
함수를 의존성 배열에 포함시켜서, increaseCount
함수의 변경 여부에 따라 useEffect
가 조건적으로 실행되도록 합니다.increaseCount
함수의 변경이 없는 한, useEffect
는 처음 한 번만 실행되고 불필요한 재실행을 방지할 수 있습니다.이렇게 리팩토링을 통해 increaseCount
함수의 재생성을 방지하고, useEffect
의 실행을 최적화하여 성능 향상을 이끌어낼 수 있습니다.
Context API
는 React
에서 제공하는 내장 API
로서 컴포넌트들에게 동일한 Context(맥락)
를 전달하는 데 사용됩니다.단방향성
입니다. 즉, 데이터는 부모 컴포넌트에서 자식 컴포넌트로만 전달됩니다.단방향성
은 애플리케이션의 안전성을 높이고 흐름을 단순화하는데 유용하지만, 깊은 컴포넌트 구조에서 자식 컴포넌트에게 데이터를 전달해야 하는 경우 복잡성이 증가
할 수 있습니다.Context API
는 이러한 상황에서 컴포넌트 간의 데이터 전달을 간단하게 처리할 수 있도록 도와줍니다.Context API
를 사용하면 부모 컴포넌트에서 생성한 컨텍스트 객체를 자식 컴포넌트에서 직접 참조할 수 있습니다. 이를 통해 중간에 위치한 컴포넌트들을 거치지 않고 데이터를 전달할 수 있습니다.Context API
를 사용하면 데이터를 공유할 범위를 지정할 수 있습니다. 컨텍스트 객체는 트리 구조 상에서 특정 컴포넌트를 기준으로 상위로 올라가거나 하위로 내려가는 방식으로 데이터를 전파합니다.Context API
는 createContext
함수를 사용하여 컨텍스트 객체를 생성하고, Provider
컴포넌트를 통해 데이터를 제공하며, Consumer
컴포넌트를 통해 데이터를 소비합니다.Provider
컴포넌트를 통해 제공된 데이터는 해당 컨텍스트를 구독하는 모든 Consumer
컴포넌트에서 접근할 수 있습니다.Context API
를 사용하면 컴포넌트 간의 데이터 전달을 효율적으로 처리할 수 있고, 중간 단계의 컴포넌트를 거치지 않고도 데이터를 전달할 수 있습니다.createContext
Context API
를 사용하기 위해서는 먼저 공유할 Context를 만들어줘야 합니다. Context
는 createContext
라는 함수를 통해서 사용할 수 있습니다.const UserContext = createContext(null);
이때 defaultValue는 Context Value의 초기값이 아닌, 다른 컴포넌트에서 Context에 접근하려고 하지만 Provider로 감싸져 있지 않은 상황에서 사용될 값을 의미합니다.
Provider
만들어진 Context
를 통해서 특정한 값을 전달하기 위해서는 Provider
컴포넌트를 이용해야 합니다.
Context
객체에는 Provider
라는 프로퍼티가 있으며 이는 리액트 컴포넌트입니다.
Provider
컴포넌트는 value
라는 props
을 가지고 있으며, value
에 할당된 값을 Provider 컴포넌트 하위에 있는 어떤 컴포넌트든 접근할 수 있게 해주는 기능을 가지고 있습니다.
const UserContext = createContext(null);
const user = {name: "yeonuk"};
<UserContext.Provider value={user}>
<Child />
</UserContext.Provider>
useContext
Class
컴포넌트에서 Context
를 통해 공유된 값에 접근하려면, Consumer
라는 다소 복잡한 방식을 통해서 접근해야 합니다. 하지만 함수 컴포넌트
에서는 useContext
라는 내장 Hook
을 이용해 Context Value
에 접근할 수 있습니다.
const UserContext = createContext(null);
const user = {name: "yeonuk"};
<UserContext.Provider value={user}>
<Child />
</UserContext.Provider>
function Child() {
const user = useContext(UserContext);
return <h1>{user.name}</h1>
}