[Wanted]_Week2-1_useEffect & Context API

hanseungjune·2023년 7월 4일
0

Wanted

목록 보기
9/21
post-thumbnail

1. 의존성 배열

  • React에서 의존성 배열은 useEffect 훅에서 사용되며, 올바르게 다루는 것이 중요합니다.
  • 의존성 배열은 useEffect의 콜백 함수가 실행되는 조건을 결정합니다.
  • 의존성 배열을 빈 배열([])로 설정하면, 컴포넌트가 처음 마운트될 때에만 콜백 함수가 실행됩니다.
  • 의존성 배열에 특정 변수 또는 상태값을 포함시키면, 해당 변수 또는 상태값이 변경될 때에만 콜백 함수가 실행됩니다.
  • 의존성 배열에 여러 개의 변수 또는 상태값을 포함시킬 수 있으며, 모든 의존성이 변경될 때에만 콜백 함수가 실행됩니다.
  • 주의해야 할 점은, 의존성 배열에 함수를 직접 넣는 것은 오작동의 원인이 될 수 있습니다. 필요한 경우 함수를 의존성 배열에 넣지 말고, useCallback을 사용하여 함수를 메모이제이션해야 합니다.
  • 의존성 배열을 올바르게 다루지 않으면, 예상치 못한 버그가 발생할 수 있습니다. 의존성 배열을 신중하게 검토하고, 필요한 의존성만 포함시키도록 해야 합니다.

의존성 배열을 제대로 이해하고 사용하면, 컴포넌트의 효율성과 정확성을 높일 수 있습니다.

1. 의존성 배열이란?

  • 의존성 배열은 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 함수가 실행되어 페이지의 타이틀이 업데이트됩니다.

2. useEffect 의존성 배열의 잘못된 활용

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이 업데이트됩니다.

3. useEffect 의존성 배열을 잘 설정하는 법

useEffect에서 버그가 발생하지 않게 의존성 배열을 잘 설정하는 방법은 아래의 원칙만 지켜주면 됩니다.

1. 의존성 줄이기

// 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 상태의 현재 값을 기반으로 새로운 값을 설정하는데, 이때 countuseEffect클로저로 가져와서 사용되고 있습니다.

  • 그러나 이렇게 상태를 업데이트할 때 현재 값을 사용하는 경우, count 값이 변경되어도 useEffect가 다시 실행되지 않습니다. 왜냐하면 setCount 함수를 호출하는 시점에서 count의 값은 이미 고정되어 있기 때문입니다.

  • 따라서 "good" 코드에서는 setCount(prevCount => prevCount + 1)을 사용하여 이전 상태 값을 가져와서 업데이트해주고 있습니다. 이 방식은 함수형 업데이트라고도 불리며, setCount 함수에 콜백 함수를 전달하여 이전 상태 값을 가져올 수 있습니다.

  • 이렇게 되면 useEffectcount 상태에 의존하지 않고, 오직 setCount 함수에만 의존성이 있게 됩니다. 그래서 setCount 함수가 변경되지 않는 이상 useEffect는 다시 실행되지 않습니다.

  • 따라서 "good" 코드는 count 값이 변경되어도 useEffect를 다시 실행하지 않으므로, 불필요한 setInterval의 중복 실행을 피할 수 있습니다. 이는 성능 측면에서 효율적입니다.

즉, "good" 코드에서는 함수형 업데이트를 사용하여 count 상태의 이전 값을 가져오므로써 useEffect의 의존성을 setCount 함수로만 제한함으로써 불필요한 재실행을 피할 수 있습니다.

2. 의존성 빼먹지 않기

// 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이 업데이트되지 않습니다.

3. 무한 루프에 빠진 코드 해결하기

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>
	);
}

해결방안 1

function Component(){
	const [count, setCount] = useState(0);

	useEffect(() => {
		const increaseCount = () => {
			setCount(prev => prev + 1);
		};

		increaseCount();
	}, []);
}

코드에서는 increaseCount 함수를 useEffect 밖으로 이동시켜서 함수 컴포넌트 내에서 정의하고 사용합니다. 그리고 useEffect 내부에서 increaseCount 함수를 호출하여 초기값으로 설정합니다. 이렇게 하면 함수가 새롭게 생성되는 것을 방지하고, 의존성 배열에는 빈 배열([])을 사용하여 한 번만 실행되도록 설정합니다.

4. 함수를 컴포넌트 바깥으로 이동시키기

// 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와 관련된 로직에 집중해야 하므로, 컴포넌트 밖으로 이동한 함수는 컴포넌트의 의도를 명확히 표현하고 가독성을 높여줍니다. 또한, 함수를 컴포넌트 외부에 위치시킴으로써 컴포넌트 내부의 로직 변경 없이 함수를 수정하거나 대체할 수 있습니다.

  • 성능 개선: 함수를 컴포넌트 외부에 위치시키면 해당 함수는 컴포넌트가 렌더링될 때마다 재생성되지 않습니다. 따라서 함수 컴포넌트 내부에서 정의된 함수의 경우, 함수가 새롭게 생성되는 것을 방지하여 성능을 개선할 수 있습니다.

  • 테스트 용이성: 함수를 컴포넌트 외부에 위치시키면 해당 함수를 단독으로 테스트하기가 더 쉬워집니다. 컴포넌트와 분리되어 있으므로 함수에 대한 테스트를 작성할 때 컴포넌트의 다른 부분에 영향을 주지 않고 테스트할 수 있습니다.

따라서 함수를 컴포넌트 외부로 빼는 것은 코드의 재사용성, 가독성, 유지 보수성, 성능 개선, 테스트 용이성 등 다양한 이점을 제공합니다.

5. 메모이제이션

//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 호출을 줄일 수 있습니다.

  1. 이전 코드의 문제점:
  • increaseCount 함수는 컴포넌트가 렌더링될 때마다 재생성되었습니다. 이로 인해 useEffect의 의존성 배열이 빈 배열([])로 설정되었음에도 불구하고, increaseCount 함수의 새로운 인스턴스가 생성되었기 때문에 useEffect는 매번 실행되었습니다.
  • 따라서, increaseCount 함수가 동일한 인스턴스를 유지하면서 의존성 배열의 변경에 따라 조건적으로 실행되도록 개선해야 했습니다.
  1. 리팩토링한 코드의 개선점:
  • useCallback 훅을 사용하여 increaseCount 함수를 캐싱합니다. 이로 인해 increaseCount 함수는 컴포넌트가 처음 렌더링될 때 한 번만 생성되고, 이후에는 동일한 함수 인스턴스를 재사용합니다.
  • increaseCount 함수를 의존성 배열에 포함시켜서, increaseCount 함수의 변경 여부에 따라 useEffect가 조건적으로 실행되도록 합니다.
  • 따라서, increaseCount 함수의 변경이 없는 한, useEffect는 처음 한 번만 실행되고 불필요한 재실행을 방지할 수 있습니다.

이렇게 리팩토링을 통해 increaseCount 함수의 재생성을 방지하고, useEffect의 실행을 최적화하여 성능 향상을 이끌어낼 수 있습니다.

2. Context API

  • Context APIReact에서 제공하는 내장 API로서 컴포넌트들에게 동일한 Context(맥락)를 전달하는 데 사용됩니다.
  • 일반적으로 리액트에서 데이터를 전달하는 기본 원칙은 단방향성입니다. 즉, 데이터는 부모 컴포넌트에서 자식 컴포넌트로만 전달됩니다.
  • 단방향성은 애플리케이션의 안전성을 높이고 흐름을 단순화하는데 유용하지만, 깊은 컴포넌트 구조에서 자식 컴포넌트에게 데이터를 전달해야 하는 경우 복잡성이 증가할 수 있습니다.
  • Context API는 이러한 상황에서 컴포넌트 간의 데이터 전달을 간단하게 처리할 수 있도록 도와줍니다.
  • Context API를 사용하면 부모 컴포넌트에서 생성한 컨텍스트 객체를 자식 컴포넌트에서 직접 참조할 수 있습니다. 이를 통해 중간에 위치한 컴포넌트들을 거치지 않고 데이터를 전달할 수 있습니다.
  • Context API를 사용하면 데이터를 공유할 범위를 지정할 수 있습니다. 컨텍스트 객체는 트리 구조 상에서 특정 컴포넌트를 기준으로 상위로 올라가거나 하위로 내려가는 방식으로 데이터를 전파합니다.
  • Context APIcreateContext 함수를 사용하여 컨텍스트 객체를 생성하고, Provider 컴포넌트를 통해 데이터를 제공하며, Consumer 컴포넌트를 통해 데이터를 소비합니다.
  • Provider 컴포넌트를 통해 제공된 데이터는 해당 컨텍스트를 구독하는 모든 Consumer 컴포넌트에서 접근할 수 있습니다.
  • Context API를 사용하면 컴포넌트 간의 데이터 전달을 효율적으로 처리할 수 있고, 중간 단계의 컴포넌트를 거치지 않고도 데이터를 전달할 수 있습니다.

사용법

  1. createContext
    Context API를 사용하기 위해서는 먼저 공유할 Context를 만들어줘야 합니다. ContextcreateContext 라는 함수를 통해서 사용할 수 있습니다.
const UserContext = createContext(null);
  • createContext 함수를 호출하면 Context 객체가 리턴됩니다.
  • 함수를 호출할 때는 defaultValue를 인자로 전달할 수 있습니다.

이때 defaultValue는 Context Value의 초기값이 아닌, 다른 컴포넌트에서 Context에 접근하려고 하지만 Provider로 감싸져 있지 않은 상황에서 사용될 값을 의미합니다.

  1. 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>
  1. 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>
}

profile
필요하다면 공부하는 개발자, 한승준

0개의 댓글