[React Basic] React의 Hooks (feat. useState, useEffect)

Joah·2022년 9월 23일
0

React Basic

목록 보기
16/25

게시글은 리액트 공부용이며 출처를 제시합니다.
출처: 소플의 처음 만난 React, 리액드를 다루는 기술, 모던 자바스크립트 deep dive

Hook

원래 존재하는 어떤 기능에 마치 갈고리를 거는 것처럼 끼어 들어가 같이 수행되는 것

리액트의 state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행되도록 만든 것

각 기능을 사용하겠다는 의미로 훅의 이름은 모두 use로 시작한다.

리액트 v16.8에 새로 도입된 기능으로 함수 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 수행하는 useEffect 등의 기능을 제공하여 기존의 함수 컴포넌트에서 할 수 없었던 다양한 작업을 가능하게 한다.


🛼 useState

state를 사용하기 위한 훅
함수 컴포넌트에서 상태를 관리해야 한다면 사용한다.

const [변수명 set함수명] = useState(초깃값)

state가 업데이트 되면 리렌더링이 일어난다.

import React, {useState} from "react";

funtion Counter(props){
	const [count, setCount] = useState(0);
  
  return(
  	<div>
    	<p>{count}번 클릭했습니다.</p>
		<button onClick={()=> setCount(count + 1)}> 클릭 </button>
    </div>
  )
}
const UserInfo = () => {
  const [name, setName] = useState("");
  const [nickName, setNickName] = useState("");

  const onChangeName = (e) => {
    setName(e.target.value);
  };
  const onChangeNickname = (e) => {
    setNickName(e.target.value);
  };
  return (
    <div>
      <div>
        <input value={name} onChange={onChangeName} />
        <input value={nickName} onChange={onChangeNickname} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임:</b> {nickName}
        </div>
      </div>
    </div>
  );
};

export default UserInfo;

🛼 useEffect

사이드 이펙트를 수행하기 위한 훅

사이드 이펙트 Side Effect
사전적으로는 부작용이라는 뜻을 가지고 있다. 개발자가 의도치 않은 코드가 실행되면서 버그가 나타나면 사이드 이펙트가 발생했다고 말한다.
하지만 리액트에서는 부정적인 의미는 아니다. 리액트에서의 사이드 이펙트는 효과 혹은 영향을 뜻하는 이펙트의 의미에 가깝다.

  • 예) 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 등의 작업

이 작업들이 다른 컴포넌트에 영향을 미칠 수 있으며 렌더링 중에는 작업이 완료될 수 없기 때문이다. 렌더링이 끝난 이후에 실행되어야 하는 작업이다.

클래스형 컴포넌트에서의 componentDidMount(), componentDidUpdate()를 함친 형태로 보아도 무방하다.


useEffect는 사이드 이펙트를 실행할 수 있도록 해주는 훅이다.

useEffect(이펙트 함수, 의존성 배열);

📍 의존성 배열은 이펙트가 의존하고 있는 배열이며 배열 안에 있는 변수 중에 하나라도 값이 변경되었을 때 이펙트 함수가 실행된다.

📍 이펙트 함수는 처음 컴포넌트가 렌더링된 이후와 업데이트로 인한 리렌더링 이후에 실행된다.

의존성 배열을 빈 배열로 지정하면 해당 이펙트가 state나 props의 값에 의존하지 않는 것이 되어 여러번 실행되지 않는다.

하지만 의존성 배열을 생략하게 되면 컴포넌트가 업데이트될 때마다 호출된다.

import React, {useState, useEffect} from "react";

funtion Counter(props){
	const [count, setCount] = useState(0);
  	
  	
  	//componentDidMount, componentDidUpdate와 비슷하게 작동한다.
  	useEffect(()=>{
    	document.title = `${count}번 클릭했습니다.`
    });
  
  return(
  	<div>
    	<p>{count}번 클릭했습니다.</p>
		<button onClick={()=> setCount(count + 1)}> 클릭 </button>
    </div>
  )
}
  • 브라우저에서 제공하는 API를 사용하여 document의 title을 업데이트 한다.

  • 의존성 배열 없이 useEffect() 를 사용하면 DOM이 변경된 이후에 해당 이펙트 함수를 실행하라는 의미

👉🏼 즉, 위의 코드의 이펙트 함수는 처음 컴포넌트가 마운트되었을때 실행되고 이후 컴포넌트가 업데이트 될때마다 실행하는 것

📍 이펙트는 함수 컴포넌트 안에서 선언되기 때문에 해당 컴포넌트의 props와 state에 접근할 수도 있다.

위에서는 count라는 state에 접근하여 해당 값이 포함된 문자열을 생성해 사용했다.


마운트될 때만 실행하고 싶을 때

의존성 배열에 빈 배열을 넣는다.

const UserInfo = () => {
  const [name, setName] = useState("");
  const [nickName, setNickName] = useState("");

  useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    console.log({ name, nickName });
  }, []);

  const onChangeName = (e) => {
    setName(e.target.value);
  };
  const onChangeNickname = (e) => {
    setNickName(e.target.value);
  };
  return (
    <div>
		...
    </div>
  );
};

export default UserInfo;

비어있는 의존성 배열 안에 name을 작성하면 name state가 변경될 때마다 콘솔을 출력한다.


componentWillUnmount() 와 동일한 기능은 어떻게 구현할까?

즉, 뒷정리를 하는 것인데 useEffect는 기본적으로 렌더링이되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 달라진다.

컴포넌트가 언마운트 되기 전이나 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리 함수를 반환해야 한다.

import React, {useState, useEffect} from "react";

function UserStatus(props){
	const [isOnline, setIsOnline] = useState(null);
  
  	const handleStatusChange = (status) => {
    	setIsOnline(status.isOnline)
    }
    
    useEffect(()=>{
    	ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
      return()=>{
      	ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
      };
    });
  
  if (isOnline === null){
  	return '대기중....';
  }
  
  return isOnline ? '온라인' : '오프라인';
}
  • ServerAPI를 사용해 사용자의 상태를 구독하고 있다.

  • 이후 함수 하나를 리턴하는데 해당 함수 안에은 구독을 해지하는 API를 호출하도록 되어 있다.

useEffect()에서 리턴하는 함수는 컴포넌트가 마운트 해제될 때 호출한다.

결과적으로 useEffect()의 리턴 함수의 역할은 componentWillUnmonet() 함수가 하는 역할과 동일하다.


다른 예시

  useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    console.log({ name, nickName });
    return () => {
      console.log("cleanup");
      console.log(name);
    };
  }, [name]);

** 언마운트 시에만 뒷정리 함수를 호출하고 싶다면

 useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    return () => {
      console.log("언마운트 되었습니다.");
    };
  }, []);


useEffect() 훅은 하나의 컴포넌트에 여러 개를 사용할 수 있다.

import React, {useState, useEffect} from "react";

function UserStatus(props){
  	const [count, setCount] = useState(0);
  	useEffect(()=>{
    	document.title = `${count}번 클릭했습니다.`
    })

  	const [isOnline, setIsOnline] = useState(null);
    useEffect(()=>{
    	ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
      return()=>{
      	ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
      };
    });
  
  	const handleStatusChange = (status) => {
    	setIsOnline(status.isOnline)
  	}
  
  if (isOnline === null){
  	return '대기중....';
  }
  
  return isOnline ? '온라인' : '오프라인';
}
  • 두 useEffect 함수 모두 의존성 배열이 없기 때문에 첫 렌더링과 매 렌더링 마다 즉, state에 변경사항이 있다면 적용 렌더링 이후에 실행되는 이펙트이다.

정리

useEffect(()=>{
  // 컴포넌트가 마운트 된 이후,
  // 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
  // 의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한 번씩만 실행됨
  //의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행됨
 ...
 
 return () => {
 	// 컴포넌트가 마운트 해제되기 전에 실행됨
   ...
 }
},[의존성 변수1, 의존성 변수2,...])

따라서 서버에서 데이터를 받아올 때 이펙트 함수를 사용해고 의존성 배열을 빈 배열을 적용했구나!

서버에서 날아오는 데이터의 양은 많은데 만약 state가 변경될때 마다 방대한 양의 데이터를 매번 불러오는 것은 성능 저하의 원인이 될 수 있다.

만약 의존성 배열에 url 주소를 변수에 넣어 지정하면 url에 바뀜에 따라 query string이 수정되도록 했으니 그에 따른 주소를 url이 수정되면 이펙트 함수를 실행한다.

useEffect는 의존성 배열에 넣는 변수와 관련된 이펙트가 적용될때 작성하면 된다.

예시)

const HandleScroll = () => {
  const [show, setShow] = useState(false);

  const controlNavBar = () => {
    if (window.scrollY > 590) {
      setShow(true);
    } else {
      setShow(false);
    }
  };

  useEffect(() => {
    window.addEventListener("scroll", controlNavBar);
  }, []);
}

의존성 배열이 빈 배열이기 때문에 마운트시와 언마운트시에 단 한 번씩만 실행된다. 함수는 이펙트 함수의 콜백함수가 되며 내비게이션 바 컴포넌트가 마운트, 언마운트 될 때 실행한다.
show state는 controlNavBar 함수에 의해 업데이트 된다.

  • 과정
  1. 자바스크립트 엔진이 위에서 차례로 런타임을 실시한다.
  2. useEffect를 사용했다는 것은 리액트에게 렌더링 하고 또 일이 있어 렌더링 하고 와서 useEffect 봐봐
  3. addEventListner에 의해 "scroll"이 일어나기를 감지한다.
  4. 사용자가 스크롤을 움직이면 콜백함수인 controlNavBar가 실행된다.
  5. window객체의 스크롤이 590일때의 조건문으로 사용자가 590까지 스크롤을 이동하면
  6. setState에 의해 show는 true 값을 가지면서 리렌더링이 되면서 조건부 연산자로 네브바가 나타나게 된다.
  • 이유
    의존성 배열을 넣지 않으면 함수 안에 다른 state가 업데이트될 때도 addEventListner의 콜백 함수가 계속 호출된다. 하지만 네브바는 스크롤이 590위치에 있을 때만 호출하면 되기 때문에 굳이 필요하지 않은 함수를 부를 낭비는 하지 않는게 좋다.
    따라서 의존성 배열에 빈 배열을 넣어 컴포넌트가 처음 마운트 될때와 언마운트 될때 즉 네브바가 나타나고 사라질때만 호출하면 되는 함수이기에 빈 배열을 명시한다.

🏝 Long Story Short

useEffect를 사용하면서도 왜 사용하는지 정확하게 숙지하지 않은채로 무지성으로 사용했다. 데이터를 받아올 때는 당연히 처음에만 받아오면 되니깐, 의존성 배열에 넣는 변수가 변할때만 사용하는 것 이런식으로만 대략 알고 있었는데

렌더링이 끝난 후에 실행되는 사이드 이펙트를 실행하기 위한 함수,
DOM이 변경된 후에 호출되는 함수(의존성 배열 없을때 즉 리렌더링마다)
리턴하면 언마운트될 때 호출하는 함수

이렇게 정확히 알고 사용해야 한다. 유즈이펙트는 진짜 자주 쓰이니깐!
역시 공식문서 짱

What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don’t need a special API to read it — it’s already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.

profile
Front-end Developer

0개의 댓글