React의 함수형 컴포넌트! (feat.Hooks)

Solmii·2020년 7월 7일
21

React

목록 보기
9/11
post-thumbnail

wecode 2차 프로젝트가 시작되었다!!!!
2차 프로젝트로는 내가 의견 낸 라네즈 사이트를 클론하게 되었다!!!

(참고로 우리팀 이름은 마요네즈........ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ)

내가 맡은 기능은 로그인(소셜 로그인 기능), 회원가입, 브랜드 소개 페이지, 장바구니, 리뷰 게시판이당!
여기까지 빨리 끝낼 수 있다면 다음 2순위 (매장 찾기, 예약 페이지 등...) 중에 골라서 해보기로!!

그리고 이번 2차 프로젝트 부터는 기존대로 class형 component과 scss 파일을 사용해도 되고, function형 component(feat.Hooks), styled component 를 공부해서 사용해도 되는데, 나는 뭐 당연히 후자 도전~!!!

이 자는 몸의 70%가 도전으로 이루어져 있다.


함수형 컴포넌트란?

이름에서부터 알 수 있듯이, 함수형 컴포넌트란 함수를 기반으로 작성하는 컴포넌트를 말한다.
기존에 우리가 사용했던 클래스형 컴포넌트에 비해 훨씬 짧고 직관적인 코드를 짤 수 있고, 함수형 프로그래밍을 할 수 있게 해준다.
아래에서 나오는 Hooks가 도입되면서 함수형 컴포넌트에서도 클래스형 컴포넌트의 라이프 사이클 메서드와 같은 기능을 사용할 수 있게 되었다.
react 공식 문서에서도 함수형 컴포넌트 + Hooks 조합을 추천하고 있으며, 아직 낯설고 어려운 개념이긴 하지만 2차 프로젝트는 함수형 컴포넌트 + Hooks + styled component로 진행해보려고 한다!


Hooks 란?

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


1. useState

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

js 파일 내에서

import React, { useState } from 'react'; // import 로 useState 를 불러온다!

const SayLove = () => { // 함수형 컴포넌트 시작~!
  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 이렇게 기존 상태에 직접 접근해서 수정하면, 값이 바뀌어도 리렌더링 되지 않는다.
또한, 불변성을 지켜야지만 컴포넌트의 업데이트 성능 최적화를 제대로 할 수 있다.


2. 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;


3. 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 = 로딩이 끝나면 보여줄 내용

이라고 작성해준다!


4. 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에서는 이렇게 작성할 수 있다!!


개발 왕초보 코린이입니다!
이 내용은 각종 강의&구글링을 통해 배운 내용을 정리하는 것으로, 제가 이해하고 넘어간 개념이 틀렸거나 더 보충할 개념이 있다면 댓글 남겨주시면 정말 감사하겠습니다!!

profile
하루는 치열하게 인생은 여유롭게

3개의 댓글

comment-user-thumbnail
2020년 7월 11일

이번에 React에 대해서 공부하는 중인데, 제가 필요한 부분을 잘 설명해주셔서 좋은 것 같습니다.
감사합니다!

답글 달기
comment-user-thumbnail
2021년 1월 31일

좋은 글 잘 읽었습니다 감사합니다!

답글 달기
comment-user-thumbnail
2021년 2월 10일

계속 며칠동안 고민하던 문제가 이 글을보고 바로 해결이 됐네요 너무 감사합니다 !

답글 달기