230620 useState | useEffect | useRef | useContext

나윤빈·2023년 6월 20일
0

TIL

목록 보기
5/55

📌 React Hook: useState

1) 가장 기본적인 react hook

2) 함수형 컴포넌트 내에서 가변적인 상태를 갖게 함

3) 형태

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

useState가 반환하는 것은 배열임으로 배열을 구조분해 할당으로 받음
배열의 return 값은 초기값으로 설정해놓은 state와 state를 제어하는 setState로 이루어짐

4) 함수형 업데이트

일반적인 업데이트

import "./App.css";
import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State: {number}</div>
      <button
        onClick={() => {
        setNumber(number + 1)
        }}
      >
        누르면 UP
      </button>
    </>
  );
}
export default App;

함수형 업데이트

import "./App.css";
import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State: {number}</div>
      <button
        onClick={() => {
          setNumber((currentNumber) => {
            return currentNumber + 1;
          });
        }}
      >
        누르면 UP
      </button>
    </>
  );
}
export default App;

함수형 업데이트는 setState 함수 안에 수정할 값이 아니라 함수를 넣을 수 있음 그리고 그 함수의 인자로 현재 state를 가져올 수 있으며 메인 로직 부분에 값을 변경하는 코드를 작성할 수 있음

5) 일반적인 업데이트와 함수형 업데이트의 차이

일반적인 업데이트

import "./App.css";
import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State: {number}</div>
      <button
        onClick={() => {
          setNumber(number + 1)
          setNumber(number + 1)
          setNumber(number + 1)
        }}
      >
        누르면 UP
      </button>
    </>
  );
}
export default App;

숫자가 1씩 올라감
왜? 배치성으로 처리하기 때문
배치성 업데이트란? 명령들을 다 모아서 최종적으로 한 번만 실행

함수형 업데이트

import "./App.css";
import { useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State: {number}</div>
      <button
        onClick={() => {
          setNumber((currentNumber) => currentNumber + 1);
          setNumber((currentNumber) => currentNumber + 1);
          setNumber((currentNumber) => currentNumber + 1);
        }}
      >
        누르면 UP
      </button>
    </>
  );
}
export default App;

숫자가 3씩 올라감
왜? 함수형 업데이트는 명령들을 모아서 순차적으로 각각 한 번씩 실행하기 때문에
어떻게? 인자로 현재 state를 받아서 업데이트 된 state를 반환하기 때문에

6) React는 왜 배치성 업데이트?

react에서 랜더링이 잦다는 것은 성능에 이슈가 있는 것, 즉 불필요한 랜더링을 피하기 위함

📌 React Hook: useEffect

1) 언제? 리액트 컴포넌트가 랜더링 될 때마다 특정한 작업을 수행해야 할 때 설정

2) 컴포넌트가 화면에 보여졌을 때

3) 컴포넌트가 화면에서 사라졌을 때(return)

4) 형태

  useEffect(() => {
    console.log("hello useEffect");
  });

매개변수로 함수가 들어감

5) 코드 읽기

import "./App.css";
import { useEffect, useState } from "react";

function App() {
  const [value, setValue] = useState("");
  
  useEffect(() => {
    console.log("hello useEffect");
  });

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}
export default App;
  1. input에 값을 입력
  2. value, 즉 state가 변경
  3. state가 바뀌었기 때문에 App 컴포넌트가 리랜더링
  4. 리랜더링 되었기 때문에 useEffect 다시 실행
  5. 1~4번 반복
    즉, input 태그에 값이 입력할 때마다 랜더링이 다시 되고 있다!
    처음 로딩이 되었을 때만 실행되도록 하고 싶다면? 의존성 배열!

6) Dependency Array

의존성 배열이란? 이 배열에 값을 넣으면, 그 값이 바뀔 때만 useEffect를 실행한다.

형태 : useEffect 함수 끝나는 곳 뒤에 두 번째 인자로 배열을 추가해준다

  useEffect(() => {
    console.log("hello useEffect");
  }, []);

의존성 배열에 아무것도 입력을 안 했음에도 동작한다? 어떤 값이 변하든지 간에 의존성 배열에는 값이 없기 때문에 어떤 state가 변해도 화면이 처음 로딩될 때만 동작한다!

value가 변경 됐을 때 동작하게 하고 싶다면?

  const [value, setValue] = useState("");

  useEffect(() => {
    console.log(`hello useEffect! : ${value}`);
  }, [value]);

위 state가 바뀔 때마다 동작하도록 할 수 있음

7) clean up
처음에 어떤 컴포넌트가 사라졌을 때 무언가를 실행하고 싶다즉, 화면에서 사라질 때 동작하게 하고 싶다면?

  useEffect(() => {
    console.log(`hello useEffect! : ${value}`);

    return () => {
      console.log("나 사라져요!");
    };
  }, [value]);

useEffect 안에서 return문을 함수 형태로 작성해주기

📌 React Hook: useRef

1) 형태

 const ref = useRef("초기값");

➡️ ref {current: '초기값'}

ref로 선언한 값에 접근하여 변경도 가능

import "./App.css";
import { useRef } from "react";

function App() {
  const ref = useRef("초기값");
  console.log("ref", ref);

  ref.current = "변경값";
  console.log("ref2", ref);

  return <div></div>;
}
export default App;

➡️ ref2 {current: '변경값'}

💡 useRef를 통해서 만든 값은 컴포넌트가 계속해서 랜더링이 되더라도 컨포넌트가 죽기 전까지 값을 유지한다!!

2) 저장공간으로서의 useRef

state와 비슷한 역할을 한다. 다만! state는 변화가 일어나면 다시 랜더링이 되지만 (함수가 다시 그려지지만) ref에 저장한 값은 변화가 일어나도 렌더링을 일으키지 않는다. 따라서 랜더링으로 인해 내부 변수들이 초기화 되는 것을 막을 수 있음 즉! 컴포넌트가 여러 번 렌더링 되더라고 ref에 저장한 값은 유지 됨

💡 state는 리렌더링이 꼭 필요한 값을 다룰 때 쓰고, ref는 리렌더링을 발생시키지 않은 값을 저장할 때 쓴다

2-1) state와 ref의 차이

import "./App.css";
import { useState, useRef } from "react";
const style = {
  border: "1px solid black",
  margin: "10px",
  padding: "10px",
};
function App() {
  
  const [count, setCount] = useState(0);
  
  const countRef = useRef(0);

  const plusStateCountButtonHandler = () => {
    setCount(count + 1);
  };

  const plusRefCountButtonHandler = () => {
    countRef.current++;
    console.log(countRef.current);
  };

  return (
    <>
      <div style={style}>
        state 영역입니다. {count} <br />
        <button onClick={plusStateCountButtonHandler}>state 증가</button>
      </div>
      <div style={style}>
        ref 영역입니다. {countRef.current} <br />
        <button onClick={plusRefCountButtonHandler}>ref 증가</button>
      </div>
    </>
  );
}
export default App;

➡️ state는 버튼을 누를 때마다 화면에 숫자도 함께 올라가고, ref 값은 버튼을 누를 때마다 변하지만 화면에 숫자는 바뀌지 않음을 알 수 있다.

3) DOM요소 접근으로서 useRef

🤔 화면이 랜더링 될 때 아이디가 focusing 되게 해줘!

import "./App.css";
import { useState, useEffect, useRef } from "react";

function App() {

  // '아이디 input'를 지정해주자!-> 'input'에 대한 레퍼런스 필요!! : useRef!!
  const idRef = useRef("");

  // 화면이 랜더링 될 때, 어떤 작업을 하고 싶다! : useEffect!!!
  useEffect(() => {
    // input 태그를 지칭
    idRef.current.focus();
  }, []);

  return (
    <>
      <div>
        {/* input 태그 안에 ref라는 속성이 존재! 
       ref 속성 안에 useRef로 선언한 변수를 넣어주면 
       input태그와 레퍼런스가 연결 됨! */}
        아이디 : <input type="text" ref={idRef} />
      </div>
      <div>
        비밀번호 : <input type="password" />
      </div>
    </>
  );
}
export default App;

🤔 아이디가 10자리가 입력되면 자동으로 비밀번호 필드로 이동시켜줘!

import "./App.css";
import { useState, useEffect, useRef } from "react";

function App() {

  const idRef = useRef("");
  const pwRef = useRef("");

  // 아이디에 입력된 값을 트레킹할 필요 있음...! : useState
  const [id, setId] = useState("");

  useEffect(() => {
    idRef.current.focus();
  }, []);

  // 아이디의 길이가 10자리인지 아닌지 파악하기 : useEffect
  // 아이디라는 depency가 바뀔 때마다 수행되어야 함-> dependency array에는 id가 들어가야 함
  useEffect(() => {
    if (id.length >= 10) {
      pwRef.current.focus();
    }
  }, [id]);

  return (
    <>
      <div>
        아이디 :{" "}
        <input
          type="text"
          ref={idRef}
          value={id}
          // id의 입력값이 하나하나 입력될 때마다 id라는 state로 들어감 (state 갱신)
          onChange={(event) => {
            setId(event.target.value);
          }}
        />
      </div>
      <div>
        비밀번호 : <input type="password" ref={pwRef} />
      </div>
    </>
  );
}
export default App;

🤔 useEffect를 꼭 써줘야 하는가! 네!

import "./App.css";
import { useState, useEffect, useRef } from "react";

function App() {
  const idRef = useRef("");
  const pwRef = useRef("");

  const [id, setId] = useState("");

  useEffect(() => {
    idRef.current.focus();
  }, []);

  // useEffect(() => {
  //   if (id.length >= 10) {
  //     pwRef.current.focus();
  //   }
  // }, [id]);

  return (
    <>
      <div>
        아이디 :{" "}
        <input
          type="text"
          ref={idRef}
          value={id}
          onChange={(event) => {
            setId(event.target.value);
            if (id.length >= 10) {
              pwRef.current.focus();
            }
          }}
        />
      </div>
      <div>
        비밀번호 : <input type="password" ref={pwRef} />
      </div>
    </>
  );
}
export default App;

위의 방식은 아이디가 10자리가 아니라 11자리일 때 비밀번호로 포커스가 이동된다
왜? 리액트에서 state를 업데이는 방식이 배치 업데이트이기 때문! 변화가 일어나는 setId state가 바로 반영되는 것이 아니라 if문에서는 반영되기 직전임

⭐️⭐️⭐️ useState는 값을 변경하면 저장되면서 동시에 화면도 바뀐다(랜더링된다) useRef는 리액트 어딘가에 저장은 되지만 화면은 안 바뀐다(랜더링 되지 않는다) 하지만..! state가 변경되면 랜더링 될 때 저장된 값을 가져온다 변수는 저장이 안되기 때문에 state가 바뀌면 초기화된다. ⭐️⭐️⭐️

📌 React Hook: useContext

1) 필요성
prop drilling의 문제점? 어디에서 prop가 왔는지 파악할 수 없고, 오류가 발생하면 원인을 찾기가 힘들어짐
그래서 등장한 것이 바로 react context API!!
어떤 컴포넌트들에서든지 전역적으로 선언된 데이터에 접근이 가능함

2) 형태

    const data = useContext(context파일의이름)

3) useContext를 사용하지 않는다면?

🤔 GrandFather가 Child한테 어떤 정보를 알려줘서 Child가 그 내용을 출력한다면?

구조

main 컴포넌트(App)가 있고, 3개의 하위 컴포넌트가 존재(GrandFather/Father/Child)

App.jsx

import "./App.css";
import GrandFather from "./components/GrandFather";

function App() {
  return <GrandFather />;
}
export default App;

GrandFather.jsx

import React from "react";
import Father from "./Father";

function GrandFather() {
  const houseName = "스파르타";
  const pocketMoney = 10000;
  return <Father houseName={houseName} pocketMoney={pocketMoney} />;
}

export default GrandFather;

Father.jsx

import React from "react";
import Child from "./Child";

function Father({ houseName, pocketMoney }) {
  return <Child houseName={houseName} pocketMoney={pocketMoney} />;
}

export default Father;

Child.jsx

import React from "react";
const style = {
  color: "red",
  fontWeight: "900",
};
function Child({ houseName, pocketMoney }) {
  return (
    <div>
      나는 이 집안의 막내에요!
      <br />
      할아버지가 우리 집 이름은 <span style={style}>{houseName}</span>라고
      하셨어요.
      <br />
      게다가 용돈도 <span style={style}>{pocketMoney}</span>원 만큼이나
      주셨어요!
    </div>
  );
}

export default Child;

➡️ GrandFather 컴포넌트는 Child 컴포넌트에게 houseName과 pocketMoney를 전달하기 위해 Father 컴포넌트를 거칠 수 밖에 없음!

4) useContext를 사용하면?

4-1) creatContext: context 생성

FamilyContext.js

import { createContext } from "react";

export const FamilyContext = createContext(null);

➡️ Provider로 주입하는 하위 컴포넌트들에서 사용할 수 있는 FamilyContext가 완성된 것

4-2) Provider를 적용

GrandFather.jsx

import React from "react";
import Father from "./Father";
import { FamilyContext } from "../context/FamilyContext";

function GrandFather() {
  const houseName = "스파르타";
  const pocketMoney = 10000;
  return (
    <FamilyContext.Provider
      value={{
        houseName: houseName,
        pocketMoney: pocketMoney,
      }}
    >
      <Father />
    </FamilyContext.Provider>
  );
}

export default GrandFather;

➡️ props는 더이상 필요가 없음!
왜? props를 통해서 값을 내려주는 것이 아니라 만든 context를 통해서 외부로 접근하는 것이기 때문에
➡️ FamilyContext.Provider? 하위 컴포넌트인 Father 컴포넌트 밑으로 context를 제공해준다!
➡️ property로 value를 넘겨주는데, value 안에는 객체가 들어감

Father.jsx

import React from "react";
import Child from "./Child";

function Father({ houseName, pocketMoney }) {
  return <Child />;
}

export default Father;

4-3) Context로부터 data 받아오기

Child.jsx

import React, { useContext } from "react";
import { FamilyContext } from "../context/FamilyContext";
const style = {
  color: "red",
  fontWeight: "900",
};
function Child() {
  const data = useContext(FamilyContext);
  return (
    <div>
      나는 이 집안의 막내에요!
      <br />
      할아버지가 우리 집 이름은 <span style={style}>{data.houseName}</span>라고
      하셨어요.
      <br />
      게다가 용돈도 <span style={style}>{data.pocketMoney}</span>원 만큼이나
      주셨어요!
    </div>
  );
}

export default Child;

➡️ props로 내려준 값을 쓴 것이 아니라,context를 이용해서 값을 받아옴

🔥 랜더링 이슈
useContext를 사용할 때, Provider(GrandFather컴포넌트)에서 제공한 value가 달라진다면 useContext를 사용하고 있는 모든 컴포넌트(Father 이하의 컴포넌트)가 전부 리렌더링 됨! -> 매우 비효율적

profile
프론트엔드 개발자를 꿈꾸는

0개의 댓글