리액트(React)에서 제공하는 여러 hooks.
👉 기본 hook: useState()
, useEffect()
, useContext()
👉 추가 hook: useReducer()
, useMemo()
, useCallback()
, useRef()
❗ 함수형 컴포넌트에서 사용(not Class)
💡 상태 유지 값(state
)과 그 값을 갱신하는 함수(setState
)를 반환.
최초로 렌더링을 하는 동안 state가 읽히고, state를 갱신할 때 setState함수가 사용된다.
useMemo
를 활용하는 방법이 있다.)import React from 'react'; const Home = () => { let [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => {setCount(count+ 1)} > Click me </button> </div> ); }; export default Home;
useEffect(function, deps)
>> 첫 번째 인자는 수행하고자 하는 작업, 두 번째 인자는 배열 형태이며, 배열 안에는 검사하고자 하는 특정 값 또는 빈 배열이 올 수 있다(state값 넣어주기)import React from 'react'; const Home = () => { let [count, setCount] = useState(0); useEffect(() => { document.title = `you clicked ${count} times`; // 이는 맨 첫 번째 렌더링은 물론 그 이후의 모든 렌더링에 똑같이 적용된다. } return ( <div> <p>You clicked {count} times</p> <button onClick={() => {setCount(count+ 1)} > Click me </button> </div> ); }; export default Home; // 컴포넌트가 종료될 때는 return을 활용한다 import React from 'react'; const Home = () => { const [time, setTime] = useState(new Date()); useEffect(() => { const timer = setTimeout(setTime(new Date(), 1000)); return () => { clearTimeout(timer) }; // timer 제거 (return으로) }, [time]); return ( <div> <h2>{time.toLocaleString()}</h2> </div> ); }; export default Home;
🍎 useEffect(function, deps)
의 두 번째 인자는 컴포넌트가 렌더링 될 때 효율적으로 관리할 수 있다.
let [count, setCount] = useState(0);
useEffect(() => {
console.log('컴포넌트가 마운트 될 때만 실행된다');
})
// deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 될 때마다 useEffect함수가 실행된다
useEffect(() => {
console.log('컴포넌트가 처음 렌더링 될 때만 실행된다');
}, [])
// 빈 배열 넣어주기
useEffect(() => {
console.log('컴포넌트가 count state가 실행 될 때만 실행된다');
}, [count])
useContext
를 사용하면 하위 컴포넌트에서도 상위 컴포넌트에서 전달하는 값을 공유 받을 수 있다.
- context 생성:
createContext()
>> 부모 컴포넌트에서 생성- context 조회:
useContext()
>> 자식 컴포넌트에서 조회
⭕ 사용방법 ---------------------------------------------------------------
(1) [부모 컴포넌트] createContext()를 실행 및 상단 import 하기
import { createContext, useState } from 'react'; // 상단에 import const Parent = () => { // state값 설정 (user 변수 안에 담김) const user = { nickname: 'danuel', isAdmin: true } // createContext선언(countContext변수에 담는다) const countContext = createContext(user) // 전달하려는 state값을 인자에 담는다 return (); }; export default Parent;
(2) return 문 안에서 state값을 props로 전달하고 싶은 컴포넌트에 감싸준다(value에 state값 전달하기)
import { createContext } from 'react'; const Home = () => { const user = { nickname: 'danuel', isAdmin: true } const userContext = createContext(user) return ( <userContext.Provider value={user}> // userContext.Provider로 감싸준다 (state값 전달) {Children} <userContext.Provider> ); }; export default Home;
(3) [자식 컴포넌트] useContext를 이용해서 state값 전달받기
import { createContext } from 'react'; const Children = () => { // useContext()함수를 통해 값을 받아온다. 인자에는 createContext가 담긴 변수명을 담는다. const count = useContext(countContext) // count라는변수에 담는다 return ( {count} ); }; export default Children;
useState
의 대체 함수 이다. useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용함.❗
reducer
란, 현재 상태(state)와 액션 객체(action)를 파라미터로 받아와서 새로운 상태를 반환해주는 함수이다.function reducer(state, action) { // 새로운 상태를 만드는 로직 // const setState = ... return setState; // << 새로운 상태 } ----- // 증가/감소 변경 로직을 적용한 예시 function reducer(state, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
const [state, dispatch] = useReducer(reducer, initialState); // 여기서 state 는 앞으로 컴포넌트에서 사용 할 수 있는 상태를 가리키게 되고, // dispatch 는 액션을 발생시키는 함수 ----- // 위에서 만든 reducer함수를 적용한 예시 import React, { useReducer } from 'react'; function Counter() { const [number, dispatch] = useReducer(reducer, 0); // reducer함수를 넣어준다. 초기값은 0 // useState가 아니라 useReducer를 통해 state값을 지정하고 reducer(action, dispatch)를 적용할 수 있다 const onIncrease = () => { dispatch({ type: 'INCREMENT' }); }; const onDecrease = () => { dispatch({ type: 'DECREMENT' }); }; return ( <div> <h1>{number}</h1> // state값 <button onClick={onIncrease}>+1</button> <button onClick={onDecrease}>-1</button> </div> ); }
❗❗❗ Memoization
memoization이란 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법.
memoization을 절적히 적용하면 중복 연산을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있다.
👉useMemo()
,useCallback()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo
는 2개의 인자를 받는다.//App.js import React, { useState } from "react"; import ShowState from "./ShowState"; // 자식 컴포넌트 import const App = () => { // state 생성 const [number, setNumber] = useState(0); const [text, setText] = useState(""); const increaseNumber = () => { setNumber((prevState) => prevState + 1); }; const decreaseNumber = () => { setNumber((prevState) => prevState - 1); }; const onChangeTextHandler=(e)=>{ setText(e.target.value); } return ( <div className="App"> <div> <button onClick={increaseNumber}>+</button> <button onClick={decreaseNumber}>-</button> <input type="text" placeholder="Last Name" onChange={onChangeTextHandler} /> </div> <ShowState number={number} text={text} /> // 자식 컴포넌트에 state값 넘겨주기 </div> ); }; export default App; // ShowState.js import React from "react"; import "./styles.css"; const getNumber = (number) => { console.log("숫자가 변동되었습니다."); return number; }; const getText = (text) => { console.log("글자가 변동되었습니다."); return text; }; const ShowState = ({ number, text }) => { const showNumber = getNumber(number); const showText = getText(text); return ( <div className="info-wrapper"> {showNumber} <br /> {showText} </div> ); }; export default ShowState;
App.js
에서 +,- 버튼을 눌러 number 값을 바꾸거나 input창에서 text를 변경할 때마다 state값의 변경이 일어나고, 그럴 때마다 자식 컴포넌트의 값도 변경된다. (불필요한 렌더링)
이런 불필요한 렌더링을 막기 위해, ShowState
컴포넌트 내에서 useMemo를 사용함으로써 개선할 수 있다.
//ShowState.js import React,{useMemo} from "react"; import "./styles.css"; const getNumber = (number) => { console.log("숫자가 변동되었습니다."); return number; }; const getText = (text) => { console.log("글자가 변동되었습니다."); return text; }; const ShowState = ({ number, text }) => { const showNumber = useMemo(()=>getNumber(number),[number]); // useMemo 적용 const showText = useMemo(()=>getText(text),[text]); // useMemo 적용 return ( <div className="info-wrapper"> {showNumber} <br /> {showText} </div> ); }; export default ShowState;
실제 App.js
에서 number값과 text값이 변동될 때만 재렌더링 되고, 그 외 다른 값들의 변경에는 반응하지 않는다.
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b] );
useMemo와 비슷함. 주로 렌더링 성능을 최적화해야 하는 상황에서 사용. 이벤트 핸들러 함수를 필요할 때만 생성할 수 있다.
useCallback은 2개의 인자를 받는데, 첫번째 인자에는 생성하고 싶은 함수를 넣고, 두번째 인자에는 배열을 넣는다. 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시해야 한다.
함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터 안에 포함시켜 주어야 한다.
//App.js import React, { useState } from "react" import List from "./List" // 자식 컴포넌트 import function App() { // state 생성 const [number, setNumber] = useState(1) const [dark, setDark] = useState(false) const getItems = useCallback(() => { // useCallback 적용 return [number, number + 1, number + 2] }, [number]) const theme = { backgroundColor: dark ? "#333" : "#fff", color: dark ? "#fff" : "#333", } return ( <div style={theme}> <input type="number" value={number} onChange={e => setNumber(parseInt(e.target.value))} /> <button onClick={() => setDark(prevDark => !prevDark)}>테마 변경</button> <List getItems={getItems} /> // 자식 컴포넌트에 값 전달 </div> ) } export default App //List.js import React, { useEffect, useState } from "react" export default function List({ getItems }) { const [items, setItems] = useState([]) useEffect(() => { setItems(getItems()) console.log("숫자가 변동되었습니다.") }, [getItems]) return items.map((item, index) => <div key={index}>{item}</div>)
👀 useMemo() 와 useCallback() 의 차이점
useMemo(() => fn, deps) / useCallback(fn, deps)
useMemo
는 함수를 반환하지 않고, 함수의 값만 memoization해서 반환.
useCallback
은 함수 자체를 memoization 해서 반환.💡 자식 컴포넌트에서 특정한 props 값들의 변화를 최적화시키고 싶을때는 useMemo를, 부모 컴포넌트에서 계산량이 많은 props함수를 자식 컴포넌트에게 넘겨줄 때는 useCallback을 사용하는 것이 가장 좋은 방법
1) 특정 DOM 을 선택해야 하는 상황에서 useRef()
를 설정한다
JavaScript 를 사용 할 때, 특정 DOM 을 선택해야 하는 상황에 getElementById, querySelector 같은 DOM Selector 함수를 사용해서 DOM 을 선택한다.
리액트를 사용하는 프로젝트에서도 가끔씩 DOM 을 직접 선택해야 하는 상황이 발생 할 때도 있다. (ex. 특정 element의 크기를 가져오기, 스크롤바 위치를 가져오기(설정하기), 포커스를 설정하기 등)
그럴 땐, 리액트에서ref
라는 것을 사용한다.함수형 컴포넌트에서 ref 를 사용 할 때에는 useRef 라는 Hook 함수를 사용한다.
(클래스형 컴포넌트에서는 콜백 함수를 사용하거나 React.createRef 라는 함수를 사용)
useRef
함수는 current 속성을 가지고 있는 객체를 반환.useRef
를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킨다.import React, { useState, useRef } from 'react'; function InputSample() { // input의 기본(초기) state값 설정 const [inputs, setInputs] = useState({ name: '', nickname: '' }); // useRef()객체 설정 const nameInput = useRef(); // nameInput값을 useRef()객체에 담아준다. // nameInput값 = current값 // onReset함수 설정 >> nameInput의 .current에 focus() 설정하는 함수 (초기화 하는 기능) const onReset = () => { setInputs({ // 변경함수가 실행될 때 값이 모두 초기화 된다 name: '', nickname: '' }); nameInput.current.focus(); // onReset함수가 실행되면 초기화 되면서 nameInput에 포커스가 맞춰짐 }; const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출 return ( <div> <input name="name" placeholder="이름" onChange={onChange} value={name} ref={nameInput} // ref값으로 설정해준다. /> <button onClick={onReset}>초기화</button> ); }; export default InputSample;
2) useRef
hook은 DOM선택 용도 외에도, 컴포넌트 안에서 조회/수정 가능한 변수를 관리하는 용도가 있다.
useRef
로 변수를 관리하게 될 때, useRef의 current 속성은 값을 변경해도 상태를 변경할 때 처럼 React 컴포넌트가 다시 랜더링되지 않는다.
즉, 그 변수가 업데이트 된다고 해서 컴포넌트가 리렌더링 되지 않는다. (굳이 리렌더링 할 필요가 없는 변수라면 useRef로 관리해주는 것이 효율적이다.
const interval = useRef(); // useRef 객체를 생성 후 interval 변수에 담는다. ❗ const로 변수를 설정했다 하더라도, useRef는 객체이기 때문에 값이 바뀔 수 있다. useEffect(() => { interval.current = setInteral(handleChange, 100); // ref객체의 current에 setInterval함수를 넣어준다. return () => { clearInterval(interval.current) } // 컴포넌트가 unmount될 때, clearInterval함수를 활용해서 setInterval함수가 들어있는 // ref객체를 초기화해준다. })