React - 리액트 훅과 구조분해 할당

Noma·2021년 4월 14일
0

이번 포스팅에서는 함수형 컴포넌트 사용시 유용한 리액트 훅과 구조분해 할당에 대해서 다뤄보겠습니다. 👀

클래스 컴포넌트는 멤버변수 및 함수를 클래스 최초 생성시 한 번만 선언하고, 이후에 재선언하지 않는다.
(state나 props에 따라 render 함수만 호출됨)

반면, 함수 컴포넌트는 결국 함수이기 때문에 컴포넌트 내 코드블럭 전체가 계속 반복적으로 호출된다. 즉 로컬변수, 콜백함수 등 매번 새로 생성하고 새로운 참조값을 전달하게 된다.

이를 방지하기 위해선 어떻게 해야할까? 바로 리액트 훅을 사용하면 된다. state는 useState로, 멤버함수는 useCallback으로, ref는 useRef를 이용하는 등 값을 메모리에 저장해 코드블록이 반복적으로 호출되지 않게 즉, 재사용이 가능하도록 만들 수 있다.

1. Reack Hook

리액트 훅은 16.8 버전에 새로 추가된 것으로, 함수 컴포넌트에서 React state와 Lifecycle methods를 연동(hook into)할 수 있게 해주는 함수 이다. Hook은 class안에서는 동작하지 않으며, class 없이 React를 사용할 수 있게 해주는 함수이다.

Hook에는 내장 Hook과 커스텀 Hook이 있는데 여기선 내장 Hook만 다뤄보겠다.

1.1 State Hook - useState

아래 예시를 통해 함수 컴포넌트에 state을 추가하는 방법을 알아보자.

import React, { useState } from 'react';
// useState을 import 해야 함

function Example() {
  // "count"라는 새 상태 변수를 선언
  const [count, setCount] = useState(0);
  const handleIncrement=()=>{
  	setCount(count+1);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleIncrement}>
        Click me
      </button>
    </div>
  );
}

새로운 상태 변수를 선언할 때는 아래와 같이 하고,

const [상태변수이름,set상태변수이름]=useState(상태변수초기값);

상태를 업데이트 할 때 set상태변수이름()을 클래스 컴포넌트의 this.setState() 사용하듯 쓰면 된다.

🔍특징

  • this.state와 달리 setState Hook의 state 초기값은 객체일 필요가 없다.
  • this.setState와는 달리 이전 state와 새로운 state를 합치지 않는다. 즉, state 변수를 업데이트할 때 업데이트된 필드를 객체에 병합하는 class의 this.setState와 달리 useState는 그 값을 대체한다.
  • 함수형 컴포넌트가 여러번 호출 되어도 초기화되지 않고 일정한 데이터를 기억하고 있다.
  • 하나의 컴포넌트 내에서 여러개의 State Hook을 사용할 수도 있다.

📍__예시

function ExampleWithManyStates() {
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks'  }]);
}

1.2 Effect Hook - useEffect

Effect Hook, 즉 useEffect는 함수 컴포넌트 내에서 side effects를 수행할 수 있게 해준다. 이는 클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount의 기능들을 하나의 API로 통합한 것이다.

🔍 Side Effects란? ( = Effects )
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 등의 동작들을 말한다. 이는 다른 컴포넌트에 영향을 주기도 하고, 렌더링 과정에서는 구현할 수 없는 작업들이다.

📍__예시 1
마운트 될 때, 그리고 state나 props가 업데이트 될 때마다 호출하는 방법
componentDidMount+componentDidUpdate처럼 사용가능

useEffect(()=>{
  // do something...
}) 

📍__예시 2
마운트 될 때만 호출하는 방법 → componentDidMount처럼 사용가능

useEffect(()=>{
  // do something...
},[]); 
// 🌟빈 배열을 두 번째 인자로 전달

📍__예시 3
마운트 될 때, 그리고 두 번째 인자(=디펜던시)로 전달받은 state가 업데이트 될 때마다 호출하는 방법
componentDidMount+componentDidUpdate처럼 사용가능

useEffect(()=>{
  // do something...with some states or props                             
},[count]); 
// 🌟여러가지 전달 가능 [state1, state2, ...]

보통 useEffect내에서 쓰이는 stateprops[]안에 전달하는 것이 좋다.

📍__예시 4
언마운트될 때, 콜백함수를 자동으로 호출하는 방법
componentWillUnmount처럼 사용가능

useEffect(()=>{
  // do something...
  return ()=> {
  	// do something...
  };
});

return에 콜백 함수를 등록해 놓으면 React가 알아서 언마운트시 호출해준다.

📍__참고
하나의 컴포넌트 내에서 여러 개의 effect를 사용 가능하다.

  const [count, setCount] = useState(0);
  useEffect(() => {
    // do something...
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    // do something...
    return () => {
      // do something...
    };
  });

1.3 useCallback

앞서 말했듯이, 함수형 컴포넌트에서는 리렌더링될 때 선언된 멤버변수 및 함수가 재선언된다. 이러한 특성으로, 콜백함수를 props로 받는 자식 컴포넌트가 있을 경우 새로운 참조값이 전달되게 돼 자식 컴포넌트 또한 리렌더링이 일어나게 된다. 이를 막기 위해 쓰는 것이 useCallback이다.

useCallback은 한 번 만들어진 함수를 캐시하는 것이라 메모리가 소모된다. 따라서 남발하면 안되므로 자식 컴포넌트에 props로 전달되어지는 함수에만 사용 하고, 그외에 일반 div나 button 등과 같은 말단 요소에 전달되어지는 콜백일 경우 성능에 문제가 없으므로 사용하지 않는 것이 좋다.

📍__예시

const Example=({data})=>{ //이 부분이 생소하다면 3.destructurting 부분을 보자.
  const [count,setCount]=useState(0);
  ...
  const handleIncrement=useCallback(()=>{
     setCount(count+1);
     data.add();
  },[data]);
  return(
     <Cart onIncrement={handleIncrement} count={count} />
  );
}
export default Example;

useCallback() 안에서 컴포넌트가 받아온 props를 쓸 경우(여기선, data) 디펜던시를 전달하지 않으면 이전의 props(업데이트X)를 계속 사용하게 된다. 따라서, 이러한 경우 두 번째 인자로 의존성 배열(디펜던시)를 꼭 전달해 업데이트 되도록 하자.

1.4 useRef

함수형 컴포넌트는 호출될 때마다 React.createRef가 있을 경우 계속 새로운 참조값을 만들어 할당하게 되므로, useRef를 사용해 방지하도록 하자. ( 용도는 React.creatRef와 비슷함 )

const Example=()=>{
  const inputRef=useRef();
  ...
  return(
 	<input ref={inputRef} type="text"/> 
  );
}
export default Example;

React.createRef때와 같이 inputRef.current. 으로 접근해 사용하면 된다.

2. Hook 사용 규칙

Hook은 그냥 JavaScript 함수이지만, 두 가지 규칙을 준수해야 한다.

  • 최상위 레벨(at the top level)에서만 호출해야 한다. 반복문, 조건문, 중첩된 함수 내에서 실행하지 말자.
  • React 함수 컴포넌트와 custom hook내에서만 호출해야 한다. 일반 JavaScript 함수에서는 호출 X

이로써 React Hook에 대해 어느정도 다뤘다. 더 자세한 것은 공식문서를 참고하면서 필요할 때마다 공부하도록 하자.


➕ 구조 분해(destructuring) 할당

구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다. 자세한 내용은 이곳에서 확인해보고, 여기서는 함수형 컴포넌트에서 어떻게 쓰면 좋을 지에 대해서 다루겠다.

먼저 예시가 될 함수 컴포넌트를 생성해보면, 아래와 같다.

import React from 'react';
const Example = (props) => {
  return(
	<h1>Hello, world!</h1>
  );            
};
export default Example;

❗ 참고
function으로도 생성이 가능하다.

function Example(props){
  return(
    <h1>Hello, world!</h1>
  );
};

위의 컴포넌트가 아래와 같은 props를 부모로부터 전달 받았다고 가정해보자. 그러면 Example 컴포넌트 내에서 props.onAdd와 같이 접근해 사용할 수 있을 거다.

<Example onAdd={handleAdd} user={user.name}/>

하지만 매번 props.onAdd, props.user....처럼 쓰는 것이 번거롭다면 props 대신 ({onAdd, user})로 작성하면 props.를 때고 그냥 onAdd, user 이런식으로 접근이 가능해진다.

import React from 'react';
const Example = ({onAdd, user}) => {
  return(
	<h1>Hello, {user}!</h1>
  );            
};
export default Example;

만약 받아온 props를 다른 이름으로 사용하고 싶다면 ({user:userName,handleAdd})와 같이 :를 이용해 새 이름을 정의해 쓸 수 있다. 아래 코드에선 user가 userName으로 변경되어 사용되고 있다.
(handleAdd는 재정의 하지 않았으므로 그대로 사용됨)

import React from 'react';
const Example = ({onAdd, user:userName}) => {
  const handleAdd=()=>{
  	console.log(`${userName} added!`);
    onAdd();
  };
  return(
    <div>
      <h1>Hello, {userName}!</h1>
      <button onClick={handleAdd}>Add</button>
    </div>
  );            
};
export default Example;

📚 reference

profile
Frontend Web/App Engineer

0개의 댓글