솜이의 데브로그

[React Native] Hooks 본문

dev/React native

[React Native] Hooks

somsoming 2021. 11. 12. 01:52

Reference : 처음 배우는 리액트 네이티브

 

Hooks

리액트 네이티브 16.8 버전에서 새롭게 추가된 기능이다.

컴퓨넌트의 상태를 관리하거나 생명 주기에 따라 특정 작업을 수행하려면 클래스형 컴포넌트를 사용해야 했다.

하지만 Hooks를 이용할 수 있게 되면서 함수형 컴포넌트에서도 상태를 관리할 수 있게 되었다.

 

 

useState

  • useState 함수를 호출하면 변수와 그 변수를 수정할 수 있는 세터 함수를 배열로 반환한다.
  • 파라미터로 전달한 값을 초깃값으로 갖는 상태 변수와 그 변수를 수정할 수 있는 세터 함수를 배열로 반환.
  • 상태를 관리하는 변수는 반드시 세터 함수를 이용해 값을 변경해야 하고, 상태가 변경되면 컴포넌트가 변경된 내용을 반영하여 다시 렌더링한다.

 

src/components/Counter.js

  • 숫자를 나타내는 count 생성
  • Button 컴포넌트를 이용해 클릭될 때마다 세터함수를 이용해 상태를 변경

 

세터 함수
  • 세터 함수의 파라미터에 함수를 전달 할 수 있다.
  • 세터 함수는 비동기로 동작하기 때문에 상태 변경이 여러 번 일어날 경우 상태가 변경되기 전에 또 다시 상태에 대한 업데이트가 실행된다.
  • 상태에 대해 여러 업데이트가 함께 발생할 경우, 세터 함수에 함수를 인자로 전달하여 이전 상태값을 이용한다.
  • 이전 상태의 값에 의존하여 상태를 변경할 경우 세터함수에 함수를 인자로 전달한다.
import React, { useState } from "react";
import styled from 'styled-components/native';
import Button from "./Button";

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

const Counter = () => {
    const [count, setCount] = useState(0);

    return(
        <>
            <StyledText>count: {count}</StyledText>
            <Button
                title="+"
                onPress={()=> {
                    setCount(prevCount => prevCount+1);
                    setCount(prevCount => prevCount+1);
                }}
            />
            <Button
                title="-"
                onPress={()=>{
                    setCount(count-1);
                }}
            />
        </>
    );
};

export default Counter;

 

 

useEffect

  • useEffect의 첫 번째 파라미터로 전달된 함수는 조건을 만족할 때마다 호출되고, 두 번째 파라미터로 전달되는 배열을 이용해 함수가 호출되는 조건을 설정할 수 있다.
  • 두번째 파라미터에 값을 전달하지 않으면 첫번째 파라미터로 전달된 함수는 컴포넌트가 렌더링 될 때마다 호출된다.
  • TextInput 컴포넌트를 이용해서 이메일과 이름을 입력받는 컴포넌트를 만들고, useEffect를 사용해서 컴포넌트와 다시 렌더링 될 때마다 name과 email을 출력하도록 한다.

 

특정 조건에서 실행하기
  • useEffect에 설정한 함수를 특정 상태가 변경될 때만 호출하고 싶은 경우, 두번째 파라미터에 해당 상태를 관리하는 변수를 배열로 전달한다.
  • 세터함수가 비동기로 작동하므로 상태의 값이 변경되면 실행할 함수를 useEffect를 이용해서 정의한다.
  • 두번째 파라미터로 [email] 을 전달하면 email의 상태가 변경되었을 때만 함수가 실행되도록 한다.
  • 두번째 파라미터에 빈 배열을 전달하면 컴포넌트가 처음 렌더링 될 때만 함수가 호출되도록 작성할 수 있다.

 

src/components/Form.js

import React, { useState, useEffect } from "react";
import styled from "styled-components/native";

const StyledTextInput = styled.TextInput.attrs({
    autoCapitalized: 'none',
    autoCorrect: false,
})`
    border: 1px solid #757575;
    padding: 10px;
    margin: 10px 0;
    width: 200px;
    font-size: 20px;
`;

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

const Form = () => {
    const[name, setName] = useState('');
    const[email, setEmail] = useState('');

    useEffect(()=> {
        console.log(`\n======Form Component Mount=======\n`);
        return() => console.log('\n========Form Component Unmount========\n');
    }, []);

    return(
        <>
            <StyledText>Name: {name}</StyledText>
            <StyledText>Email: {email}</StyledText>
            <StyledTextInput
                value={name}
                onChangeText={text => setName(text)}
                placeholder="name"
            />
            <StyledTextInput
                value={email}
                onChangeText={text => setEmail(text)}
                placeholder="email"
            />
        </>
    );
};

export default Form;

 

 

useRef

  • 리액트 네이티브에서 특정 컴포넌트로 포커스를 설정하고 싶은 경우 해당 컴포넌트를 선택할 수 있다. 
  • 이 때 Hook 함수 중 useRef를 사용한다.
  • 컴포넌트의 ref로 지정하면 생성된 변수에 값이 저장되는 것이 아니라 변수의 .current 프로퍼티에 해당 값을 담는다.
  • useState를 이용하여 생성된 상태와 달리 useRef의 내용이 변경돼도 컴포넌트는 다시 렌더링 되지 않는다.
  • useRef 함수를 이용하여 refName과 refEmail 을 생성해 각각 이름과 이메일을 입력받는 TextInput 컴포넌트의 ref로 설정하고, 키보드의 완료 버튼을 각각 next와 done으로 변경한다.
  • next 버튼을 누르면 이메일을 입력받는 컴포넌트로 포커스가 이동하도록 작성한다.

 

 

useMemo

  • 동일한 연산의 반복 수행을 제거해서 성능을 최적화하는데 사용한다.
  • 첫 번째 파라미터에 함수를 전달하고, 두 번째 파라미터에는 함수 실행 조건을 배열로 전달한다.

src/components/Length.js

import React, { useState, useMemo } from "react";
import styled from 'styled-components/native';
import Button from "./Button";

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 _onPress = () =>{
        ++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;

 

  • 버튼을 클릭할 때마다 문자열의 길이를 구하는 컴포넌트
  • useMemo를 사용하여 문자열의 변화가 없는 경우 반복하지 않도록 한다.

 

 

커스텀 Hooks 만들기

  • 특정 API에 GET 요청을 보내고 응답을 받는 함수를 만들어보자.
  • 리액트 네이티브에서는 네트워크 통신을 위해 Fetch와 XMLHttpRequest를 제공하고, WebSocket도 지원한다.
  • Fetch를 이용하여 useFetch라는 이름의 Hook을 만든다.

 

src/hooks/useFetch.js

import { useState, useEffect } from "react";

export const useFecth = url => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    const [inProgress, setInProgress] = useState(false);

    useEffect(async () => {
        const fetchData = async () => {
            try{
                setInProgress(true);
                const res = await fetch(url);
                const result = await res.json();
                if (res.ok){
                    setData(result);
                    setError(null);
                } else{
                    throw result;
                }
            } catch(error){
                setError(error);
            } finally{
                setInProgress(false);
            }
        };
        fetchData();
    }, []);

    return {data, error, inProgress};
};

 

  • 전달받은 API의 주소로 요청을 보내고 결과의 성공 여부에 따라 data 혹은 error에 담아서 반환하는 useFecth를 작성한다.
  • 비동기 함수를 이용해야 하는 상황에서는 useEffect에 전달되는 함수 내부에 비동기 함수를 정의하고 사용해야한다.
  • API의 진행 상태를 관리하는 inProgress를 만들고, API 요청 시작 전과 완료 후 상태를 변경해서 useFetch의 API 진행상태를 확인하도록 한다.

 

src/components/Dog.js

 

import React from "react";
import styled from "styled-components/native";
import { useFecth } from "../hooks/useFetch";

const StyledImage = styled.Image`
    background-color: #7f8c8d;
    width: 300px;
    height: 300px;
`;

const ErrorMessage = styled.Text`
    font-size: 18px;
    color: #e74c3c;
`;

const LoadingMessage = styled.Text`
    font-size: 18px;
    color: #2ecc71;
`;

const URL = 'https://dog.ceo/api/breeds/image/random';
const Dog = () => {
    const {data, error, inProgress} = useFecth(URL);

    return(
        <>
            {inProgress && (
                <LoadingMessage>The API request is in progress</LoadingMessage>
            )}
            <StyledImage source={data?.message ? { uri: data.message }: null} />
            <ErrorMessage>{error?.message}</ErrorMessage>
        </>
    );
};

export default Dog;
  • Dogs API를 이용해 무작위로 강아지 사진을 받아오는 컴포넌트를 생성한다.

 

 

Hooks는 클래스형 컴포넌트를 사용하지 않아도 함수형 컴포넌트에서 상태를 관리하고 다양한 상황에 맞춰 작업할 수 있게 해주는 기능이다.

함수형 컴포넌트는 이를 이용해 클래스형 컴포넌트에서 가능했던 거의 대부분의 기능을 구현할 수 있다.