1. 올바른 초기값 설정
const [count, setCount] = useState();
const [list, setList] = useState();
위의 초기값설정은 아래처럼 변경되어야 올바른 초기값 설정입니다.
const [ count, setCount ] = useState(0);
const [ list, setList ] = useState([]);
왜 이렇게 값을 설정해야하는지 한번 알아보겠습니다.
초기값 ?
- 가장 먼저 렌더링될대 순간적으로 보여질 수 있는 값이기도 하다.
- 당연히 초기에 렌더링 되는 값
초기값 지키지 않을 경우 ?
- 렌더링 이슈, 무한루프, 타입 불일치로 의도치 않은 동작 발생 -> 런타임 에러
- 초기값을 넣지 않으면 ? : undefined
- 상태를 CRUD => 상태를 지울때도 초기값을 잘 기억해놔야 원상태로 돌아간다.
초기값을 잘 설정해주면 빈값이나 NULL 처리를 할때 불필요한 방어코드도 줄여준다
예시코드를 보면 아래코드에서는 list라는 상태값에 아무런 초기값도 할당하지 않은 상태입니다. 그 상태에서 useEffect로 api의 데이터를 fetch해서 list 상태값에 받아옵니다. 그리고 return문에서 해당 상태값을 이용해 map으로 요소들을 보여주는 코드입니다.
import { useEffect, useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
const [list, setList] = useState();
useEffect(() => {
const fetchData = async () => {
const response = await fetch("https://api.example.com/data");
const result = await response.json();
setList(result);
};
fetchData();
}, []);
return (
<>
<p>Count:{count}</p>
<button
onClick={() => {
setCount(count + 1);
}}>
Increment
</button>
{list.map((item) => {
console.log(item);
})}
</>
);
}
export default App;
위의 코드를 실행시켜보면 해당 값이 빈값이거나 null값일때 처리를 해주지 않아서 렌더링 오류가 발생하고 아무런 화면이 보이지 않게 됩니다. 그래서 위의 코드에서 list 상태값을 선언할때 빈배열 ' [] ' 을 넣어주게 된다면 이를 방지할 수 있습니다.
const [list, setList] = useState([]);
요약
- 초기상태를 올바르게 설정하자
2. 업데이트 되지 않는 값
업데이트가 되지 않는 일반적인 객체라면 리액트 외부로 내보내는 것이 좋습니다. 예를 들어보면 아래의 코드에서 INFO라는 싱글리터럴객체가 있습니다. 이런 케이스는 상수를 다루거나 아니면 일반적인 방치가 흔한 케이스입니다. 중요한것은 이 객체는 어디서도 지금 업데이트가 되고 있지 않습니다. 즉 이객체에 있는 속성을 바꾸는 경우가 전혀 존재하지 않습니다. 그리고 return문을 보면 props로 객체를 전달해주고 있습니다.
import { useEffect, useState } from "react";
import "./App.css";
function App() {
const INFO = {
name: "My Component",
value: "Clean Code React",
};
const [count, setCount] = useState(0);
const onIncrement = () => {
setCount(count + 1);
};
const onDecrement = () => {
setCount(count - 1);
};
return (
<>
<ShowCount info={INFO} count={count} />
</>
);
}
export default App;
위와 같은 경우는 정말 좋지 못한 케이스입니다. 일단 리액트 컴포넌트 내부에 있기 때문에 매번 렌더링 될때마다 같은 값이더라도 또 다시 참조해서 트리거하고 계산해야 될 시기와 기억해야 될 시기에 대한 로직이 전혀 들어가 있지 않습니다. 그러다 보니까 불필요하게 참조하고 물고 있는 겁니다. 그래서 어떻게 하냐면 컴포넌트 외부로 빼주면됩니다.
import { useEffect, useState } from "react";
import "./App.css";
const INFO = {
name: "My Component",
value: "Clean Code React",
};
function App() {
const [count, setCount] = useState(0);
const onIncrement = () => {
setCount(count + 1);
};
const onDecrement = () => {
setCount(count - 1);
};
return (
<>
<ShowCount info={INFO} count={count} />
</>
);
}
export default App;
요약
- 업데이트가 되지 않는 일반적인 객체라면 리액트 외부로 내보내기
3.플래그 상태
- 플래그 값 : 프로그래밍에서 주로 특정 조건 혹은 제어를 위한 조건을 불리언으로 나타내는 값
플래그 상태의 가장 흔한 예는 무엇일까? 바로 로그인이다. 우리가만드는 리액트 앱은 대채적으로 client state 프론트단에서 상태를 가지고 화면을 보여주는 경우가 많기 때문에 이러한 값들을 가지기 위해 정말 많은 값들을 다룹니다. 토큰, 쿠키, valid한지,새로운유저인지 등 수많은 값을 조건으로 분기합니다. 아래처럼 useEffect로 다 잡아서 관리하고 분기문을 넣어서 때려넣는 경우가 많습니다.
import { useEffect, useState } from "react";
import "./App.css";
function FlagState() {
const [isLogin, setIsLogin] = useState(false);
useEffect(()=>{
if(hasToken){
setIsLogin(true);
}
if(hasCookie){
setIsLogin(true);
}
if(isValidCookie){
setIsLogin(true);
}
if(isNewUser===false){
setIsLogin(true);
}
if(isValidToken){
setIsLogin(true);
}
},[hasToekn,hasCookie,isValidCookie,isNewUser,isValidToken]);
return (
<>
<div>{isLogin&&'안녕하세요! 반갑습니다!'}</div>
</>
);
}
export default App;
위의 코드를 좀더 깔끔히 해보면 아래처럼 if문을 하나로 합칠 수 있습니다.
import { useEffect, useState } from "react";
import "./App.css";
function FlagState() {
const [isLogin, setIsLogin] = useState(false);
useEffect(()=>{
if( hasToken &&
hasCookie &&
isValidCookie &&
isNewUser==false &&
isValidToken
)
{
setIsLogin(true);
}
},[hasToekn,hasCookie,isValidCookie,isNewUser,isValidToken]);
return (
<>
<div>{isLogin&&'안녕하세요! 반갑습니다!'}</div>
</>
);
}
export default App;
아니면 isLogin으로 아래처럼 묶어볼 수 있습니다. 그런데 이전에 '불필요한 상태를 최대한 만들지 않는것'이 좋다고 했습니다. 그러면 useEffect안의 isLogin을 밖으로 뺄 수 있습니다.
function FlagState() {
const [isLogin, setIsLogin] = useState(false);
useEffect(()=>{
const isLogin=hasToken &&
hasCookie &&
isValidCookie &&
isNewUser==false &&
isValidToken;
if(isLogin){
setIsLogin(true);
}
},[hasToekn,hasCookie,isValidCookie,isNewUser,isValidToken]);
return (
<>
<div>{isLogin&&'안녕하세요! 반갑습니다!'}</div>
</>
);
}
그래서 최종적인 코드를 보면 토큰과 쿠키가 true인지 쿠키와토큰이 유효한값을 가지는지 그리고 새로운 유저가 아닐때 라는 조건이 지금 매 랜더링마다 다시계산이 되면서 isLogin에 들어가고 아래 return에서 불리언 값이 판단이 되기 때문에 굳이 상태가 필요하지 않다는 겁니다.
function FlagState() {
const isLogin =
hasToken &&
hasCookie &&
isValidCookie &&
isNewUser == false &&
isValidToken;
return (
<>
<div>{isLogin && "안녕하세요! 반갑습니다!"}</div>
</>
);
}
이런식으로 하면 굳이 상태를 만들고 그 상태를 위한 분기문을 칠 필요가 없습니다. 무슨 얘기냐면 로그인 대상에 무언가 값이 또 바뀌어서 추가적으로 요구사항이 생긴다면 정말 골치 아파집니다. 이럴때마다 if문이 늘어나고 switch케이스가 늘어나고 계속 조건들이 늘어나는데 이렇게 한번 플래그 변수를 컴포넌트 내부에서 만들고 이 조건만 잘 관리하면 굳이 큰 수정 없이 큰버전 변경없이 관리할 수가 있습니다.
요약
- usetState 대신 플래그로 상태를 정의할 수 있다.
불필요한 상태
완료한 일만 가지고 있는 유저(MOCK_DATA에 리스트객체의 속성 중 completed 값이 true인) 리스트를 만든다고 했을때 filter로 completed가 true인 녀석들만 넣어준다고 할 수 있습니다. 하지만 이렇게 하면 큰 낭비입니다.
const [userList,setUserList] = useState(MOCK_DATA); // 1. 초기상태선언
const [complUserList,setComplUserList]=useState(MOCK_DATA); // 2. 변경 후 저장할 상태 선언
useEffect(()=>{
const newList = complUserList.filter((user):boolean=>user.completed==true);
setUserList(newList);
},[userList]);
위의 코드에서 불필요한 상태를 제거 해보면 아래처럼 됩니다.
const complUserList = complUserList.filter((user):boolean=>user.completed===true);
렌더링마다 고유한 값을 가질 수 있다라고 가정을 하면 렌더링 될때마다 값이 바뀌고 관여되지만 이 값은 굳이 상태로 만들 필요가 없다는 겁니다. 결국엔 렌더링될때마다 고유의 'complUserList'가 생성이 되기 때문에 관리할 필요도 없고 set할 필요도 없어집니다.
그럼 React컴포넌트 내부에서 변수는 어떤 역할을 할까?라는 생각이 들 수 있습니다.
- 컴포넌트 내부에서의 변수 : 렌더링마다 고유한 값을 가지는 계산된 값을 가진다. 즉, Computed value 랜더링마다 고유한 값을 가지는 값
요약
- props를 useState에 넣지 않고 바로 return문에 사용하기
- 컴포넌트 내부 변수는 렌더링마다 고유한 값을 가짐
useState 대신 useRef
보통 useState랑 useRef를 직접적으로 비교하기보다 useRef는 필요에 따라 많이 사용합니다. 하지만 간혹 useState를 대신해서 useRef를 사용할 때가 있습니다. 기본적인 차이는 useState와 다르게 useRef는 가변 컨테이너 라고볼 수 있습니다. 즉 한번 고정된 값을 컴포넌트에서 계속해서 사용하는 값을 굳이 useState로 사용할 필요가 없습니다. 즉 '컴포넌트의 전체적인 수명과 동일하게 지속된 정보를 일관적으로 제공해야 하는 경우' useState로 만들 필요가 없습니다. 만약 useState로 사용할 경우에는 setState를 사용하면서 리렌더가 컴포넌트에 발생하기 때문에 좋지 않습니다.
export const component = () => {
const [isMount, setIsMount] = useState(false);
useEffect(() => {
if (!isMount) {
setIsMount(true);
}
}, [isMount]);
return <></>;
};
꼭 DOM과 관련할때만 useRef를 사용할 필요가 없습니다.
export const component = () => {
const isMount = useRef(false);
useEffect(()=>{
isMount.current=true;
return()=>(isMount.current=false);
},[]);
return <></>;
};
'프론트엔드 > react' 카테고리의 다른 글
01. 클린코드 리액트(React) - 클린코드, Vite (0) | 2024.03.20 |
---|---|
Billim 프로젝트 화면구성 (0) | 2023.12.09 |
왜 react-query ? (3) | 2023.12.03 |
React - Carousel설계 ( useRef ) (1) | 2023.11.26 |
React. 최근본상품 - localStorage에 저장 (1) | 2023.10.06 |