함수형 컴포넌트가 클래스형 컴포넌트보다 훨씬 간단합니다.
하지만, 함수형 컴포넌트 그 자체만으로는 클래스형 컴포넌트의 모든 기능을 흉내낼 수 없습니다.
그래서 React 에서 함수형 컴포넌트에서도 클래스형 컴포넌트와 동일한 기능을 사용 가능하도록 도구를 만들어 주었습니다. 이 도구를 Hooks(훅) 이라고 부릅니다.
대표적인 Hooks 에는 useState
, useEffect
가 있습니다.
💡 잠시만요!
useState, useEffect가 무엇인지는 이후에 나오게 됩니다.
지금은 이러한 Hooks이 있기 때문에,간단한 함수형 컴포넌트로 클래스형 컴포넌트를 대신
할 수 있다는 것만 이해하고 넘어가시면 됩니다.
state란 리액트 컴포넌트에서 데이터를 담기 위한 상자
입니다.
우리는 자바스크립트에서 데이터를 담기 위한 상자로 변수
를 배웠습니다.
다시 말해, state는 컴포넌트에서 사용하는 변수
입니다.
💡state, setState, useState
state: 컴포넌트에서 사용하는
변수
setState: 컴포넌트에서 사용하는변수를 바꿔주는 기능
useState: 컴포넌트에서 사용하는변수를 만들어주는 기능
리액트 컴포넌트는 앞쪽에 화면에 보여지는 부분
과 뒷쪽에 데이터를 관리하는 부분
으로 나눌 수 있습니다.
만약, 단순히 자바스크립트 변수 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) 작성한 내용을 검증하고잘못된 부분을 빨간색으로 표기
할 때 사용합니다.
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
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
setstate는 비동기로 작동 합니다.
setState가 동기로 작동하게되면 변경될때마다 바로 바로 렌더링을 하기 때문에 비효율적입니다.
따라서 임시 저장소에 모아두었다가 코드를 끝까지 읽고 한번에 바꿔서 렌더링합니다.
💡 리렌더가 되는 상황
- 새로운 props가 들어올 때
- 부모 컴포넌트가 렌더링 될 때
- 강제 업데이트(forceUpdate)가 실행될 때
- 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
에 대해서 기억하시나요?
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 tag
의readOnly
속성은boolean
타입입니다.
때문에 이 타입이string
타입이 되도록 만들어주어야합니다. 이 문제를 해결하기위해 이중부정연산자를 사용할 수 있습니다.
data?.fetchBoard.writer
은string
타입이지만boolean
타입으로 보면string
이 있기 때문에true
입니다. 여기에!data?.fetchBoard.writer
해주시면false
가 되고, 그럼 우리가 할당해준 데이터를 그대로 가져오고싶은 경우에는!!data?.fetchBoard.writer
처럼 이중부정연산자를 사용하시면true
인 상태 그대로 데이터를 가져올 수 있겠죠? 상황에 따라 부정연산자와 이중부정연산자(!!)를 잘 활용해보시길 바랍니다!
클래스형 컴포넌트에는 componentDidMout
와 같은 생명주기 메서드들이 있습니다.
그렇다면 함수형 컴포넌트에서의 생명주기관련 훅은 무엇일까요?
바로 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 안에서 setState의 사용
→ useEffecrt 내에서 setState를 사용할때는 정말 필요한 경우가 아니라면 지양하시는게 가장 좋습니다.
컴포넌트가 마운트된 이후에 setState를 적용하게 되면,
1. state가 변경되고,
2. 변경된 state로 컴포넌트가 다시 그려지게(=리렌더) 됩니다.
즉, useEffecrt 내에서 setState를 사용하게 되면 불필요한 리렌더나 무한루프를 일으키게 되고 성능면에서 비효율적이게 됩니다.
우리가 여태까지 힘들게 만들었던 폼, 사실은 폼을 대신 만들어주는 도구가 있습니다.
react-form, redux-form, react-hook-form, formik 등 그 종류도 굉장히 다양합니다.
함수형 컴포넌트와 hook을 사용하는 경우, 가장 사용하기 쉽고 성능적으로 좋은 폼은 react-hook-form 입니다.
react-hook-form_homepage
react-hook-form_github
폼 라이브러리를 사용하면 하나하나 직접 만들지 않고, 밸리데이션 체크 및 폼 관리 등을 좀 더 깔끔하게 할 수 있습니다.
지금까지 우리는 useState
, useContext
, useEffect
, useRef
등 다양한 react
내장 Hook
을 사용해 왔습니다. 그렇기에 함수형 컴포넌트에서 state
와 LifeCycle
관리가 가능하였습니다.
Custom Hook
이란 이름 그대로 개발자가 스스로 커스텀 한 훅을 말합니다. 커스텀 훅을 통해 우리는 좀 더 간결한, 재사용성 높은 코드를 만들 수 있게됩니다.
예를 들어, 페이지 이동을 커스텀 훅을 통해 사용한다면,
HOF 방식을 사용하여 useMove 라는 커스텀 훅을 만들어주고,
import 시켜 간결한 사용이 가능해집니다!
리액트를 사용하기 전까지 우리는 특정 태그에 접근할 때 document.getElementById()
를 사용했었습니다.
하지만, 리액트는 실제 DOM이 아닌 virtual DOM(가상돔)을 다루기 때문에 위의 방법으로 접근했을 때 문제가 생길 수 있습니다.
따라서 우리는 Ref
를 이용해 태그를 직접 변수에 저장하도록 하겠습니다.
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
훅을 이용해 특정 태그에 접근합니다.
// 훅 임폴트
import {useRef} from 'react'
// Ref코드 생성
const inputRef = useRef()
// Ref를 적용하고 싶은 input태그(=접근하고 싶은 태그)
<input type="text" ref={inputRef} />
// 태그에 접근해서 실행시킬 함수 _ useEffect참고는 바로 아래에서!
useEffect(()=>{
console.log("마운트 됨")
inputRef.current?.focus()
})