React-Hooks

huni_·2022년 7월 5일
0

React

목록 보기
8/57
post-thumbnail

함수형 컴포넌트가 클래스형 컴포넌트보다 훨씬 간단합니다.

하지만, 함수형 컴포넌트 그 자체만으로는 클래스형 컴포넌트의 모든 기능을 흉내낼 수 없습니다.

그래서 React 에서 함수형 컴포넌트에서도 클래스형 컴포넌트와 동일한 기능을 사용 가능하도록 도구를 만들어 주었습니다. 이 도구를 Hooks(훅) 이라고 부릅니다.

대표적인 Hooks 에는 useState, useEffect가 있습니다.

💡 잠시만요!

useState, useEffect가 무엇인지는 이후에 나오게 됩니다.
지금은 이러한 Hooks이 있기 때문에, 간단한 함수형 컴포넌트로 클래스형 컴포넌트를 대신할 수 있다는 것만 이해하고 넘어가시면 됩니다.


state 친구들 (state, setState, useState)

state란 리액트 컴포넌트에서 데이터를 담기 위한 상자입니다.

우리는 자바스크립트에서 데이터를 담기 위한 상자로 변수를 배웠습니다.

다시 말해, state는 컴포넌트에서 사용하는 변수입니다.

💡state, setState, useState

state: 컴포넌트에서 사용하는 변수
setState: 컴포넌트에서 사용하는 변수를 바꿔주는 기능
useState: 컴포넌트에서 사용하는 변수를 만들어주는 기능

2. 리액트에서 let을 안쓰고 state를 변수로 사용하는 이유

리액트 컴포넌트는 앞쪽에 화면에 보여지는 부분과 뒷쪽에 데이터를 관리하는 부분으로 나눌 수 있습니다.

만약, 단순히 자바스크립트 변수 let(또는 상수 const) 를 사용해서 좋아요를 화면에 그렸다면, 좋아요가 증가해서 17로 변경되었을 때, 뒷쪽의 데이터 부분만 변경되고, 앞쪽 화면에는 반영이 되지 않습니다.

하지만, 컴포넌트 변수 state를 사용해서 화면에 그리고, setState()를 사용해서 좋아요를 변경하면, setState() 안에서 화면을 새롭게 그리라는 명령이 실행되어 변경된 데이터가 화면에 새로 그려집니다.

// let으로 count 예제 실험해보기
function New() {
    let count = 0     // let으로 자바스크립트 변수 만들기
    
    function handleClick() {
        count = count + 1     // 갯수는 증가했지만, 화면에는 **반영이 안됨**
    }

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={handleClick}>let을 사용하여 count 증가</button>
        </div>
    )
}

export default New

// state로 count 예제 실험해보기
import { useState } from 'react'

function New() {
    const [count, setCount] = useState(0)     // state로 **컴포넌트 변수** 만들기
    
    function handleClick() {
        setCount(count + 1)     // 갯수가 증가하면서, 화면에 정상적으로 **반영됨**
    }

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={handleClick}>state를 사용하여 count 증가</button>
        </div>
    )
}

export default New

💡 실무적용 - {state, setState, useState} 언제 사용하나요?

1) 회원가입, 게시물작성 내용 등을 서버컴퓨터에 전송하기위해 변수에 담아둘 때 사용합니다.
2) 작성한 내용을 검증하고 잘못된 부분을 빨간색으로 표기할 때 사용합니다.

1. 서버컴퓨터에 전송하기위해 state에 담아두기

import { useState } from "react"

function Login () {
    const [id, setId] = useState('')
    const [pw, setPw] = useState('')

    function handleChangeId(event) {
        const value = event.target.value
        setId(value)
    }

    function handleChangePw(event) {
        const value = event.target.value
        setPw(value)
    }

    return (
        <div>
            <h1>로그인</h1>
            <div>아이디 {id}</div>
            <input type="text" onChange={handleChangeId}/>
            <div>비밀번호 {pw}</div>
            <input type="text" onChange={handleChangePw}/>
        </div>
    )
}

export default Login

2. 작성한 내용을 검증하고 잘못된 부분을 빨간색으로 표기하기

import { useState } from "react"

function Login () {
    const [id, setId] = useState('')
    const [pw, setPw] = useState('')
		const [errorId, setErrorId] = useState('')
    const [errorPw, setErrorPw] = useState('')

    function handleChangeId(event) {
        const value = event.target.value
        setId(value)
    }

    function handleChangePw(event) {
        const value = event.target.value
        setPw(value)
    }

		function handleClickLogin() {
        if(id === "") {
            setErrorId('! 아이디를 정확히 입력해 주세요.')
        }
        if(pw === "") {
            setErrorPw('! 비밀번호를 정확히 입력해 주세요.')
        }
        if(id !== "" && pw !== ""){
            alert('아이디와 비밀번호가 모두 입력되었습니다. 로그인을 시작합니다.')
        }
    }

    return (
        <div>
            <h1>로그인</h1>
            <div>아이디 {id}</div>
            <input type="text" onChange={handleChangeId}/>
						<div style={{color: 'red'}}>{errorId}</div>
            <div>비밀번호 {pw}</div>
            <input type="text" onChange={handleChangePw}/>
						<div style={{color: 'red'}}>{errorPw}</div>
						<div>
                <button onClick={handleClickLogin}>로그인</button>
            </div>
        </div>
    )
}

export default Login

state 리렌더

리액트가 리렌더하는 방식

setstate는 비동기로 작동 합니다.

setState가 동기로 작동하게되면 변경될때마다 바로 바로 렌더링을 하기 때문에 비효율적입니다.

setState는 state가 변경되지 않으면 무시합니다.

즉 값이 변경되지 않으면 렌더링이 되지 않고 다음 코드로 넘어갑니다.

따라서 임시 저장소에 모아두었다가 코드를 끝까지 읽고 한번에 바꿔서 렌더링합니다.

레벨업!

💡 리렌더가 되는 상황

  1. 새로운 props가 들어올 때
  2. 부모 컴포넌트가 렌더링 될 때
  3. 강제 업데이트(forceUpdate)가 실행될 때
  4. state가 변경될 때

레벨업!

💡 setstate의 비동기적 특성

아래의 버튼을 클릭하면 어떤값이 나올까요?
→ 정답은 1입니다.
이는 setState가 비동기적 특성을 갖기때문입니다.
(동기적으로 작동했다면 코드를 읽고 바로 내려가서 또 1을 더하고 또 1을 더해 3이 나왔을 것 입니다.)

export default function stateTest(){
const [value,setValue]=useState(0)

const onClick = () => {
  setValue(value+1)
  setValue(value+1)
  setValue(value+1)
}

return (
  <div className="App">
    <button onClick={onClick}>+</button>
    <h1>{value}</h1>
  </div>);
}

State와 Prev

이전에 배운 State에 대해서 기억하시나요?

State란 리액트 컴포넌트에서 데이터를 담기 위한 상자라고 했었습니다. 이 상자에 담긴 내용들은 함수가 모두 끝나게 되면 화면에 반영이 되는데요. State를 이해하기 위해 만든 counter를 다시 보겠습니다.

import { useState } from 'react'

function New() {
    const [count, setCount] = useState(0)
    
    function handleClick() {
        setCount(count + 1)     // 갯수가 증가하면서, 화면에 정상적으로 **반영됨**
    }

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={handleClick}>state를 사용하여 count 증가</button>
        </div>
    )
}

export default New

위 코드를 화면에 출력해보면 버튼을 클릭할 때 마다 갯수가 하나씩 증가하면서 정상적으로 반영됩니다.

하지만, count를 5개씩 증가시켜주기 위해 코드가 아래와 같이 바뀌게 된다면 어떻게 될까요? 우리가 의도한대로count가 5개씩 증가될까요?

function handleClick() {
   setCount(count + 1)      // 현재 count는 0 + 1 => 1을 상자에 담는다.
   setCount(count + 1)      // 1 이 상자에 담겼지만 여전히 count는 0 이므로 1이 상자에 담긴다
   setCount(count + 1)      // ...
   setCount(count + 1)      // ...
   setCount(count + 1)      // 같은 방식으로 최종적으로 1이 상자에 담겨 화면에 1이 반영된다
}

위와 같은 로직에 따라 count가 하나씩만 증가되게 됩니다. 우리가 의도한대로 count가 5개씩 증가되도록 하려면 prev라는 임시저장공간을 사용하여 작성해야합니다.

function handleClick() {
   setCount((prev)=>prev+1)     // 임시저장공간의 0(기본값, prev) + 1 => 1을 상자에 담는다.
   setCount((prev)=>prev+1)     // 임시저장공간에 담긴 1(prev) + 1 => 2를 상자에 담는다.
   setCount((prev)=>prev+1)     // ...
   setCount((prev)=>prev+1)     // ...
   setCount((prev)=>prev+1)     // 같은 방식으로 최종적으로 5가 상자에 담겨 화면에 5가 반영된다
}

이렇게 prev를 사용하시게되면 임시 저장공간에 있는 값을 먼저 꺼내오고, 만약 임시 저장공간에 있는 값이 없다면 기본 값을 불러오게 됩니다.

prev를 사용하시게되면 우리가 의도한대로 화면에 반영함과 동시에, 코드를 리팩토링하는데도 효율적이기 때문에 잘 활용해주시면 좋겠습니다.


💡 Fragments

React에서 DOM에 별도의 노드를 추가하지 않고 여러 자식을 그룹화 할 수 있는 태그입니다.
사용 시 key가 없다면 <></> 처럼 빈 태그로 사용할 수 있지만, 만약 key가 있다면 <Fragment></Fragment> 문법으로 명시적으로 선언해주어야합니다.

💡 input tag의 readOnly 속성에 data?.fetchBoard.writer(string)를 넣어주었는데 타입스크립트 오류가 납니다.

기본적으로 input tagreadOnly 속성은 boolean 타입입니다.
때문에 이 타입이 string 타입이 되도록 만들어주어야합니다. 이 문제를 해결하기위해 이중부정연산자를 사용할 수 있습니다.
data?.fetchBoard.writerstring 타입이지만 boolean 타입으로 보면 string이 있기 때문에 true 입니다. 여기에 !data?.fetchBoard.writer 해주시면 false가 되고, 그럼 우리가 할당해준 데이터를 그대로 가져오고싶은 경우에는 !!data?.fetchBoard.writer 처럼 이중부정연산자를 사용하시면 true인 상태 그대로 데이터를 가져올 수 있겠죠? 상황에 따라 부정연산자와 이중부정연산자(!!)를 잘 활용해보시길 바랍니다!


함수형 컴포넌트의 생명주기 훅 useEffect

클래스형 컴포넌트에는 componentDidMout와 같은 생명주기 메서드들이 있습니다.

그렇다면 함수형 컴포넌트에서의 생명주기관련 훅은 무엇일까요?

바로 useEffect 입니다.

useEffect

componentDidMount

// 의존성 배열[]에 아무것도 넣지 않으면 Mount시에만 렌더해주고 끝나게 됩니다.(1번만 실행)
useEffect(()=>{
	console.log("마운트 됨!!")
},**[]**)

componentDidUpdate와 비슷

// 의존성 배열이 없기 때문에 뭐 하나라도 바뀌면 무조건 다시 실행됩니다.
useEffect(()=>{
	console.log("수정하고 다시 그려짐!!")
})
// someState가 수정될때만 리렌더 해주기
useEffect(()=>{
	console.log("수정하고 다시 그려짐!!")
},[someState])

componentWillUnmount

useEffect(()=>{
	console.log("수정하고 다시 그려짐!!")
	//이부분이 끝나고 진행할 것들
	return()=>{
		console.log("여기서 나갈래요!!")
	}
},[])

componentDidUpdate와 비슷하지만 다른점 하나는, Mount 되자마자 실행되는 점입니다.

💡 useEffect의 실행 시점
생명주기 메서드,훅 은 기본적으로 렌더(화면그리기) 이후에 실행됩니다.
**따라서 useEffect와 lifecycle 메서드는 렌더 이후에 실행됩니다.**

useEffect 사용시 주의 사항

💡 useEffect 안에서 setState의 사용

→ useEffecrt 내에서 setState를 사용할때는 정말 필요한 경우가 아니라면 지양하시는게 가장 좋습니다.
컴포넌트가 마운트된 이후에 setState를 적용하게 되면,
1. state가 변경되고,
2. 변경된 state로 컴포넌트가 다시 그려지게(=리렌더) 됩니다.
즉, useEffecrt 내에서 setState를 사용하게 되면 불필요한 리렌더나 무한루프를 일으키게 되고 성능면에서 비효율적이게 됩니다.


폼 라이브러리(react-hook-form)

우리가 여태까지 힘들게 만들었던 폼, 사실은 폼을 대신 만들어주는 도구가 있습니다.

react-form, redux-form, react-hook-form, formik 등 그 종류도 굉장히 다양합니다.

함수형 컴포넌트와 hook을 사용하는 경우, 가장 사용하기 쉽고 성능적으로 좋은 폼은 react-hook-form 입니다.

react-hook-form_homepage
react-hook-form_github

폼 라이브러리를 사용하면 하나하나 직접 만들지 않고, 밸리데이션 체크 및 폼 관리 등을 좀 더 깔끔하게 할 수 있습니다.


Custom Hooks

지금까지 우리는 useState, useContext, useEffect, useRef 등 다양한 react 내장 Hook을 사용해 왔습니다. 그렇기에 함수형 컴포넌트에서 stateLifeCycle 관리가 가능하였습니다.

Custom Hook이란 이름 그대로 개발자가 스스로 커스텀 한 훅을 말합니다. 커스텀 훅을 통해 우리는 좀 더 간결한, 재사용성 높은 코드를 만들 수 있게됩니다.

예를 들어, 페이지 이동을 커스텀 훅을 통해 사용한다면,

HOF 방식을 사용하여 useMove 라는 커스텀 훅을 만들어주고,

import 시켜 간결한 사용이 가능해집니다!


Ref

리액트를 사용하기 전까지 우리는 특정 태그에 접근할 때 document.getElementById() 를 사용했었습니다.

하지만, 리액트는 실제 DOM이 아닌 virtual DOM(가상돔)을 다루기 때문에 위의 방법으로 접근했을 때 문제가 생길 수 있습니다.

따라서 우리는 Ref를 이용해 태그를 직접 변수에 저장하도록 하겠습니다.

class형 컴포넌트 createRef()

class형 컴포넌트에는 createRef() 메서드를 이용해 특정 태그에 접근합니다.

// 메서드 임폴트
import {createRef} from 'react'

// Ref코드 생성
inputRef = createRef()

// Ref를 적용하고싶은 input태그(=접근하고싶은 태그)
<input type="text" ref={this.inputRef} />

// 태그에 접근해서 실행시킬 함수
componentDidMout(){
	console.log("마운트 됨")
	this.inputRef.current?.focus()
}

함수형 컴포넌트 useRef()

함수형 컴포넌트에서는 useRef 훅을 이용해 특정 태그에 접근합니다.

// 훅 임폴트
import {useRef} from 'react'

// Ref코드 생성
const inputRef = useRef()

// Ref를 적용하고 싶은 input태그(=접근하고 싶은 태그)
<input type="text" ref={inputRef} />

// 태그에 접근해서 실행시킬 함수 _ useEffect참고는 바로 아래에서!
useEffect(()=>{
	console.log("마운트 됨")
	inputRef.current?.focus()
})
profile
FrontEnd Developer

0개의 댓글