본문 바로가기

React

[새싹 프론트엔드] 8주차 - 1 Hooks

Hooks

Hooks란?

• 리액트 버전 16.8에 새로 도입된 기능

• 함수형 컴포넌트에서 상태 관리를 할 수 있는 기능 제공

 

종류

• useState 

• useEffect

• useReducer

• useMemo

• useCallback

• useRef

 

useState()

• 가장 기본적인 Hook

• 함수형 컴포넌트가 가변적인 상태를 지닐 수 있도록 해 줌

 

• 형태

const [state, setState] = useState(초기값);

 

• 예시

const [ number, setNumber ] = useState(3);

• 현재 number 값 = 3

• number 값 변경 = setNumber(6);

 

 

실습) AddName

 

AddName.js (App.js에서는 호출하는 중)

import React, { useState } from "react";
function AddName() {
  const [names, setNames] = useState(["정수아", "리액트"]);
  const [input, setInput] = useState("");
  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames([input]);
  }

  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

 

실행 결과)

"리액트" 다음 줄에 "내용추가"가 올 줄 알았는데 기존 내용 없어지고 "내용추가"만 입력되는 문제 발생 

 

문제 원인: map함수를 names라는 배열에 돌리면서 p태그를 그려라 하고 있고 names 라는 배열 안에 두개의 데이터가 있음.  uploadInput 코드가 내용을 추가 중이며 기존에 있던 names를 새로운 값으로 바꿔주세요 라고 하고 있음.

→ 인풋 값으로 인풋 안에는 기존의 값 유지 안하고  내가 새롭게 입력한 한 개로 바꿔주세요(갱신해주세요)라고 한 꼴임   

 

따라서 기존의 값 유지해달라는 코드를 추가해야함 

 

uploadInput() 함수 수정

state의 기존 값을 유지하면서 새로운 값을 추가하려면 setState()의 콜백 함수prevState 값을 전달해서 유지해야 함

 

function uploadInput() {
       setNames((prevState)  =>  [input, ...prevState]);
}

 

위에는 화살표 함수 아래는 그냥 함수 (둘이 같음)

 

function uploadInput() {
       setNames(function(prevState) {
               return [input, ...prevState]
        });
}

위 코드 추가하여 AddName.js 수정

import React, { useState } from "react";
function AddName() {
  const [names, setNames] = useState(["정수아", "리액트"]);
  const [input, setInput] = useState("");
  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]); //수정완료
  }

  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

실행결과)

 

prevsate는 매개변수일 뿐(이름바꿔도 상관 없음) 그러나 이전 값들이 들어온다

 

게시판 댓글기능 활용

 

setState() 함수 동작 과정

 

render() - 화면에 출력 바뀐 것만 자동으로 갱신

 

 

setState() 함수

setState() 함수의 인자로 state를 전달 시 동작 과정

이전 state와 새로운 state를 비교하여 바뀐 데이터만 업데이트

• 변경되지 않은 값은 그대로 유지함

 

 

 

useState() 성능 최적화

성능 최적화

• useState() 함수의 인자에 초기값을 지정한 경우

      • state 값이 업데이트 될 때마다 초기값이 계속해서 호출됨

      • 만약 초기값에 복잡한 계산식이 있다면 성능 저하 문제 발생

 

 

실습)

AddName.js 수정

import React, { useState} from "react";

function AddName() {
  const [names, setNames] = useState(heavyWork());
  const [input, setInput] = useState("");

  function heavyWork() {
    for (let i = 0; i < 1000; i++) {
      console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
    }
    return ["정수아", "리액트"];
  }

  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
 
  });

 

  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

실행 결과

 

문제점

• state가 업데이트 될 때마다 heavyWork()가 계속 호출

• 초기값은 최초 한번만 호출되도록 수정해야 함

 

해결 방법

• useState()함수의 인자로 콜백 함수를 넣어줌

 

 

AddName.js 수정

import React, { useState} from "react";

function AddName() {
  const [names, setNames] = useState(() => heavyWork()); //콜백함수로 넣어줘서 수정완료
  const [input, setInput] = useState("");

  function heavyWork() {
    for (let i = 0; i < 1000; i++) {
      console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
    }
    return ["정수아", "리액트"];
  }

  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
 
  });


  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

 

실행 결과

 

 


useEffect()

• 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정해주는 Hook

• 최초에 한번 실행하게 하고 싶은 작업을 작성할 때 주로 사용

   예) fetch()를 이용한 네트워크 통신 연결

 

 

 

useEffect() 구조

useEffect() 매개변수로 두개의 데이터를 받아올 수 있음. (첫번째는 필수, 두번째는 옵션)

매개변수 1 - 콜백함수

매개변수 2 - 배열 or [변수이름]

 

 

첫번째 케이스)  useEffect()의 매개변수에 콜백함수만 있는 경우

• useEffect()의 매개변수에 콜백함수만 있는 경우

    • 컴포넌트가 렌더링 될 때마다 실행됨

 

useEffect(() => {
    // 작업
});

 

콜백함수만 있는 경우엔 사용하는 의미가 없음

 

MyComponent() {

   function test(){

     //작업

   } 

- 네트워크 통신 -> 데이터를 가져오기

- 매번 데이터를 가져온다.

 


 

두번째 케이스) useEffect()의 매개변수에 콜백함수, 배열이 있는 경우

     매개변수1: 콜백함수

     매개변수2: 배열 => []

    • 컴포넌트가 처음 렌더링 될 때 실행

    • value 값이 변경 되었을 때 실행

 

-  최초에 한번만 콜백함수를 실행 (배열을 비워주기)

useEffect(() => {
     // 작업
}, []);

 


 

세번째 케이스) useEffect()의 매개변수인 배열에 데이터가 있는 경우

매개변수1 : 콜백함수

매개변수2 : 배열-> [변수이름]

 

- 변수의 값이 변경될 때마다 콜백함수 실행

useEffect(() => {
     // 작업
}, [value]);

 

👉 의미있게 사용하려면 매개변수 2개 넣어야 함

 

 

 

실습) useEffect 사용하여 AddName.js 수정

import React, { useState, useEffect } from "react";
function AddName() {
  const [names, setNames] = useState(() => heavyWork());
  const [input, setInput] = useState("");

  function heavyWork() {
    for (let i = 0; i < 1000; i++) {
      console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
    }
    return ["정수아", "리액트"];
  }

  function InputChange(e) {
    setInput(e.target.value);
  }
  
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
  
  useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    console.log({ names });
  });

 
  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

 

실행 결과

 

 

mount될 때만 실행하고 싶을 때

함수의 두 번째 매개변수에 빈 배열을 넣음

useEffect(()=>{
                            console.log("렌더링이 완료되었습니다.");
                            console.log({names});
}, [])

 

useEffect 두번째 변수에 빈배열 추가하여 AddName.js 수정

import React, { useState, useEffect } from "react";
function AddName() {
  const [names, setNames] = useState(() => heavyWork());
  const [input, setInput] = useState("");

  function heavyWork() {
    for (let i = 0; i < 1000; i++) {
      console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
    }
    return ["정수아", "리액트"];
  }

  function InputChange(e) {
    setInput(e.target.value);
  }
  
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
  
  useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    console.log({ names });
  }, []);

 
  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

 

실행 결과)

 

 

특정 값이 update될 때만 실행하고 싶을 때

함수의 두 번째 매개변수 배열에 검사하고 싶은 값을 넣어줌

 

useEffect(()=>{
                             console.log("렌더링이 완료되었습니다.");
                             console.log({names});
}, [names])

 

import React, { useState, useEffect } from "react";
function AddName() {
  const [names, setNames] = useState(() => heavyWork());
  const [input, setInput] = useState("");

  function heavyWork() {
    for (let i = 0; i < 1000; i++) {
      console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
    }
    return ["정수아", "리액트"];
  }

  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
  useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    console.log({ names });
  }, [names]);

 

  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

 

실행 결과)

 

※ 내꺼만 두번 렌더링 되는 사항이 발생

index.js 에서 블록 처리 -> 1번만 랜더링 됨

 

 

뒷정리하기 - cleanup

컴포넌트가 update되기 직전 또는 unmount되기 직전에 어떠한 작업을 수행하고 싶다면 뒷정리(cleanup) 작업을 해줘야 함

 

해결 방법

 useEffect() 함수 내부에서 return 함수를 반환하면 됨

 

 

실습) cleanup

 

AddName.js 수정

import React, { useState, useEffect } from "react";

function AddName() {
  const [names, setNames] = useState(() => heavyWork());
  const [input, setInput] = useState("");

  function heavyWork() {
    for (let i = 0; i < 1000; i++) {
      console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
    }
    return ["정수아", "리액트"];
  }

  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
  useEffect(() => {
    console.log("렌더링이 완료되었습니다.");
    console.log({ names });
    
    return() => {   //뒷작업 해주기
    console.log("cleanup");
    console.log({names});
    }
  }, [names]);

  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <div>
        {names.map((name, idx) => (
          <p key={idx}>{name}</p>
        ))}
      </div>
    </div>
  );
}
export default AddName;

 

 

실행 결과

 


useRef()

컴포넌트 내부에서 사용되는 변수를 저장하는 Hook

 

특징

• 컴포넌트가 재렌더링되어도 저장된 변수 값을 유지 - 실제 화면을 다시 그리진 않음

• 불필요한 렌더링을 방지할 수 있음

특정 DOM 요소에 접근 가능

 

사용 방법

const ref = useRef(value)

• useRef() 함수는 value 값으로 초기화된 ref 객체를 반환

 

• ref 객체

{ current : value }

 

• ref 객체 값 변경

ref.current = "hello"

함수에 매개변수로 내가 넣어주고 싶은 값을 넣어주면 됨

 

 

실습) useRef() 사용하여 값 저장하기

 

UseRefComponent1.js

import React, {useRef} from 'react';

const UseRefComponent1 = () => {
    const ref = useRef("안녕하세요");
    console.log("변경 전 ref 값 : ", ref.current);

    ref.current = "Hello";
    console.log("변경 후 ref 값 : ", ref.current);
    return (
        <div>  
        </div>
    );
};

export default UseRefComponent1;

 

 

실행 결과)

 

실습) State vs useRef()

 

UseRefComponent2.js

import React, { useState, useRef } from "react";

const UseRefComponent2 = () => {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  function addStateHandler() {
    console.log("STATE 변경");
    setCount(count + 1);
  }

  function addRefHandler() {
    countRef.current = countRef.current + 1;
    console.log(countRef.current);
  }
  return (
    <div>
      <h3>State 값 : {count}</h3>
      <h3>Ref 값 : {countRef.current}</h3>
      <button onClick={addStateHandler}>State</button>
      <button onClick={addRefHandler}>Ref</button>
    </div>
  );
};

export default UseRefComponent2;

 

 

실행 결과

• State 버튼 5번 클릭, Ref 버튼 3번 클릭

 

• State 버튼 1번 클릭

 

UseRefComponent3.js

import React, { useState, useRef } from "react";

const UseRefComponent3 = () => {
  const [refresh, setRefresh] = useState();
  const countRef = useRef(0);
  let currentVar = 0;

  function refreshHandler() {
    setRefresh(refresh + 1);
  }

  function addRefHandler() {
    countRef.current = countRef.current + 1;
    console.log("ref : ", countRef.current);
  }

  function addVarHandler() {
    countRef.current = countRef.current + 1;
    console.log("ref : ", countRef.current);
  }

  function addVarHandler() {
    currentVar = currentVar + 1;
    console.log("var : ", currentVar);
  }
  return (
    <div>
      <h3>Ref 값 : {countRef.current}</h3>
      <h3>변수 값 : {currentVar}</h3>
      <button onClick={addRefHandler}>Ref</button>
      <button onClick={addVarHandler}>Var</button>
      <button onClick={refreshHandler}>Rendering</button>
    </div>
  );
};

export default UseRefComponent3;

 

실행 결과

• Ref 버튼 3번 클릭, Var 버튼 2번 클릭, Rendering 버튼 1번 클릭

 

useState값이 변경되면 화면이 렌더링 됨

useRef 값은 저장, 변경되고 있지만 화면 렌더링(출력)은 안 됨

 

컴포넌트 내부에서 자주 바뀌는 거 (화면에 영향을 미치지는 않는) - useRef에 저장하면 됨

화면을 재렌더링 한다는 것은 컴퓨터 메모리를 많이 쓴다는 것임

 

일반변수

값은 바뀌지만 렌더링되는 순간에 값이 초기값으로 되돌아감 - 거의 안 씀

 

 

실습) useRef()로 DOM에 접근하기

import React, {useEffect, useRef} from 'react';

const UseRefDom = () => {
    const inputRef = useRef();

    useEffect(()=> {
        console.log(inputRef);

        //input 태그에 focus 설정
        inputRef.current.focus();
    }, [])
    return (
        <div>
           ID : <input type="text" ref={inputRef} />
        </div>
    );
};

export default UseRefDom;

 

 

실행 결과

 

useRef -  ref는 JS의 getElementById()처럼, component의 어떤 부분을 선택할 수 있게 해주는 함수
리액트에 있는 모든 component는 reference element를 가지고 있어서,
어떤 component에 ref={변수명} 을 넣어주면, 해당 component를 참조하게 됨

 

출처)

https://velog.io/@suuhyeony/React-useRef-useEffect

 

[리액트] useRef, useEffect

리액트에 내장된 Hook에 대해 더 알아보자.특정 DOM을 가리킬 때 사용하는 Hook 함수.Ex. 포커스 설정, 특정 엘리먼트의 크기/색상 변경 등..: ref는 JS의 getElementById()처럼, component의 어떤 부분을 선택

velog.io

 

 

 

[실습회고]

 

1. 라면 

렛 

이런식으로 뜨는 문제 발견

import React, {useState} from "react";

const Practice1 = () => {
  const [names, setNames] = useState(["초콜렛", "사탕"]);
  const [input, setInput] = useState("");

  function InputChange(e) {
    setInput(e.target.value);
  }
  function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
  return (
    <div>
      <input type="text" onChange={InputChange} />
      <button onClick={uploadInput}>추가</button>
      <ul>
        {names.map((name, idx) => (
          <li key={idx}>{name}</li>
        ))}
      </ul>
    </div>
  );
};

export default Practice1;

 

 function uploadInput() {
    setNames((prevState) => [input, ...prevState]);
  }
여기서 [prevState]라고 배열로 감싸줘서 그랬던 거였다
function inputHandler(e){
        console.log("text 값이 변경되었습니다.")
        setInput (e.target.value);
    }
h1 값 바꾸기
 
 
 
 

3.

import React, { useEffect, useState } from "react";

const Practice3 = () => {
  const [count, setCount] = useState(0);
  const [renderCount, setRenderCount] = useState(-1);
  useEffect(() => {
    setRenderCount(renderCount + 1);
    console.log("랜더링 완료");
  },[count]);
  return (
    <div>
      <h1>Count : {count}</h1>
      <h1>랜더링 횟수 : {renderCount}</h1>
      <button onClick={() => setCount(count + 1)}>클릭</button>
    </div>
  );
};

export default Practice3;

useRef를 써야 하는 문제인데 안 쓰고 풀어벌인 ,,,

그리고 렌더링 카운트 세는 게 0부터 시작이 안 되어서 -1로 박아놓은 상태

 

풀이

import React, { useEffect, useRef, useState } from "react";


const Prac03 = () => {
  const [count, setCount] = useState(0);

  // 값이 변해도 렌더링되지 않는 useRef()를 사용
  const renderCount = useRef(0);

  // 렌더링 될 때마다 실행
  useEffect(() => {
    renderCount.current = renderCount.current + 1;
    console.log("렌더링 완료");
  });

  return (
    <div>
      <h1>Count : {count}</h1>
      <h1>렌더링 횟수 : {renderCount.current}</h1>
      <button onClick={() => setCount(count + 1)}>클릭</button>
    </div>
  );
};


// const Prac03 = () => {
//   const [count, setCount] = useState(0);
//   const [renderCount, setRenderCount] = useState(0);

//   // 의존성 배열이 없기 때문에, 렌더링 될 때 마다 useEffect()가 실행
//   // 따라서 setRenderCount()는 계속 호출 됨
//   useEffect(() => {
//     setRenderCount(renderCount + 1);
//     console.log("렌더링 완료");
//   });

//   return (
//     <div>
//       <h1>Count : {count}</h1>
//       <h1>렌더링 횟수 : {renderCount}</h1>
//       <button onClick={() => setCount(count + 1)}>클릭</button>
//     </div>
//   );
// };

export default Prac03;

 

 4. 풀었긴 했는데 처음엔 또 useref를 안썼었다,,,

 

풀이

import React, { useRef, useState } from "react";

/*
const Prac04 = () => {
  const [currentText, setCurrentText] = useState("");
  const inputRef = useRef();   // inputRef = { current : input }

  function sendBtnHandler(e) {
    setCurrentText(inputRef.current.value); // 렌더링 -> inputRef.current.value 화면 업데이트
    console.log("렌더링 완료");
  }

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={sendBtnHandler}>전송</button>
      <h3>전송된 단어 : {currentText}</h3>
    </div>
  );
};
*/

const Prac04 = () => {
  const [text, setText] = useState("");
  const [currentText, setCurrentText] = useState("");

  function changeHandler(e) {
    setText(e.target.value);
    console.log(text);
  }

  return (
    <div>
      <input type="text" value={text} onChange={changeHandler} />
      <button onClick={() => setCurrentText(text)}>전송</button>
      <h3>전송된 단어 : {currentText}</h3>
    </div>
  );
};

export default Prac04;

 

 useRef 굉장히 편한 것 같은데 아직 익숙치 않아서 사용하기 쉽지 않다 ,, 그러나 익숙해지도록 노력할 것 ,,,!

 

 

 

 

 

 

새싹DT 기업연계형 프론트엔드 실무 프로젝트 과정 8주차 블로그 포스팅