Space
article thumbnail
Published 2023. 7. 6. 00:09
[React] State & Hooks React & NextJS
반응형

State

컴포넌트의 상태(state)를 의미합니다.

state는 컴포넌트 내에서 변할 수 있는 값입니다.

React에서 제공하는 useState hook을 사용하여 state를 관리할 수 있습니다.


state hook

Hook 사용 규칙

리액트 함수의 최상위에서만 호출해야한다.

     - 조건문, 반복문, 중첩된 함수 내에서 Hook을 호출하면 안된다.

// example

if (counter) {
    const [sample, setSample] = useState(0);
}  // Error 발생

// Error
// React Hook "useState" is called conditionally.
// React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
// => useState를 조건문 안에서 호출하면 안된다는 말임

 

오직 리액트 함수 내에서만 사용되어야 한다.

     - 리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 JS 함수 안에서 호출하면 안된다.

// example

window.onload = function () {
    useEffect(() => {
        // do something
    }, [counter]);
}  // Error

// Error
// React Hook "useEffect" is called in function "window.onload" 
// that is neither a React function component nor a custom React Hook function.
// React component names must start with an uppercase letter.
// React Hook names must start with the word "use" react-hooks/rules-of-hooks
// => window.onload라는 함수에서 useEffect를 호출하면 안된다는 말임

useState

import { useState } from "react";

const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);

// state 갱신 함수로 값을 변경하여야 한다. useState에 직접 값을 할당하면 안된다.

// typeof useState === array

// State 값이 업데이트가 되면 자동으로 렌더링해주는데, 렌더링하면서 함수도 처음부터 다시 읽는다.
// (= 함수 안의 변수의 값이 초기화 된다.)
// example
function CheckboxExample() {
  const [isChecked, setIsChecked] = useState(false);

  const handleChecked = (event) => {
    setIsChecked(event.target.checked);
  };

  return (
    <div className="App">
      <input type="checkbox" checked={isChecked} onChange={handleChecked} />
      <span>{isChecked ? "Checked!!" : "Unchecked"}</span>
    </div>
  );
}

// 실행 순서
1. CheckboxExample 함수 컴포넌트가 호출됩니다.
2. useState 훅을 사용하여 isChecked라는 상태 변수와 setIsChecked라는 상태 변경 함수를 생성합니다. 
   isChecked의 초기값은 false입니다.
3. handleChecked 함수가 정의됩니다. 이 함수는 체크박스의 변경 이벤트를 처리하며, 
   이벤트 객체를 매개변수로 받습니다.
4. handleChecked 함수 내에서 setIsChecked 함수를 호출하여 isChecked 상태를 변경합니다. 
   event.target.checked는 체크박스의 현재 선택 상태를 나타내며, 이 값을 사용하여 isChecked 값을 갱신합니다.
5. JSX로 구성된 리액트 요소가 반환됩니다.
6. 반환된 JSX 요소를 렌더링하여 화면에 표시합니다.
7. 반환된 JSX 요소의 체크박스 엘리먼트에 checked prop을 isChecked 값으로 설정합니다. 
   이로써 isChecked 값에 따라 체크박스의 선택 상태가 결정됩니다.
8. 체크박스의 onChange 이벤트에 handleChecked 함수를 연결합니다. 
   이렇게 하면 체크박스의 선택 상태가 변경될 때마다 handleChecked 함수가 호출됩니다.
9. 반환된 JSX 요소에는 isChecked 값에 따라 "Checked!!" 또는 "Unchecked"를 표시하는 span 엘리먼트가 있습니다. 
   isChecked 값이 true일 때 "Checked!!"가 표시되고, 그렇지 않으면 "Unchecked"가 표시됩니다.
10. 최종적으로 렌더링된 결과를 화면에 표시합니다.

이렇게 하면 사용자가 체크박스를 선택 또는 해제할 때마다 isChecked 값이 변경되고, 
변경된 값에 따라 화면에 표시되는 내용이 업데이트됩니다.
반응형

useEffect

// useEffect 형식 
import { useEffect } from 'react';
useEffect(함수, [종속성1, 종속성2, ...])

// 종속성 배열(Dependency Array)은 옵션이다.
// 종속성 배열은 useEffect의 두 번째 인자로, Effect hook의 조건을 담은 배열이다.
// example
import { useEffect } from 'react';

// 종속성 배열에 아무것도 넣지 않은 경우
useEffect (() => {
   console.log(첫 렌더링과 props 혹은 state가 업데이트 될 때 마다 useEffect가 실행됩니다.)
})

// 종속성 배열에 빈배열을 넣은 경우
useEffect (() => {
   console.log(첫 렌더링 때에만 useEffect가 실행됩니다.)
},[])

// 종속성 배열에 특정 배열을 넣은 경우
useEffect (() => {
   console.log(첫 렌더링과 dep가 업데이트 될 때마다 useEffect가 실행됩니다.)
},[dep])
// example
import { useState, useEffect } from "react";

function MainPage() {
    const [dataList, setDataList] = useState([])
    
    useEffect(() => {
       const URL = "~~~~.com"
       fetch(URL)
          .then(res => res.json())
          .then(jsonData => setDataList(jsonData)
          .catch(error => console.log('Internet server Error', error)
    }, [])

   return (
      <main>
          { dataList를 mapping한 데이터 출력 } 
      </main>
   )
}

useRef

● React에서 DOM을 직접 건드리는 것은 금지이지만! 예외적으로 DOM을 직접 건드리는 경우 사용한다.
     (useState 로 상태를 만들어서 사용하는 경우보다 DOM을 건드려서 사용하는 것이 효율적인 경우에 사용한다.)

 

● 대부분의 경우 기본 React 문법을 벗어나 useRef를 남용하는 것은 부적절하고,

     React의 특징이자 장점인 선언형 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야한다.

// useRef 형식
import { useRef } from "react";

const 주소값을_담는_그릇 = useRef(참조자료형)
return (
    <>
      <input ref={주소값을_담는_그릇} type="text" />
    </>
);

// React에서 사용 가능한 ref라는 속성에 주소값을_담는_그릇을 값으로 할당하면
// 주소값을_담는_그릇 변수에는 input DOM 엘리먼트의 주소가 담깁니다.
// 향후 다른 컴포넌트에서 input DOM 엘리먼트를 활용할 수 있습니다.
// example
import React, { useRef } from "react";

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>);
}

// const firstRef = useRef(null); 에서
// firstRef.current === document.querySelector 이라고 생각하면 된다.

useMemo

최적화 할 때 사용함 (useCallback과 사용법이 비슷하다)

● 특정 값(value)을 재사용하고자 할 때 사용하는 Hook

계산 비용이 높은 함수의 결과 값을 캐시(저장)해서 재사용하고, 결과 값을 반환한다.

      - 즉, 계산 비용이 높은 함수의 결과 값을 최소한으로 사용하기 위해 useMemo를 사용한다.

● 종속성 배열을 기반으로 동작하고, 해당 종속성 배열 안의 값이 변경되지 않으면 이전 결과 값을 사용한다.

// useMemo 형식
import { useMemo } from "react"

const 변수이름 = useMemo (() => {
      return 기억할 값;
   }, [dependency array]
)
// Dependency array(종속성 배열)는 useEffect와 똑같다.
// 입력하지 않은 경우 -> 첫 렌더링과 props 혹은 state가 업데이트 될 때마다 실행된다.
// [] -> 첫 렌더링 시에만 실행한다.
// [num] -> 첫 렌더링과 num의 상태가 변경될 때마다 실행한다.


// example
import { useMemo } from "react"

const value = useMemo (() => {
      return calculate();
   }, [item];
)

 

// example
function Component() {
   const value = calculate()
   return <div>{value}</div>
}

function calculate () {
   return '5초정도 걸리는 코드'
}

렌더링 -> 컴포넌트 함수 호출 -> 모든 내부 변수 초기화되므로

렌더링 될 때마다 컴포넌트 함수 호출로 인해 5초 동안 더 기다리야 한다. 

(렌더링 -> 컴포넌트 함수 호출 -> 5초 기다림

  -> 만약 리렌더링 -> 컴포넌트 함수 호출 -> 5초기다림 ...)

 

// example
function Component() {
   const value = useMemo (
      () => calculate(), []   
   )
   return <div>{value}</div>
}

function calculate () {
   return '5초정도 걸리는 코드'
}

useMemo를 사용하면

렌더링 -> 컴포넌트 함수 호출 -> 5초 기다림

-> 만약 리렌더링 -> 컴포넌트 함수 호출 -> Memoize된 값 재사용(5초 기다리지 않음)


useCallback

최적화 할 때 사용함 (useMemo와 사용법이 비슷하다.)

● 특정 함수를 재사용하고자 할 때 사용하는 Hook

함수 자체를 캐시(저장)해서 재사용하는 데 사용하고, 함수 자체를 반환한다.

● 종속성 배열을 기반으로 동작하고, 해당 종속성 배열 안의 값이 변경되지 않으면 이전 함수를 재사용한다.

// useCallback 형식
import { useCallback } from "react"

const 변수이름 = useCallback (() => {
      return 기억할 함수;
   }, [dependency array]
)
// Dependency array(종속성 배열)는 useEffect와 똑같다.
// 입력하지 않은 경우 -> 첫 렌더링과 props 혹은 state가 업데이트 될 때마다 실행된다.
// [] -> 첫 렌더링 시에만 실행한다.
// [num] -> 첫 렌더링과 num의 상태가 변경될 때마다 실행한다.


// example
import React, { useCallback } from "react";

function Calculator({x, y}){
   const add = useCallback(() => {
      return x + y
   }, [x, y]);

   return 
      <>
         <div>
            {add()}
         </div>
      </>;
}
// 참고 - 원시 자료형, 참조 자료형
// double1과 double2는 같은 함수를 할당했음에도 메모리 주소 값이 다르기 때문에 같다고 보지 않습니다.

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 Hook

개발자가 스스로 커스텀한 Hook

● 여러 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 사용한다.

 

● 장점

      - 상태관리 로직의 재활용이 가능하다.

      - 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있다.

      - 함수형으로 작성하기 때문에 보다 명료하다.

      - Custom Hook 안에 다른 React Hook들을 사용할 수 있다. (useState, useEffect ...)

 

● 규칙

      - Custom Hook을 정의할 때는 함수 이름 앞에 use를 붙여야 한다.

      - 대부분의 경우 프로젝트 내의 Hooks 디렉토리에 Custom Hook을 위치시킨다.

      - Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다.

         즉 return 하는 값은 조건부여서는 안된다.

 

● 대표적인 예제로 useInput과 usefetch가 있다.

 

useInput

// useInput.js
import { useState } from "react"
export function useInput (initialValue, submitAction) {
   const [inputValue, setInputValue] = useState(initialValue);
   
   const handleChange = (event) => {
      setInputValue(event.target.value);
   };
   
   const handleSubmit = () => {
      setInputValue('');
      submitAction(inputValue);
   }
   
   return [inputValue, handleChange, handleSubmit];
}


// App.js
import { useInput } from "./useInput"

// useInput.js의 useInput의 두 번째 매개변수인 submitAction에 들어간다.
function displayMessage(message) {
   alert(message);
}

export default function App () {
   const [inputValue, handleChange, handleSubmit] = useInput("초기값 입력", displayMessage)
   const [inputValue2, handleChange2, handleSubmit2] = useInput("초기값 입력2", displayMessage)
   
   return (
      <div>
         <input value={inputValue} onChange={handleChange} />
         <input value={inputValue2} onChange={handleChange2} />
         <button onClick={handleSubmit}>확인</button>
         <button onClick={handleSubmit2}>확인</button>
      </div>
   );
}

 

useFetch

// useFetch.js
import { useEffect, useState } from "react"

export function useFetch (baseUrl, initialType) {
   const [data, setData] = useState(null);
   
   const fetchUrl = (type) => {
      fetch(baseUrl + '/' + type)
         .then(res => res.json())
         .then(jsonData => setData(jsonData));
   };
   
   useEffect (() => {
      fetchUrl(initialType)
   }, []);
   
   return { data, fetchUrl }
}

// App.js
import { useFetch } from './useFetch';

const baseUrl = 'https://jsonplaceholder.typicode.com';

export default function App () {
   const { data, fetchUrl } = useFetch (baseUrl, 'users')
      
   return (
      <div>
         <button onClick={() => fetchUrl('users')}>Users</button>
         <button onClick={() => fetchUrl('posts')}>Posts</button>
         <button onClick={() => fetchUrl('todos')}>Todos</button>
         <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
   )
}

// 또는 (위의 App.js와 결과가 같다.)
// App.js
import { useFetch } from './useFetch';

const baseUrl = 'https://jsonplaceholder.typicode.com';

export default function App () {
   // { data: userData }에서 data: 없으면 인식하지 못한다.
   // data: 는 useFetch.js에서 type으로 filter하여 가져온 data이다.
   const { data: userData } = useFetch (baseUrl, 'users')
   const { data: postData } = useFetch (baseUrl, 'posts')
   const { data: todoData } = useFetch (baseUrl, 'todos')
      
   return (
      <div>
         {userData && <pre>{JSON.stringify(userData, null, 2)}</pre>}
         {postData && <pre>{JSON.stringify(postData, null, 2)}</pre>}
         {todoData && <pre>{JSON.stringify(todoData, null, 2)}</pre>}
      </div>
   )
}

 

 

State 알아보는 시간이었습니다.

틀린 내용은 댓글로 알려주시면 감사하겠습니다.

 

 

반응형
profile

Space

@Space_zero

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!