[리액트 네이티브] 6장 Hooks

minkyung·2024년 1월 19일
0
post-thumbnail

처음 배우는 리액트 네이티브 - 김범준 정리

6장 Hooks

6.1 useState

컴포넌트 상태를 관리하기 위한 useState

const [count, setCount] = useState(0);
  • 호출 시 변수와 그 변수를 수정할 수 있는 세터 함수를 배열로 반환
  • 호출할 때 전달한 값을 초깃값으로 갖는 상태 변수
  • 여러 번 사용 가능함
  • 변수는 반드시 세터 함수를 이용해 값을 변경해야함
  • 상태가 변경되면 컴포넌트가 변경된 내용을 반영하여 리렌더링 됨

6.1.1 useState 사용하기

src/components/Counter.js

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(count +1);
                }}
            />
            <Button
                title="-"
                onPress={() => {
                    setCount(count -1);
                }}
            />

        </>
    );
};

export default Counter;

숫자 상태를 나타내는 count를 useState로 생성, Button 컴포넌트를 이용하여 클릭마다 세터 함수를 이용해 상태 변경

6.1.2 세터 함수

  1. 세터 함수에 변경될 상태의 값 전달
  2. 세터 함수의 파라미터에 함수를 전달
setState(prevState => {});

전달된 함수에는 변경되기 전 상태값이 파라미터로 전달 됨
이 값을 어떻게 수정할 지 함수로 정의
세터 함수는 비동기로 동작하기 때문에 상태 변경이 여러번 일어날 경우 상태가 변경되기 전에 상태에 대한 업데이트가 일어나서 의도대로 동작하지 않을 수 있음
바로바로 변경 x
이를 해결하기 위해서 세터 함수에 함수를 인자로 전달하여 이전 상태값을 이용할 수 있음

setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);

이렇게 코드를 작성하면 두 번 호출했을 때 count가 의도대로 2번 증가함

6.2 useEffect

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

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

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

6.2.1 useEffect 사용하기

  • 두 번째 파라미터에 어떤 값도 전달하지 않으면 첫 번째 파라미터로 전달된 함수는 컴포넌트가 렌더링 될 때마다 호출
import React, { useState, useEffect } from 'react';
import styled from 'styled-components/native';

const StyledTextInput = styled.TextInput.attrs({
    autoCapitalize: '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(`name: ${name}, email: ${email}\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;


6.2.2 특정 조건에서 실행하기

  • 두 번째 파라미터에 해당 상태를 관리하는 변수를 배열로 전달
  • 그 값이 변할 때마다 호출됨
  • 세터함수가 비동기이므로 값이 변경되면 실행할 함수를 useEffect로 정의
    useEffect(() => {
        console.log(`name: ${name}, email: ${email}\n`);
    }, [email]);

두 번째 파라미터를 email로 지정

email이 변할 때만 log 생김

6.2.3 마운트시 실행

  • 두 번째 파라미터에 빈 배열을 전달하면 컴포넌트가 처음 렌더링될 때만 함수가 호출
    useEffect(() => {
        console.log(`\n===== Form Component Mount =====\n`);
    }, []);

6.2.4 언마운트시 실행

  • useEffect에서 전달하는 함수에서 반환하는 함수를 정리 함수라고 함
  • 실행 조건이 빈 배열인 경우 언마운트 될 때 정리 함수를 실행
    useEffect(() => {
        console.log(`\n===== Form Component Mount =====\n`);
        return () => console.log('\n===== Form Component Unmount =====\n');
    }, []);
const App = () => {
    const [isVisible, setIsVisible] = useState(true);
    return (
    <Container>
        <Button
            title={isVisible ? 'Hide' :'Show'}
            onPress={() => setIsVisible(prev => !prev)}
        />
        {isVisible && <Form />}
    </Container>
    );
};

6.3 useRef

특정 DOM을 선택해야할 때 사용하는 getElementById 처럼 특정 컴포넌트를 선택해야 할 때 사용
예를 들어 컴포넌트로 포커스를 설정하고 싶은 경우

const ref = useRef(initialValue);

주의할 점

  • 컴포넌트의 ref로 지정하면 생성된 변수에 값이 저장되는 것이 아닌 변수의 .current 프로퍼티에 저장
  • useRef는 useState와 달리 내용이 변경되어도 다시 렌더링되지 않음
const Form = () => {
  ...
    const refName = useRef(null);
    const refEmail = useRef(null);
  ...

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

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

    );

};


키보드의 완료 버튼을 각각 next와 done으로 변경하고 마운트 될 때 name 에 포커스가 있도록, next를 누르면 email로 포커스를 옮기도록 설정함

6.4 useMemo

동일한 연산의 반복 수행을 제거하여 성능을 최적화 시킴

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

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

useEffect 사용 전

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]);
    };
    return(
        <>
            <StyledText>Text: {text}</StyledText>
            <StyledText>Length: {length}</StyledText>
            <Button title="Get Length" onPress={_onPress} />
        </>
    );
}

export default Length;

useEffect 사용 후

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

export default Length;

useMemo를 사용해 text의 값이 변경되었을 때만 text의 길이를 구함
중복 연산을 방지함으로써 성능을 최적화

6.5 커스텀 Hooks 만들기

Fetch를 사용하여 useFetch라는 이름의 Hook 만들기
useFetch.js

import { useState, useEffect } from "react";

export const useFetch = url => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

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

       return {data, error};
    };

useEffect에 비동기함수를 첫 번째 파라미터로 전달할 경우 함수 내부에 비동기함수를 정의하고 사용하는 방법으로 경고를 방지할 수 있음
Dog.js

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

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

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

    return(
        <>
            <StyledImage source={data?.message ? {uri: data.message} : null} />
        </>
    );
};

export default Dog;

이때 data?.message 는 옵셔널 체이닝 문법으로 data 속성이 있을 경우에만 접근하여 TypeError를 방지하며 안전하게 접근하는 문법임

강아지 api
Dogs API https://dog.ceo/api/breeds/image/random


useFetch.js

...
    const [inProgress, setInProgress] = useState(false);
...
    useEffect(() => {
       const fetchData = async () => {
        try {
            setInProgress(true);
			...
        } catch (error){
            setError(error);
        } finally {
            setInProgress(false);
        }
        };
        fetchData();
       }, []);

       return {data, error, inProgress};
    };

Dog.js

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

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

    return(
        <>
            {inProgress && (
                <Loading>The API request is in progress</Loading>
            )}
...
        </>
    );
};

export default Dog;

api 요청 중 로딩 메세지를 띄워주도록 코드 추가

profile
개발/보안 기록용

0개의 댓글