heyday2024 님의 블로그
[React 숙련 1주차(2)] 리액트 훅: useState, useEffect, useRef, useContext 본문
[React 숙련 1주차(2)] 리액트 훅: useState, useEffect, useRef, useContext
heyday2024 2024. 11. 5. 16:011. useState
useState는 가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 가지게 해줌.
const [state, setState] = useState(initialState);
- useState 라는 함수가 배열을 반환하고, 이것을 구조 분해 문법으로 꺼내놓은 모습.
- 위 코드에서는 state를 변수로 사용했고, setState를 이용해서 state 값을 수정할 수 있었음.
- 만약 state가 원시 데이터타입이 아닌 객체 데이터 타입인 경우에는 불변성을 유지해줘야함.(spread operator 사용.)
<state를 업데이트할 수 있는 두가지 방법>
1. 직접 새로운 값을 전달하기
- setState 함수에 직접 새로운 값을 전달하여 상태를 업데이트.
- 이 경우, 이전 상태를 참조하지 않으므로 단순히 새로운 값을 설정하는 상황에 적합.
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1); // 새로운 값을 직접 전달
};
위에 방식과 달리 setState의 () 안에 수정할 값이 아니라 함수를 넣을 수 있는데, 이 함수의 인자에서는 현재의 state를 가져올 수 있고, 이 것으로 값을 변경하는 코드를 작성할 수 있음.
2. 업데이트 함수로 상태 설정하기
- setState에 함수를 전달하여 현재 상태를 기반으로 새로운 상태를 설정.
- 이전 상태를 참조해야 할 경우에 유용하며, 특히 상태가 비동기적으로 업데이트될 때 안정적인 동작을 보장.
const [count, setCount] = useState(0);
const increment = () => {
setCount((prevCount) => prevCount + 1); // 이전 상태를 기반으로 새로운 값 설정
};
// src/App.js
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 1씩 플러스된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber(number + 1); // 첫번째 줄
setNumber(number + 1); // 두번쨰 줄
setNumber(number + 1); // 세번째 줄
}}
>
버튼
</button>
</div>
);
}
export default App;
- 이 코드를 실행하면, 버튼 클릭했을 때 3이 한번에 증가하는 것이 아니라, 1씩 증가한다.
// src/App.js
import { useState } from "react";
const App = () => {
const [number, setNumber] = useState(0);
return (
<div>
{/* 버튼을 누르면 3씩 플러스 된다. */}
<div>{number}</div>
<button
onClick={() => {
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
setNumber((previousState) => previousState + 1);
}}
>
버튼
</button>
</div>
);
}
export default App;
- 이 코드를 실핼하면 한번의 클릭 당 3씩 증가한다.
왜 다르게 동작할까요?
일반 업데이트 방식은 버튼을 클릭했을 때 첫번째 줄 ~ 세번째 줄의 있는 setNumber가 각각 실행되는 것이 아니라, 배치(batch)로 처리함.
즉 우리가 onClick을 했을 때 setNumber 라는 명령을 세번 내리지만, 리액트는 그 명령을 하나로 모아 최종적으로 한번만 실행을 시킴. 그래서 setNumber을 3번 명령하던, 100번 명령하던 1번만 실행됨.
반면에 함수형 업데이트 방식은 이전 상태를 기반으로 새로운 상태를 계산하는 형태이기 때문에, 3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번씩 실행시킴.
===> 이 특징을 살려서 부모-자식 컴포넌트에서 state를 변경할 때, 이전 처럼 필요한 모든 속성들을 넘겨받을 필요 없이!
업데이트를 위한 함수만을 넘겨 받아 그 함수 안에서 현재 state를 이용해 업데이트 해줄 수 있음!!!
1) count와 setCount 둘 다 넘겨줌
setCount(count + 1);
2) setCount만 넘겨줌
setCount(prev => prev + 1);
===> 이 두번쨰 방법처럼 함수형 업데이트를 이용함으로서 불필요한 props를 전달 받지 않아도됨.
리액트는 성능을 위해 setState()를 단일 업데이트(batch update)로 한꺼번에 처리할 수 있음!!
불필요한 리-렌더링을 방지(렌더링 최적화)하기 위해 즉, 리액트의 성능을 위해 한꺼번에 state를 업데이트 한다고함.
2. useEffect
useEffect는 리액트 컴포넌트가 렌더링 된 이후마다 특정 작업을 수행하도록 설정할 수 있는 hook임.
( 어떤 컴포넌트가 화면에 보여졌을 때 내가 무언가를 실행하고 싶다면? 또는 어떤 컴포넌트가 화면에서 사라졌을 때 무언가를 실행하고 싶다면? useEffect를 사용)
즉, 함수형 컴포넌트에서 side effect를 처리하기 위해 사용함.
==> import React, {useEffect} from "react"; 로 import해서 사용함.
==> 이 Hook을 통해 컴포넌트가 렌더링될 때마다 특정 작업을 수행하거나, 상태 변경, API 요청, 이벤트 리스너 등록과 같은 작업을 수행할 수 있음.
// src/App.js
import React, { useEffect } from "react";
const App = () => {
useEffect(() => {
// 이 부분이 실행된다.
console.log("hello useEffect");
});
return <div>Home</div>;
}
export default App;
브라우저에서 우리가 App 컴포넌트를 눈으로 보는 순간, 즉, App 컴포넌트가 화면에 렌더링 된 이후 useEffect 안에 있는 console.log가 실행됨. 여기서 핵심은 컴포넌트가 렌더링 된 이후 실행된다는 것임!
하지만, useEffect를 사용할 때 고려해야할 부분이 있다!
useEffect와 리렌더링
useEffect는 useEffect가 속한 컴포넌트가 화면에 렌더링 된 이후 실행되는데, 이 특징에 의해 의도치않은 동작을 경험하게 될 수 있다.
아래 코드 과정을 살펴보면,
- input에 값을 입력한다.
- value, 즉 state가 변경된다.
- state가 변경되었기 때문에, App 컴포넌트가 리렌더링 된다.
- 리렌더링이 되었기 때문에 useEffect가 다시 실행된다.
- 1번 → 5번 과정이 계속 순환환다.
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
useEffect(() => {
console.log("hello useEffect");
});
return (
<div>
<input
type="text"
value={value}
onChange={(event) => {
setValue(event.target.value);
console.log("value => ", value);
}}
/>
</div>
);
}
export default App;
즉, 여기서 useEffect는 컴포넌트의 해당 value가 변하는 과정에서 리렌더링될때마다 매번 실행되어 콘솔에 hello useEffect가 계속 찍히게 됨을 알 수 있다.
만약, useffect를 이용해서 컴포넌트가 마운트될 때 한번만 찍히길 원했다면, 위와 같은 코드는 적합하지 않다.
그래서 꼭 알아야할 의존성 배열!!
(''이 배열에 값을 넣으면, 그 값이 바뀔 때만 useEffect를 실행할게'')
의존성 배열(dependency array)은 React의 useEffect Hook에서 두 번째 인자로 전달되어, useEffect가 언제 실행될지를 제어하는 역할을 함.
의존성 배열에 포함된 값이 변경될 때마다 useEffect의 콜백 함수가 다시 실행됨.
의존성 배열의 동작 방식
- 빈 배열 ([]): 의존성 배열을 빈 배열로 설정하면, useEffect는 컴포넌트가 마운트될 때 한 번만 실행되고, 이후에는 다시 실행되지 않는다. 이 방법은 초기 API 호출이나 단발성 작업에 유용함.
- (의존성 배열에 아무것도 넣지 않았으니, 당연히 useEffect는 최초 렌더링 때 딱 한번만 실행되고, 그 이후로는 어떤일이 일어나도 실행 안됨.)
useEffect(() => {
// 이 작업은 컴포넌트가 처음 마운트될 때 한 번만 실행됩니다.
}, []);
- 특정 값이 있는 배열 ([state1, state2]): 배열 안에 상태나 props를 넣으면, 이 값들이 변경될 때마다 useEffect가 재실행됨. 예를 들어, count 상태를 넣으면 count가 업데이트될 때마다 콜백 함수가 실행됨.
useEffect(() => {
console.log(`Count 값이 변경되었습니다: ${count}`);
}, [count]);
- 의존성 배열 생략: useEffect 호출 시 의존성 배열을 생략하면, 컴포넌트가 렌더링될 때마다 실행됨. (이것이 앞에서 다뤘던 예시의 모습.) 이 경우 모든 렌더링마다 실행되므로, 성능에 영향을 줄 수 있어 주의가 필요.
useEffect(() => {
// 렌더링될 때마다 실행됩니다.
});
의존성 배열의 역할
- 리렌더링 최적화: 필요할 때만 useEffect가 실행되도록 하여 불필요한 리렌더링을 방지.
- 상태 변화 추적: 특정 상태나 props가 변경될 때만 원하는 작업을 수행하도록 제어 가능.
- 클린업 관리: 의존성 배열을 통해 의존 값이 변경될 때마다 기존 작업을 정리(클린업)하고, 새로운 작업을 시작할 수 있음.
<참고>
React.StrictMode에서 useEffect가 두 번 실행되는 이유
React.StrictMode는 개발 모드에서만 작동하며, React 애플리케이션에서 잠재적인 문제나 비효율적인 코드 패턴을 감지하고 경고해주는 역할을 함. 이 중 하나가 컴포넌트의 사이드 이펙트를 두 번 실행하는 것인데, 이는 useEffect와 같은 Hook이 예상대로 작동하는지, 메모리 누수나 다른 문제를 일으킬 가능성이 없는지를 확인하기 위함!
===> 즉, 이중 호출을 통해 개발자가 작성한 useEffect 코드가 의도치 않은 부작용을 일으키지 않도록 체크하는 것
이중 호출이 필요한 이유
- 클린업 함수 확인: useEffect에서 반환하는 클린업 함수가 제대로 작동하는지 확인하여 메모리 누수 방지.
- 순수 함수 구현 확인: 함수가 불필요한 상태를 유지하지 않는지 확인하여 예측 가능한 동작 보장.
- 동기화 문제 예방: 컴포넌트가 동기적으로 상태와 일관성을 유지하는지 확인.
이러한 방식은 개발 모드에서만 적용되므로, 실제 배포된 애플리케이션에는 영향을 미치지 않음.
==> strict mode를 제거하는 방법은 main.jsx에서 <React.StrictMode> 부분을 제거하면됨!
clean up
컴포넌트가 unmount 되거나 특정 상태가 변경될 때 이전 효과를 정리하는 동작!
컴포넌트가 렌더링되었을 때 동작하는 것은 useEffect의 effect 함수였다면, 컴포넌트가 제거되었거나 의존성이 변경되었을 때 발생하는 부작용을 정리하는 작업을 클린 업이라고 표현함.
<클린업 하는 법>
// src/App.js
import React, { useEffect } from "react";
const App = () => {
useEffect(()=>{
// 화면에 컴포넌트가 나타났을(mount) 때 실행하고자 하는 함수를 넣어주세요.
return ()=>{
// 화면에서 컴포넌트가 사라졌을(unmount) 때 실행하고자 하는 함수를 넣어주세요.
}
}, [])
return <div>hello react!</div>
};
export default App;
useEffect 안에 return을 해주고, 이 부분에 실행되길 원하는 함수 넣으면 됨.
컴포넌트 라이프사이클
리액트 컴포넌트도 태어나고, 살아가고, 죽는 생애주기가 존재함.
클래스형 컴포넌트를 주로 사용했을 이전 버전에서는 이 생애주기와 관련된 여러 메서드가 존재했지만 현재처럼 함수형 컴포넌트를 사용할 때는 useEffect를 주로 사용하여 핸들링함!
컴포넌트의 생명주기 단계:
- 마운트 (Mount) : 컴포넌트가 DOM에 처음 삽입되어 렌더링되는 과정
- 업데이트 (Update): 컴포넌트의 상태(state)나 props가 변경될 때마다 재렌더링되는 과정
- 언마운트 (Unmount): 컴포넌트가 DOM에서 제거되는 과정(클린업 작업- 사용중인 리소스를 정리하거나 해제하는 작업)
==> 함수형 컴포넌트에서 useEffect 하나로 마운트, 업데이트, 언마운트를 제어할 수 있음!
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// 마운트 시에만 실행
useEffect(() => {
console.log('컴포넌트가 마운트되었습니다.');
// 언마운트 시에 실행되는 클린업 함수
return () => {
console.log('컴포넌트가 언마운트됩니다.');
};
}, []);
// count가 업데이트될 때마다 실행
useEffect(() => {
console.log(`count가 ${count}로 업데이트되었습니다.`);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
3. useRef
React에서 제공하는 Hook 중 하나로, 특정 값을 저장하기 위해 사용함.
useRef는 리렌더링과 상관없이 값을 유지할 수 있어서 컴포넌트가 리렌더링될 때도 초기화되지 않는 변수를 생성함.
==> 이 특징을 이용해 변수를 유지하거나, 자바스크립트 DOM API를 직접 사용하지 않고 DOM 요소에 직접 접근하기 위해 사용됨.
import "./App.css";
import { useRef } from "react";
function App() {
const ref = useRef("초기값");
console.log("ref", ref);
return (
<div>
<p>useRef에 대한 이야기에요.</p>
</div>
);
}
export default App;
- useState이용할 때 처럼, 초기값 줄 수 있음.
import "./App.css";
import { useRef } from "react";
function App() {
const ref = useRef("초기값");
console.log("ref 1", ref);
ref.current = "바꾼 값";
console.log("ref 1", ref);
return (
<div>
<p>useRef에 대한 이야기에요.</p>
</div>
);
}
export default App;
- .current로 변경도 가능.
이렇게 설정된 ref 값은 컴포넌트가 계속해서 렌더링 되어도 unmount 전까지 값을 유지함!
<useRef의 두가지 용도>
1. 저장공간 (state와의 차이점)
- useRef는 state처럼 값을 저장하는 역할을 할 수 있지만, 가장 큰 차이점은 리렌더링을 발생시키지 않는다는 것입니다.
- state는 값이 변경되면 컴포넌트를 다시 렌더링하지만, useRef에 저장된 값은 리렌더링을 유발하지 않으며, 렌더링 이후에도 유지됩니다.
예를 들어, 컴포넌트가 100번 렌더링되어도 useRef에 저장된 값은 계속 유지됩니다. 이 특성 덕분에, 리렌더링이 필요하지 않은 데이터를 저장할 때 유용합니다.
정리하면:
- state는 리렌더링이 필요한 값을 다룰 때 사용합니다. 예를 들어, 화면에 표시되거나 동적으로 반영되어야 하는 값이 state에 적합합니다.
- useRef는 리렌더링이 불필요한 값을 저장할 때 사용합니다. 예를 들어, 단순히 상태를 추적하기 위한 카운터나, 리렌더링될 때마다 초기화되면 안 되는 값은 useRef에 저장합니다.
import "./App.css";
import { useRef, useState } from "react";
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const plusStateCountButtonHandler = () => {
setCount(count + 1);
};
const plusRefCountButtonHandler = () => {
countRef.current++;
};
return (
<>
<div>
state 영역입니다. {count} <br />
<button onClick={plusStateCountButtonHandler}>state 증가</button>
</div>
<div>
ref 영역입니다. {countRef.current} <br />
<button onClick={plusRefCountButtonHandler}>ref 증가</button>
</div>
</>
);
}
export default App;
- useRef는 React에서 리렌더링을 유발하지 않는 값을 저장하기 때문에, ref.current 값이 변경되어도 즉시 화면에 반영되지 않는다. 반면 useState는 상태가 업데이트되면 컴포넌트를 리렌더링하고 그 값이 화면에 바로 표시된다.
- 이 코드에서 setState 영역의 버튼을 누를 때마다 컴포넌트가 리렌더링됨. 그래서, 변화된 ref.current 값이 화면에 표시되지 않다가, setState 영역의 버튼을 누를 때 useState의 count 상태(state)가 변하면서 리렌더링되고, 그때 한번에 변화된 ref.current 값이 화면에 보이게됨.
===> let 키워드로 변수 선언해서 useRef대신 사용하려고 한다면, 리렌더링되면서(함수가 다시 호출되면서) 내부 변수가 다시 다 초기화됨. (그래서 useRef가 필요한 것임!)
2. DOM 접근
- 특정 DOM 요소에 접근해야 할 때 useRef를 사용할 수 있습니다. 예를 들어, 페이지가 로드되자마자 특정 input에 자동으로 포커스를 맞추고 싶을 때, useRef를 사용하여 DOM 요소에 접근할 수 있습니다.
- 이때 useRef는 HTML 요소에 연결되어 해당 요소를 직접 조작할 수 있는 참조를 제공하며, useEffect와 함께 사용하여 DOM 요소가 마운트된 후 포커스를 주거나 스타일을 변경할 수 있습니다.
import "./App.css";
function App() {
return (
<>
<div>
아이디 : <input type="text" />
</div>
<div>
비밀번호 : <input type="password" />
</div>
</>
);
}
export default App;
< input /> 태그에 ref라는 속성 이용. 이걸 통해 우리는 해당 DOM 요소로 접근할 수 있음.
import { useEffect, useRef } from "react";
import "./App.css";
function App() {
const idRef = useRef("");
// 렌더링이 될 때
useEffect(() => {
idRef.current.focus();
}, []);
return (
<>
<div>
아이디 : <input type="text" ref={idRef} />
</div>
<div>
비밀번호 : <input type="password" />
</div>
</>
);
}
export default App;
- 포커스를 주려면 DOM 요소에 대한 직접적인 참조가 필요한데, 이때 ref를 사용함
- ref 속성은 모든 HTML 요소에 사용할 수 있음. 따라서 input, div, span, button 등 다양한 HTML 태그에서 사용할 수 있음.
- 포커스를 주거나, 특정 값을 가져오거나, DOM 요소의 속성을 수정할 때 유용
< 아이디가 10자리 입력되면 자동으로 비밀번호 필드로 이동하는 코드>
import { useEffect, useRef, useState } from "react";
import "./App.css";
function App() {
const idRef = useRef("");
const pwRef = useRef("");
const [id, setId] = useState("");
const onIdChangeHandler = (event) => {
setId(event.target.value);
};
// 렌더링이 될 때
useEffect(() => {
idRef.current.focus();
}, []);
// 왜 useEffect 안에 놓았을까요?
useEffect(() => {
if (id.length >= 10) {
pwRef.current.focus();
}
}, [id]);
return (
<>
<div>
아이디 :
<input
type="text"
ref={idRef}
value={id}
onChange={onIdChangeHandler}
/>
</div>
<div>
비밀번호 : <input type="password" ref={pwRef} />
</div>
</>
);
}
export default App;
4. useContext
React의 Context API와 함께 사용하는 hook으로, 컴포넌트 트리에서 여러 컴포넌트 간에 데이터를 쉽게 공유할 수 있도록 도와줌.
useContext를 사용하면, 자식 컴포넌트로 반복적으로 props를 전달할 필요 없이 데이터를 바로 사용할 수 있음. (prop drilling 이슈 해결)
부모-> 자식 -> 그 자식 -> 그 자식의 자식 으로 props 전달 되면서 prop drilling이 생김.
==> prop drilling의 문제점:
- 깊이가 너무 깊어지면 이 prop이 어떤 컴포넌트로부터 왔는지 파악이 어려워져요.
- 어떤 컴포넌트에서 오류가 발생할 경우 추적이 힘들어지니 대처가 늦을 수 밖에 없죠.
===> React Context API는 컴포넌트 트리 전체에서 전역 상태나 공통 데이터를 쉽게 공유할 수 있도록 해주는 도구. Context API는 기본적으로 데이터를 생성하고, 필요한 컴포넌트에 데이터를 제공하며, 중간 컴포넌트들이 불필요하게 props를 통해 데이터를 전달할 필요를 없앰. 이를 통해 "props drilling" 문제를 해결할 수 있음.
==> 즉, useContext hook 으로 전역데이터를 쉽게 관리 할 수 있게됨.
Context API의 주요 구성 요소
- createContext: 새로운 Context 객체를 생성. 이 객체는 데이터를 공유할 수 있는 공간을 제공함.
- Provider: Context에서 데이터를 제공해주는 역할을 함. Provider를 사용해 하위 컴포넌트들에 데이터를 공급하며, value 속성으로 전달할 데이터를 지정.
- Consumer 또는 useContext: 하위 컴포넌트에서 Context의 데이터를 사용할 때 사용하는 방법 중 하나. 하지만 주로 useContext 훅을 사용하여 더 간단하게 Context 데이터를 사용
useContext와 Context API의 관계
- useContext는 Context API와 함께 쓰이는 훅으로, 특정 Context에서 제공하는 데이터를 쉽게 가져올 수 있게 함.
- useContext를 사용하면 더 이상 Consumer를 사용하지 않고도 Context 데이터를 편리하게 사용 가능.
import "./App.css";
import GrandFather from "./components/GrandFather";
function App() {
return <GrandFather />;
}
export default App;
- App.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;
- GrandFather.jsx
import React from "react";
import Child from "./Child";
function Father({ houseName, pocketMoney }) {
return <Child houseName={houseName} pocketMoney={pocketMoney} />;
}
export default Father;
- Father.jsx
import React from "react";
function Child({ houseName, pocketMoney }) {
const stressedWord = {
color: "red",
fontWeight: "900",
};
return (
<div>
나는 이 집안의 막내에요.
<br />
할아버지가 우리 집 이름은 <span style={stressedWord}>{houseName}</span>
라고 하셨어요.
<br />
게다가 용돈도 <span style={stressedWord}>{pocketMoney}</span>원만큼이나
주셨답니다.
</div>
);
}
export default Child;
- Child.jsx
===>GrandFather이 Child에게 houseName과 pocketMoney를 전달해주기 위해 Father 컴포넌트를 거칠 수 밖에 없고, 만약 이렇게 거쳐야할 컴포넌트가 10개, 100개 ... 그 이상 엄청 많다고 가정하면 코드가 너무 비효율적임.
그래서 useContext를 사용함!!!
import { createContext } from "react";
// 여기서 null로 초기화를 시켜주고, 나중에 Provider로 값을 받을 때 다시 그 값 할당 받음.
export const FamilyContext = createContext(null);
- FaimilyContext.js
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, pocketMoney }}>
<Father />
</FamilyContext.Provider>
);
}
export default GrandFather;
- GrandFather.jsx
import React from "react";
import Child from "./Child";
function Father() {
return <Child />;
}
export default Father;
- Father.jsx
import React, { useContext } from "react";
import { FamilyContext } from "../context/FamilyContext";
function Child({ houseName, pocketMoney }) {
const stressedWord = {
color: "red",
fontWeight: "900",
};
const data = useContext(FamilyContext);
console.log("data", data);
return (
<div>
나는 이 집안의 막내에요.
<br />
할아버지가 우리 집 이름은 <span style={stressedWord}>{data.houseName}</span>
라고 하셨어요.
<br />
게다가 용돈도 <span style={stressedWord}>{data.pocketMoney}</span>원만큼이나
주셨답니다.
</div>
);
}
export default Child;
- Child.jsx
==> 이처럼 useContext는 중간 컴포넌트를 거치지 않고, 필요한 곳에서 useContext로 데이터를 바로 가져올 수 있음. 하지만, Context가 전역데이터이기 때문에 이 Context의 값이 변경되면, 해당 Context를 참조하는 모든 컴포넌트가 다시 리렌더링됨으로 성능이 떨어질 수 있음!!!!
이런 부분들은 React.memo로 최적화할 수 있음!!
'프론트엔드 부트캠프' 카테고리의 다른 글
[React 숙련 1주차(4)] Redux: useSelector, dispatch action 객체, payload, ducks (0) | 2024.11.06 |
---|---|
[React 숙련 1주차(3)] Memoization - (React.memo, useCallback, useMemo), Custom Hooks (0) | 2024.11.05 |
[React 숙련 1주차(1)] Styled-Components (2) | 2024.11.04 |
[React] 올림픽 메달 추적: 개인 프로젝트 정리(2) (0) | 2024.11.01 |
[React 개인과제] 올림픽 메달 트래커 만들어보기 (1) | 2024.10.31 |