
자바스크립트에서 화면이 나타나는 시점 = load
자바스크립트에서 화면에서 벗어나는 시점 = unload
ex) 네이버 주소를 입력했을 때 화면이 보이는 시점 = load
네이버에서 다음 주소를 입력하고 화면이 이동될 때 = unload
=> 화면이 나타날 때 == mount
화면에서 벗어날 때 == unmount
라고 이해하면 쉽다.
useEffect는 컴포넌트가 시작됐을 때, 값이 바뀔 때, *특정 값이 바뀌었을 때만 앱이 재실행 되도록 할 수 있다.
didMount, useState, 선택적 useState를 할 수 있는 hook이 useEffect 이다.
import React from 'react'
import { useState,useEffect } from 'react'
export default function App() {
const [number, SetNumber] = useState(0)
const countUpdate = ()=>{
SetNumber(number+1)
}
useEffect(()=>{
console.log("useEffect를 실행합니다.")
})
return (
<div>
<p>Count: {number}</p>
<button onClick={countUpdate}>Add</button>
</div>
)
}
(결과)

useEffect는 첫 번째는 콜백함수이고, 두 번째 인자는 deps인데 값은 DependencyList(의존성 배열)가 필요하다.
따라서 위의 코드에서는 배열이 없기 때문에 컴포넌트가 랜더링 될 때마다 useEffect 함수 내부에 있는 동작들이 실행 될 것이다.
이때 랜더링이 처음 됐을 때 최초로 한 번만 실행되기를 원할 때는 useEffect를 적고, 두 번째 인자인 deps를 빈 배열로 둔다.
=> 빈 배열을 추가하게 되면 life cycle의 DidMount를 사용한 것과 같은 효과를 낼 수 있다!
import React from 'react'
import { useState,useEffect } from 'react'
export default function App() {
const [number1, SetNumber1] = useState(0)
const countUpdate1 = ()=>{
SetNumber1(number1+1)
}
const [number2, SetNumber2] = useState(0)
const countUpdate2 = ()=>{
SetNumber2(number2+1)
}
//배열이 없는 경우 컴포넌트가 렌더링 될 때마다 실행
//빈 배열을 작성한 경우 컴포넌트가 처음 실행될 때 1번만 실행
useEffect(()=>{
console.log("useEffect를 실행합니다.")
},[])
return (
<div>
<p>Count1: {number1}</p>
<button onClick={countUpdate1}>Add1</button>
<hr/>
<p>Count2: {number2}</p>
<button onClick={countUpdate2}>Add2</button>
</div>
)
}

useEffect의 문장이 1번만 출력되고 ADD 버튼을 클릭해도 다시 동작하지 않는 모습을 볼 수 있다.
배열 안에 변화시키고 싶은 변수 이름을 넣기
import React from 'react'
import { useState,useEffect } from 'react'
export default function App() {
const [number1, SetNumber1] = useState(0)
const countUpdate1 = ()=>{
SetNumber1(number1+1)
}
const [number2, SetNumber2] = useState(0)
const countUpdate2 = ()=>{
SetNumber2(number2+1)
}
//배열이 없는 경우 컴포넌트가 렌더링 될 때마다 실행
//빈 배열을 작성한 경우 컴포넌트가 처음 실행될 때 1번만 실행
useEffect(()=>{
console.log("useEffect를 실행합니다.")
},[number1])
return (
<div>
<p>Count1: {number1}</p>
<button onClick={countUpdate1}>Add1</button>
<hr/>
<p>Count2: {number2}</p>
<button onClick={countUpdate2}>Add2</button>
</div>
)
}

useEffect가 최초로 1회 실행이 되고, Count1을 업데이트 하면 useEffect가 실행되지만, 배열 안에 Count2를 변화시키는 변수를 넣지 않았기 때문에 Count2를 ADD해도 useEffect가 변화하지 않는다.
(정리)
//배열이 없는 경우 컴포넌트가 렌더링 될 때마다 실행
useEffect(()=>{
console.log("%cuseEffect가 렌더링 될 때마다 실행됩니다.",
"background:black; color:#fff"
)
})
//배열이 없는 경우 컴포넌트가 렌더링 될 때마다 실행
useEffect(()=>{
console.log("%cuseEffect가 처음 한 번만 실행됩니다.",
"background:orange; color:#fff"
)
},[])
//배열에 입력된 값이 변경된 경우에만 실행
useEffect(()=>{
console.log("%cCount1이 변경될 때마다 실행됩니다.",
"background:skyblue; color:#fff"
)
},[number1])
(결과)

console창에 css 효과를 넣어서 구분을 지었다.

Count1을 누르면 black, skyblue만 출력되고 orange는 더이상 보이지 않는다.
Count2를 누르면 black만 출력이 된다.
(코드)
import React from 'react'
import { useState,useEffect } from 'react'
export default function App() {
const [number1, SetNumber1] = useState(0)
const countUpdate1 = ()=>{
SetNumber1(number1+1)
}
const [number2, SetNumber2] = useState(0)
const countUpdate2 = ()=>{
SetNumber2(number2+1)
}
//배열이 없는 경우 컴포넌트가 렌더링 될 때마다 실행
useEffect(()=>{
console.log("%cuseEffect가 렌더링 될 때마다 실행됩니다.",
"background:black; color:#fff"
)
})
//배열이 없는 경우 컴포넌트가 렌더링 될 때마다 실행
useEffect(()=>{
console.log("%cuseEffect가 처음 한 번만 실행됩니다.",
"background:orange; color:#fff"
)
},[])
//배열에 입력된 값이 변경된 경우에만 실행
useEffect(()=>{
console.log("%cCount1이 변경될 때마다 실행됩니다.",
"background:skyblue; color:#fff"
)
},[number1])
return (
<div>
<p>Count1: {number1}</p>
<button onClick={countUpdate1}>Add1</button>
<hr/>
<p>Count2: {number2}</p>
<button onClick={countUpdate2}>Add2</button>
</div>
)
}
useEffect의 경우, 위의 코드처럼 조건을 두지 않고 사용하면 계속 반복하여 실행될 수 있다.
(예시)
//App.js
import React from 'react'
import Timer from './components/Timer'
import { useState } from 'react'
export default function App() {
const [showTimer, setShowTimer] = useState(false)
return (
<div>
<button
onClick={
()=>{
setShowTimer(!showTimer)
}
}
> {showTimer?'타이머 중지':'타이머 실행'} </button>
<hr/>
{/* showTimer ? <Timer /> : null */}
{showTimer && <Timer />}
</div>
)
}
//Timer.js
import React from 'react'
import { useEffect } from 'react'
export default function Timer() {
useEffect(()=>{
setInterval(()=>{
console.log("Running Timer...")
},1000)
},[])
return (
<div>
Timer가 실행중입니다.
</div>
)
}
버튼을 처음 클릭했을 땐 실행이 정상적으로 되고 다시 버튼을 클릭했을 때 컴포넌트 보여지기/숨겨지기가 정상적으로 되어서
useEffect도 정상적으로 보이는 것 처럼 느껴지지만, console을 확인해보면 count가 계속 올라가는 모습을 볼 수 있다.
=> 이때 useEffect의 상태를 1회만 실행되도록 하는 과정을 클린업이라고 한다.
unMount 하고싶을 때 실행시키는 문장 == return
(정리)
useEffect(()=>{})
=> 의존성 배열이 없으면 mount update
=> 배열이 비어있으면 시작 되었을 때만 mount update
useEffect(()=>{} return()=>{},[])
(클린업 코드)
//app.js
import React from 'react'
import Timer from './components/Timer'
import { useState } from 'react'
export default function App() {
const [showTimer, setShowTimer] = useState(false)
return (
<div>
<button
onClick={
()=>{
setShowTimer(!showTimer)
}
}
> {showTimer?'타이머 중지':'타이머 실행'} </button>
<hr/>
{/* showTimer ? <Timer /> : null */}
{showTimer && <Timer />}
</div>
)
}
//timer.js
import React from 'react'
import { useEffect } from 'react'
export default function Timer() {
useEffect(()=>{
const timer = setInterval(()=>{
console.log("Running Timer...")
},1000)
//cleanup:return(추가)->컴포넌트가 unMount되었을 때 혹은 useEffect가 재실행 되기 전에 동작한다.
return () => {
console.log("Stop Timer...")
clearInterval(timer)
}
},[])
return (
<div>
Timer가 실행중입니다.
</div>
)
}
setInterval이 최초 실행된 후 useState에 의해서 1초마다 새로고침 되기 때문에 멈추지 않고 계속 실행되었던 상태를 return 문장을 추가하여 멈추도록 했다.
clearInterval은 어떤 타이머를 멈출 건지, 인자를 담아주어야 하는데 이때 타이머를 변수에 담아서 구분하도록 했다.
LAM => 휘발성 메모리
컴퓨터 프로그램들은 우리가 어떤 작업들을 할 때 도움이 될 수 있도록 임시 저장소를 사용하는데 이 임시 저장소의 이름을 LAM이라고 한다.
memory의 내부에는 작은 여러개의 방들로 구성되어있으며 이 방들은 쉽게 찾고, 저장할 수 있도록 각각 고유의 이름들이 있다.
이처럼 useMemo는 컴퓨터의 memory를 사용하는 기능, memorize하는 기능이다.
사용하는 방법을 간단하게 살펴보면 어떤 변수를 지정해주면 그 변수들은 컴퓨터가 사용하는 일반적인 저장소가 아니라 useMemo라고 하는 메모리에 저장하게 되고, 저장소의 이름을 기억하게 된다.
memory는 여러 개의 작은 방이지만 프로그램은 방 한 칸만 사용해서는 원하는 만큼 저장을 할 수 없다. 그래서 당연히 메모리의 방을 몇 개씩 쓸 수 있도록 되어져 있다.
이때 우리가 javascript를 이용해서 이 방을 사용하겠다고 하면 우리가 변수를 3개 만들었다고 가정했을 때, 그 중 a라는 변수의 값을 바꾸면 같은 방의 값을 바꾸는 것이 아니라 값이 바뀔때마다 무조건 새로운 방에 다른 값을 저장하게 된다.
그래서 메모리에 저장된 건 방 번호가 변수에 저장되는거고, 방번호가 바뀌지 않으면 브라우저는 값이 바뀌었다고 판단하지 않는다.
useMemo란 연산이 오래걸리는 값들은 메모리에 저장을 해두고 똑같은 값을 가지면 연산을 다시 하지 않고 내부에 들어있던(메모리에 저장 되어있던) 값을 가져와서 계산하도록 할 수 있는 hook 이다.
따라서 스크립트에서 두 가지의 연산식을 만들었을 때 useMemo는 복잡한 값을 저장해서 사용하면 단순한 연산까지 함께 느려지는 현상을 막을 수 있다.
=> 전체적인 성능을 향상시킬 수 있다.
import React, { useMemo } from 'react'
import { useState } from 'react'
export default function App() {
const [hardProc, setHardProc] = useState(0)
const [simpleProc, setSimpleProc] = useState(0)
//const sumHardProc = hardProcFunc(hardProc)
const sumHardProc = useMemo(()=>{
return hardProcFunc(hardProc)
}, [hardProc])
const simpleHardProc = simpleProcFunc(simpleProc)
function hardProcFunc(num){
console.log("복잡한 연산 수행")
for(let i=1;i<999999999;i++){}
return num + 1000
}
function simpleProcFunc(num){
console.log("단순한 연산 수행")
return num + 1000
}
return (
<div>
<h3>복잡한 연산 함수</h3>
<input
style={{width:"80px"}}
type="number"
value={hardProc}
onChange={e=>{setHardProc(parseInt(e.target.value))}}
/> + 1000 = {sumHardProc}
<hr/>
<h3>단순한 연산 함수</h3>
<input
style={{width:"80px"}}
type="number"
value={simpleProc}
onChange={e=>{setSimpleProc(parseInt(e.target.value))}}
/> + 1000 = {simpleHardProc}
</div>
)
}
메모리는 값이 바뀌면 저장소의 번호(방 번호)가 바뀌는데 함수 자체는 function A를 function B로 바뀌는게 아니라 출력되는 결과만 바뀌는 것이다.
함수는 메모리에 저장되어 있기 때문에 함수를 실행할 순 있어도 함수라는 것 자체가 다른 값으로 바뀐 것이 아니. 따라서 useCallback을 이용해 함수 또한 memorize 한다면 useEffect를 사용해도 화면의 변화가 없는 것이다.
import { useState,useEffect,useCallback } from 'react'
export default function App() {
const [number, setNumber] = useState(0)
const [toggle,setToggle] = useState(false)
const someFunc = useCallback(() => {
console.log('Excuted Function - number :', {number});
}, [number])
useEffect(()=>{
console.log("%cChanged someFunc","color:skyblue ")
},[someFunc])
return (
<div>
<input
style={{width:"60px", marginRight:"5px"}}
type="number"
value={number}
onChange={e=>{
setNumber(parseInt(e.target.value))
}}
/>
<button
onClick={someFunc}
>Call someFunc</button>
<button
onClick={()=>{setToggle(!toggle)}}>{toggle.toString()}</button>
</div>
)
}

지금까지 사용한 기능들은 react 개발팀이 모듈 형태로 만들어 둔 것을 가져다 쓴 것이고, hook도 사용자가 직접 만들어서 사용할 수 있다.
사용자 정의 hook은 이름을 지을 때 use~로 시작하는 것으로 약속되어있다.
매개변수를 전달하여 함수를 연결하고 입력된 값을 받아서 사용할 수 있도록 한다.
//app.js
import React from 'react'
import useInput from './components/useInput'
export default function App() {
const [inputValue1, handleChange1, handleSubmit] = useInput("Hello!")
return (
<div>
<h2>useInput</h2>
<input
style={{marginRight:"5px"}}
value={inputValue1}
type="text"
onChange={handleChange1}
/>
<button
onClick={handleSubmit}
>확인</button>
</div>
)
}
//useInput.js
import React from 'react'
import { useState } from 'react'
function useInput(initValue) {
const [inputValue, setInputValue] = useState(initValue)
const handleChange = e =>{setInputValue(e.target.value)}
console.log(inputValue)
const handleSubmit = () =>{
alert(inputValue)
setInputValue("")
}
return [inputValue, handleChange, handleSubmit]
}
export default useInput

최초로 실행될 때 Hello!가 전달되도록 했기 때문에 최초실행시 Hello!가 입력되며, 이후에는 input text field가 초기화되어 빈 칸으로 변환된다.
그리도 사용자가 입력한 값이 자동으로 출력되는 모습을 볼 수 있다.
//app.js
import React from 'react'
import useInput from './components/useInput'
import Sub from './components/Sub'
export default function App() {
const [inputValue1, handleChange1, handleSubmit] = useInput("Hello!")
return (
<div>
<h2>useInput</h2>
<input
style={{marginRight:"5px"}}
value={inputValue1}
type="text"
onChange={handleChange1}
/>
<button
onClick={handleSubmit}
>확인</button>
<Sub/>
<Sub/>
<Sub/>
<Sub/>
<Sub/>
</div>
)
}
//useInput.js
import React from 'react'
import { useState } from 'react'
function useInput(initValue) {
const [inputValue, setInputValue] = useState(initValue)
const handleChange = e =>{setInputValue(e.target.value)}
console.log(inputValue)
const handleSubmit = () =>{
alert(inputValue)
setInputValue("")
}
return [inputValue, handleChange, handleSubmit]
}
export default useInput
//sub.js
import React from 'react'
import useInput from './useInput'
export default function Sub() {
const [inputValue1, handleChange1, handleSubmit] = useInput("Hello!")
return (
<div>
<h2>
Sub Component
</h2>
<input
style={{marginRight:"5px"}}
value={inputValue1}
type="text"
onChange={handleChange1}
/>
<button
onClick={handleSubmit}
>확인</button>
</div>
)
}
Sub 컴포넌트를 생성하여 반복하고 싶은 내용을 담는다.
이때 집중해야할 점은 useInput은 컴포넌트가 아닌 hook의 역할로 사용되고 있는 것이고 Sub는 컴포넌트로 사용하고 있다는 점이다.
Sub 컴포넌트에도 userInput hook이 필요하기 때문에 훅을 불러와서 사용하는 모습을 볼 수 있다.

userInput hook을 여러개를 생성하지 않고 반복적으로 사용하는 예시이다.