-
[React] React Hooks, useMemo, Custom Hooks카테고리 없음 2023. 3. 22. 21:30반응형
목차
📌 React Hooks
📍 Function Component와 Class Component
Hook은 함수 컴포넌트에서 사용하는 메서드
🔗Class Component VS Function Component
🤷🏼 컴포넌트란? 단순히 템플릿을 넘어 데이터에 맞춰 UI를 만들어 주는 기능과 API를 통해 화면의 움직임 작업을 수행
클래스형 컴포넌트의 경우 state 기능 및 라이프 사이클 기능을 사용할 수 있으며 임의 메서드를 정의할 수 있다
class Counter extends Component { constructor(props) { super(props); this.state = { counter: 0 } this.handleIncrease = this.handleIncrease.bind(this); } handleIncrease = () => { this.setState({ counter: this.state.counter + 1 }) } render(){ return ( <div> <p>You clicked {this.state.counter} times</p> <button onClick={this.handleIncrease}> Click me </button> </div> ) } }
함수형 컴포넌트는 클래스형 컴포넌트보다 선언하기 편하고, 메모리 자원을 덜 사용한다.
리액트는 점진적으로 클래스형 컴포넌트에서 함수형 컴포넌트로 옮겨져갔다.
function Counter () { const [counter, setCounter] = useState(0); const handleIncrease = () => { setCounter(counter + 1) } return ( <div> <p>You clicked {counter} times</p> <button onClick={handleIncrease}> Click me </button> </div> ) }
🔗 Hook이란?
class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해준다.
Hook은 함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드를 의미(클래스 컴포넌트 X)
render(){ /* 클래스 컴포넌트는 render() 안에서 변수를 작성할 수 있습니다. */ const [counter, setCounter] = useState(0); ... }
🔗 Hook 사용 규칙
1. 리액트 함수의 최상위에서만 호출해야 합니다.
반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 예상한 대로 동작하지 않을 우려가 있다.
2. 오직 리액트 함수 내에서만 사용되어야 합니다.
이는 리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 JavaScript 함수 안에서 호출해서는 안 된다는 의미
📍 useMemo
useMemo은 특정 값(value)를 재사용하고자 할 때 사용하는 Hook
시간이 오래 걸리는 함수가 있다고 할 때, 해당 컴포넌트는 렌더링 할 때마다 이 함수를 계속해서 호출할 것이다.
👉🏼👉🏼 클라이언트는 로딩 속도가 느리다고 판단
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */ import { useMemo } from "react"; // 시간이 오래 걸리는 계산 함수를 useMemo 훅을 사용 function Calculator({value}){ const result = useMemo(() => calculate(value), [value]); return <> <div> {result} </div> </>; }
🤷🏼 useMemo를 사용한다는 것은? 값을 어딘가에 저장 해뒀다가 다시 꺼내서 쓴다. 👉🏼👉🏼 Memoization
Memoization
기존의 수행한 ㅇ녀산의 결과값을 메모리에 저장하고, 동일한 입력이 들어오면 재활용하는 기법
중복연산 X 👉🏼👉🏼 앱 성능 최적화
📍 useCallback
useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook
함수를 재사용을 위해 사용하는 Hook
/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */ import React, { useCallback } from "react"; function Calculator({x, y}){ const add = useCallback(() => x + y, [x, y]); return <> <div> {add()} </div> </>; }
useCallback과 참조 동등성
JS에서 함수는 객체, 객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에,
반환하는 값이 같을 지라도 일치연산자로 비교했을 때 false가 출력
function doubleFactory(){ return (a) => 2 * a; } // 같은 함수를 할당했지만 주소가 다르기 때문에 값이 같을 수 없다 const double1 = doubleFactory(); const double2 = doubleFactory(); double1(8); // 16 double2(8); // 16 double1 === double2; // false double1 === double1; // true
📍 Custom Hooks
반복되는 로직을 함수로 뽑아내어 재사용할 수 있다.
Ex) input에 의한 상태 변경
- 상태관리 로직의 재활용이 가능하고
- 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있으며
- 함수형으로 작성하기 때문에 보다 명료하다는 장점이 있다. (e.g. useSomething)
사용자가 온라인인지 오프라인인지 확인하고, 상태에 따라 온라인이라면 초록색으로 표시하는 컴포넌트
//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트 function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } //FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트 function FriendListItem(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
🤷🏼 이 때, 로직을 빼네서 컴포넌트에 공유할 수는 없을까? 👉🏼👉🏼 Custom Hook
function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
🔗 Custom Hook 정의
- Custom Hook을 정의할 때는 함수 이름 앞에 use를 붙이는 것이 규칙이다.
- 대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook을 위치 시킨다.
- Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 합니다. 즉 return 하는 값은 조건부여서는 안 된다. 그렇기 때문에 위의 이 useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환하고 있다.
이렇게 만들어진 custom hook은 hook 내부에 useState와 같은 React 내장 Hook을 사용하여 작성할 수 있다.
// useFriendStatus Hook을 두 컴포넌트에 적용 function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
🔗 Custom Hook 실습
custom hook을 이용하여 useEffect 로직 분리하기
app.js
import CustomFetchExcercise from "./fetch/CustomFetchExcercise" import CustomInputExcercise from "./input/CustomInputExcercise" export default function App() { return ( <div className="container"> <h3>실습 1</h3> <CustomFetchExcercise /> <hr /> <h3>실습 2</h3> <CustomInputExcercise /> </div> ) }
useFetch.js
import { useEffect, useState } from "react"; //TODO : hooks.js의 이름도 custom hook의 규칙에 맞게 변경합니다. const useFetch = (fetchUrl) => { const [data, setData] = useState(); useEffect(() => { fetch(fetchUrl, { headers: { "Content-Type": "application/json", Accept: "application/json" } }) .then((response) => { return response.json(); }) .then((myJson) => { setData(myJson); }) .catch((error) => { console.log(error); }); }, [fetchUrl]); return data; //TODO : component 폴더의 fetch 컴포넌트에서 hook을 분리해옵니다. //분리해 온 hook을 이용하여 중심 로직을 마저 작성합니다. }; export default useFetch;
CustomFetchExcersize.js
import { useEffect, useState } from "react"; import useFetch from "./util/useFetch.jsx" import './fetch.css'; const CustomFetchExcercise = () => { // const [data, setData] = useState(); //console.log(fetchUrl); const data = useFetch('data.json'); // useEffect(() => { // fetch('data.json', { // headers: { // "Content-Type": "application/json", // Accept: "application/json" // } // }) // .then((response) => { // return response.json(); // }) // .then((myJson) => { // setData(myJson); // }) // .catch((error) => { // console.log(error); // }); // }, []); return ( <div className="todo-wrap"> <h1 className="todo-title">To do List</h1> <div className="todo-list"> {data && data.todo.map((el) => { return <li key={el.id}>{el.todo}</li>; })} </div> </div> ); } export default CustomFetchExcercise;
custom hook을 이용하여 input 로직 분리하기
input.js
function Input({ labelText, value }) { return ( <div className="name-input"> <label>{labelText}</label> <input {...value} type="text" /> </div> ); } export default Input;
useinput.js
import { useState } from "react"; function useInput(initialValue) { const [value, setValue] = useState(initialValue); const bind = { value, onChange: (e) => { setValue(e.target.value); } }; const reset = () => { setValue(initialValue); }; //return 해야 하는 값은 배열 형태의 값 return [value, bind, reset]; } export default useInput;
반응형