Develop/React Native

[React Native] 리액트 네이티브 Hooks (useState, useEffect, useRef, useMemo)

마크투비 2021. 11. 14. 15:39

오늘은 <처음 배우는 리액트 네이티브> 6장 Hooks에 대해 알아보겠다 😁

Hooks


리액트 Hooks은 리액트 16.8, 리액트 네이티브 0.59 버전부터 사용할 수 있는 기능이다. Hooks를 이용해서 함수형 컴포넌트에서도 상태를 관리할 수 있게 되었다.

1. useState

주로 컴포넌트들의 상태를 관리하기 위해 useState 함수를 사용한다.

const [state, setState] = useState(initialState);

useState 함수를 호출하면 파라미터로 전달한 값을 초깃값으로 갖는 상태 변수(state)와 그 변수를 수정할 수 있는 세터 함수(setState)를 배열로 반환한다. useState 함수는 관리해야 하는 상태의 수만큼 여러 번 사용할 수 있다.

✅ 상태를 관리하는 변수는 반드시 세터 함수를 이용해 값을 변경해야 한다.

useState에서 반환된 세터 함수를 사용하는 방법

1️⃣ 하나는 세터 함수에 변경될 상태의 값을 인자로 전달하는 방법이다.

// Counter 컴포넌트
const [count, setCount] = useState(0);

return (
  ...
  <Button
  title="+"
  onPress={() => {
      setCount(count + 1);
      setCount(count + 1);
      ...
    }}/>    
)

💥 하지만 여기서 문제가 발생한다. 세터 함수는 비동기로 동작하기 때문에 상태 변경이 여러 번 일어날 경우 상태가 변경되기 전에 또 다시 상태에 대한 업데이트가 실행되는 상황이 발생한다.

❗ 즉 세터 함수는 비동기로 동작해서 세터 함수를 호출해도 바로 상태가 변경되지 않는 문제가 발생하고, 이런 경우 다음 두 번째 방법을 사용해서 해결할 수 있다. 바로 세터 함수에 함수를 인자로 전달하여 이전 상태값을 이용하는 방법이다.

Counter 컴포넌트에서 + 버튼을 클릭했을 때 세터 함수를 두 번 호출해도 1만 증가된다. 원래 의도라면 2가 증가해야 한다.

2️⃣ 세터 함수를 이용하는 두 번째 방법은 바로 세터 함수에 함수를 인자로 전달하여 이전 상태값을 이용하는 방법이다.

// Counter 컴포넌트
const [count, setCount] = useState(0);

return (
  ...
  <Button
  title="+"
  onPress={() => {
      setCount(prevCnt => prevCnt + 1);
      setCount(prevCnt => prevCnt + 1);
      ...
    }}/>    
)

이렇게 이전 상태의 값에 의존하여 상태를 변경할 경우, 세터 함수에 함수를 인자로 전달하여 이전 값을 이용하도록 작성해야 문제가 생기지 않는다.

2. useEffect

useEffect는 컴포넌트가 렌더링될 때마다 원하는 작업이 실행되도록 설정할 수 있는 기능이다.

예시코드

useEffect(() => {}, []);

useEffect의 첫 번째 파라미터로 전달된 함수는 조건을 만족할 때마다 호출되고, 두 번째 파라미터로 전달되는 배열을 이용해 함수가 호출되는 조건을 설정한다.

두 번째 파라미터의 배열이 어떤지에 따라서 경우를 다음과 같이 나눠서 살펴보겠다.

1️⃣ 두 번째 파라미터에 어떤 값도 전달하지 않는 경우

useEffect의 첫 번째 인자로 전달된 함수는 컴포넌트가 렌더링될 때마다 호출된다.

2️⃣ 특정 상태가 변경될 때만 호출하고 싶은 경우

다음 코드를 보며 알아보겠다.

const Form = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  ...
  useEffect(() => {
    console.log('name: ${name}, email: ${email}');
  }, [email]);
  ...
};
...

위에서 useEffect의 두 번째 파라미터로 email을 지정해 email의 상태가 변경되었을 때만 함수가 실행된다.

3️⃣ 마운트될 때 실행하기

useEffect에 전달된 함수의 실행 조건이 컴포넌트가 마운트(mount)될 때로 설정하려면 두 번째 파라미터로 빈 배열 [ ]을 전달하면 된다.

✅ 잠깐! 여기서 컴포넌트가 마운트된다는 것은 컴포넌트가 처음 렌더링 되는 것을 의미한다. 즉 마운트될 때만 실행시킨다는 것은 최초로 렌더링 될 때 딱 한 번만 함수를 호출하는 것을 의미한다.

4️⃣ 언마운트될 때 실행하기

useEffect에서 전달하는 함수에서 반환하는 함수, 즉 첫 번째 인자에서 return이 호출하는 함수를 정리(cleanup) 함수라고 한다. useEffect의 두 번째 인자가 빈 배열 [ ]일 경우 컴포넌트가 언마운트 될 때 정리 함수를 실행시킨다.

3. useRef

리액트 네이티브에서 특정 컴포넌트를 선택해야 하는 경우에 사용한다. 예를 들어, 컴포넌트로 포커스를 설정하고 싶을 때 useRef를 사용해서 해당 컴포넌트를 선택할 수 있다.

예시코드

const ref = useRef(initialValue);

useRef를 사용할 때 주의할 점은 다음 두 가지이다.

  • 컴포넌트의 ref로 지정하면 생성된 변수에 값이 저장되는 것이 아니라 변수의 .current property에 해당 값을 담는다.
  • useState를 이용하여 생성된 상태와 달리 useRef의 내용이 변경돼도 컴포넌트는 다시 렌더링 되지 않는다.
import React, {useState, useEffect, useRef} from 'react';

const Form = () => {
  ...
  const refName = useRef(null);
  const refEmail = useRef(null);

  useEffect(() => {
    console.log('\n===== Form Component Monut =====\n');
    refName.current.focus();
    return () => console.log('\n===== Form Component Unmount =====\n');
  }, []);

  ...
  return (
    <>
    ...
    <StyledTextInput
      ...
      ref = {refName}
      returnKeyType = "next"
      onSubmitEditing = {() => refEmail.current.focus()}
    />
    <StyledTextInput
      ...
      ref = {refEamil}
      returnKeyType = "done"
    />
);
};
...

위 코드에서 useRef 함수를 사용해서 refNamerefEmail을 생성해 각각 이름과 이메일을 입력받는 TextInput 컴포넌트의 ref로 설정했고, 키보드의 완료 버튼을 각각 nextdone으로 변경했다.
이름을 입력받는 컴포넌트의 확인(next) 버튼을 클릭하면 이메일을 입력받는 컴포넌트로 포커스가 이동하고, Form 컴포넌트가 마운트될 때 포커스가 이름을 입력받는 컴포넌트에 있도록 했다.

4. useMemo

useMemo는 동일한 연산의 반복 수행을 제거해서 성능을 최적화하는 데 사용된다.

예시코드

useMemo(() => {}, []);

첫 번째 파라미터에는 함수를 전달하고, 두 번째 파라미터에는 함수 실행 조건을 배열로 전달한다. 이때 지정된 값에 변화가 있는 경우에만 함수가 호출된다.

다음 코드에서 문자열의 길이를 계산하는 Length 컴포넌트를 만들어보겠다.

const StyledText = styled.Text`
  font-size: 24px;
`;

const getLength = text => {
  console.log('Target Text: ${text}');
  return text.length;
};

const list = ['Javascript', 'Expo', 'Expo', 'React Native'];

let idx = 0;
const Length = () => {
  const [text, setText] = useState(list[0]);
  const [length, setLength] = useState('');

  const _onPress = () => {
    setLength(getLength(text));
    ++idx;
    if(idx < list.length) setText(list[idx]);
  };
  const length = useMemo(() => getLength(text), [text]);

  return (
    <>
      <StyledText>Text: {text}</StyledText>
      <StyledText>Length: {length}</StyledText>
      <Button title="Get Length" onPress={_onPress} />
    </> 
);
};

export default Length;

위에서 Javascript, Expo, Expo, React Native로 이루어진 배열을 생성하고, 버튼을 클릭할 때마다 배열을 순환하며 문자열의 길이를 구하는 컴포넌트를 작성했다. useMemo 함수를 사용해서 text의 값이 변할 때만 getLength 함수를 호출하도록 했다.