IT_World

[React] 함수형 컴포넌트 본문

Front-End/React

[React] 함수형 컴포넌트

engine 2022. 4. 14. 16:15

함수형 컴포넌트란 함수를 기반으로 작성하는 컴포넌트를 말한다.기존에 우리가 사용했던 클래스형 컴포넌트에 비해 훨씬 짧고 직관적인 코드를 짤 수 있다.아래에서 나오는 Hooks가 도입되면서 함수형 컴포넌트에서도 클래스형 컴포넌트의 라이프 사이클 메서드와 같은 기능을 사용할 수 있게 되었다.

Hooks 란?

리액트 v16.8 로 업데이트되면서 추가된 기능으로서,함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능 등을 제공한다.

useState

함수형 컴포넌트에서도 가변적인 상태를 지니고 있을 수 있게 해준다.함수형 컴포넌트 안에서 상태를 관리해야 하는 일이 생기면 이 Hooks를 이용하면 된다.

js 파일 내에서

import React, { useState } from 'react'; // import 로 useState 를 불러옴

const SayLove = () => { // 함수형 컴포넌트 start
  const [value, setValue] = useState(0);
  const [isModalActive, setIsModalActive] = useState(false);

  return (
    <div>
      <p>
        <b>{value}</b> 만큼 사랑합니다...
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
      
      <button onClick={() => setIsModalActive(!isModalActive)}>
        modal btn
      </button>
    </div>
  );
};

export default SayLove;

:: 배열 비구조화 할당

배열 비구조화 할당의 쉬운 예시

const array = [1, 2];
const [one, two] = array;
console.log(one, two); // 1, 2
const [value, setValue] = useState(0);

예시에서 앞의 이 코드는 배열 비구조화 할당(객체 비구조화 할당이랑 비슷)

useState(0) 도 결국은 함수 뒤에 (0) 은 파라미터인데, 상태의 기본값위의 예시에서는 SayLove 컴포넌트의 기본 상태값을 0으로 설정해준것

이 useState(0) 함수는 실행후에 배열을 반환하는데, 이 배열의 첫번째값 value 와, 두번째값 setValue 을 배열 비구조화 할당을 이용해서 따로 선언해준다.

이때 [value, setValue] 는

  • value : 원소의 현재 상태 값
  • setValue : 상태를 설정하는 Setter 함수 이다.
const [value, setValue] = useState(0); // 는

const numberState = useState(0);
const number = numberState[0];
const setNumber = numberState[1]; // 이 3줄과 같음

즉, useState(0) 함수에 파라미터를 넣어서 호출하면 전달받은 파라미터로 값이 바뀌게 되고, 컴포넌트는 정상적으로 리렌더링 된다.

useState 여러번 사용하기

만약 컴포넌트에서 관리해야 할 상태가 여러 개라면 어떻게 해야 할까?

그냥 useState를 여러번 쓰는 방법이 있고, 하나의 useState 를 설정한 뒤, 여러개의 상태값을 관리하는 방법이 있다.

useState 를 여러번 쓰는 방법은, js 파일 내에서

const Info = () => {
  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>
  );
};

이렇게 정말 여러개 써주는거고

useState 하나에 여러 상태를 관리하는 방법은, js 파일 내에서

import React, { useState } from 'react';

const Info = () => {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });

  const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출

  const onChange = (e) => {
    const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
    setInputs({
      ...inputs, // 기존의 input 객체를 전개 구문으로 펼쳐서 /복사한 뒤
      [name]: value // name 키를 가진 값을 value 로 설정 (이때 [name]은 계산된 속성명 구문 사용)
    });
  };

  const onReset = () => {
    setInputs({
      name: '',
      nickname: '',
    })
  };

  return (
    <div>
      <input name="name" placeholder="이름" onChange={onChange} value={name} />
      <input name="nickname" placeholder="닉네임" onChange={onChange} value={nickname}/>
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
}

이렇게 useState 의 파라미터로 객체를 전달한다.

이때 주의할 점은, 리액트의 state에서 객체를 수정해야 할 때에는

inputs[name] = value;

이렇게 직접 수정하면 안되고,

setInputs({
  ...inputs,
  [name]: value
});

이렇게 새로운 객체를 만들어서 새로운 객체에 변화를 주는 식으로 사용해야 한다.

아직은 어려운 개념이긴 한데.... react 의 불변성을 지키기 위해서 라고 한다.불변성을 지켜야지만, 리액트 컴포넌트에서 상태가 업데이트가 됐음을 감지 할 수 있고 이에 따라 필요한 리렌더링이 진행된다.inputs[name] = value 이렇게 기존 상태에 직접 접근해서 수정하면, 값이 바뀌어도 리렌더링 되지 않는다.또한, 불변성을 지켜야지만 컴포넌트의 업데이트 성능 최적화를 제대로 할 수 있다.

 

useEffect

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

쉽게 말하면, 클래스형 컴포넌트의 componentDidMount + componentDidUpdate 를 합친 형태라고 이해하면 된다.

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

const Info = () => {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });
  useEffect(() => { // useEffect 적용
    console.log('렌더링이 완료되었습니다');
    console.log({
      name,
      nickname
    });
  });

  const { name, nickname } = inputs;

  const onChange = (e) => {
    const { value, name } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  };
  

  const onReset = () => {
    setInputs({
      name: '',
      nickname: '',
    })
  };

  return (
    <div>
      <input name="name" placeholder="이름" onChange={onChange} value={name} />
      <input name="nickname" placeholder="닉네임" onChange={onChange} value={nickname}/>
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
}

export default Info;

useEffect 컴디마처럼 사용하기

useEffect 에서 설정한 함수를 componentDidMount 처럼 사용하고 싶을때

즉, 컴포넌트가 화면에 가장 처음 렌더링 될 때만 실행되야 할 경우, (업데이트 때는 실행 할 필요가 없는 경우) 에는 함수의 두번째 파라미터로 비어있는 배열을 넣으면 된다.

위의 예시에서,

useEffect(() => {
  console.log('렌더링이 완료');
  console.log({
    name,
    nickname
  });
});

// useEffect 안에 콜백 함수를 넣어준다.
useEffect(() => {
    console.log('마운트 될 때만 실행됩니다.');
  }, []); // [] 의 값이 바뀔때마다 계속 실행, 근데 빈배열은 즉 처음 한번만 실행된다.

이렇게 바꿔주면 끝

 꼭 [] 을 넣어야만 하는건 아니다.
두번째 파라미터의 값이 변경될 때 첫번째 파라미터가 실행되는것이므로,
두번째 파라미터에 불변값을 넣어주면 되는데, 실제로 위 코드를

useEffect(() => {
    console.log('마운트 될 때만 실행됩니다.');
  }, "hello");

이렇게 작성하거나, "hello" 자리에 숫자등의 기본형 데이터 타입을 넣어도 잘 동작한다.
하지만, [] 빈 배열을 작성하는것이 컨벤션이므로 왠만하면 뻘짓하지 말고 [] 쓰기

 fetching data

useEffect를 이용해서 data를 받아오기

useEffect( () => {
	fetchInitialData(); // useEffect 안에서 바로 fetch를 사용하지 말고, fetch 역할의 함수를 실행할것
} )
const fetchInitialData = async () => {
	const res = fetch('URL주소');
	const initialData = await res.json();
	setDatas(initialData);
}

setDatas(initialData) 를 통해 datas 상태에 초기값으로, fetch를 통해 받아온 데이터를 설정한다.

근데 이렇게만 하면, 렌더링이 계속계속계속 무한으로 반복된다.

왜? useEffect는 렌더링 된 직후에 실행되는데, 이 때 data를 fetch로 받아와서 초기 state를 setDatas 해주었고, setState가 일어나면 다시 렌더링 되기 때문에, 렌더링 직후에 실행되는 useEffect 가 또 실행되는것

그러니까 위의 예시처럼, 두번째 인자에 빈배열을 추가해준다.

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

loading data

실제 데이터가 받아와지는 동안 딜레이가 생기는 경우가 많다.

이때, loading 값을 관리하여 예쁜 로딩바나 로딩 애니메이션으로 사용자 경험을 높일 수 있다.

const [loading, setLoading] = useState(false);

loading state를 추가해주고,

const fetchInitialData = async () => {
	setLoading(true); // data를 받아오기 시작할 때 loading 값을 true로
	const res = fetch('URL주소');
	const initialData = await res.json();
	setDatas(initialData);
	setLoading(false); // data를 받아오는게 끝날 때 loading 값을 false로
}

data를 받아오는 fetchInitialData 메서드에 다음과 같이 setLoading을 실행한다.

그리고 로딩창을 보여줄 component의 return 위에

let toDoList = 로딩 화면
if (!loading) toDoList = 로딩이 끝나면 보여줄 내용

이라고 작성해준다.

useEffect 컴디업처럼 사용하기

useEffect 에서 설정한 함수를 componentDidUpdate 처럼 사용하고 싶을때

즉, 컴포넌트의 특정 값이 변경될 때만 useEffect 를 호출하고 싶을 때에는 함수의 두번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 넣으면 된다.

위의 예시에서,

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

useEffect(() => {
    console.log(name);
  }, [name]); // [name] : [name] 값이 바뀔 때만 실행, 즉 처음 한번만 실행된다.

이렇게 바꿔주면 끝

참고로, [name] 이라는 배열 안에는 useState 를 통해 관리하고 있는 상태를 넣어줘도 되고, props 로 전달받은 값을 넣어줘도 된다.

참고로 위의 코드를 클래스형 컴포넌트에서 쓴다면 이렇게 쓸 수 있다.

componentDidUpdate(prevProps, prevState) {
  if (prevProps.value !== this.props.value) {
    doSomething();  
  }
}

:: useEffect 로 비동기 처리 하기

handleBtnColor = () => {
	this.setState({
		color: "red"
	}, () => console.log(this.state.color))
}

class형 component에서는 이렇게 작성했다.

( state의 color 값이 바뀔때마다 console.log 함수를 실행)

const [color, setColor] = useState("blue")

const handleBtnColor = () => {
	setColor("red")
}

useEffect(() => {
	console.log(color)
}, [color])

function형 component에서는 이렇게 작성할 수 있다.

'Front-End > React' 카테고리의 다른 글

[React] React에 대해 정리2  (0) 2022.05.19
[React] React에 대해 정리  (0) 2022.05.19
[React] Signin form validation check 적용  (0) 2022.04.14
[React] Login / Password  (0) 2022.04.14
[React] Webhook  (0) 2022.04.12
Comments