상태
관리
컴포넌트, 전역, 서버... 등등
이런 상태를 변경하고 최적화가 따라옴
그렇다면 우린
왜
상태관리를 할까? 로 돌아가보자
- 요구사항부터 생각하기
~상태는 왜 필요한지, 최적화는 언제하는지를 생각해보자.
- Ex. Array에 데이터가 있는지 확인하는 과정(Nullable 방어코드)
count [count, setCount] = useState('0');
count [list, setList] = useState();
{Array.isArray(list) && list.map((item)=> (<Item item = {item} />))}
우리가 초기 list 값을 잘 넣어놨다면 불필요한 과정
- Ex. Event 활용 이벤트는 String 으로 받아오고 이를 number로 사용하는 경우
초기값 :가장 먼저 렌더링될때 순간적으로 먼저 보여질 수 있는 값.
초기값을 생각없이 넣게되면
- 렌더링 이슈
- 무한루프
- 타입 불일치
- 런타임 에러
- Undefined(안넣는 경우)
- CRUD 시 원상태 복귀 불가
발생 가능
- Ex. 타입불일치
count [count, setCount] = useState('0');
const resetState = () => {
setCount(INIT_COUNT_STATE);
}
count의 초기값을 string 으로 설정해놓고, 함수에서 number로 활용하는 경우
초기값은 앱 시작~종료 전 과정에서 중요하며, 불필요한 방어코드를 줄여줄 수 있는 출발점.
- 변수를 리액트 컴포넌트 내부에 직접적으로 갖는 경우
export const Component = () => {
// 상수 혹은 일반적 방치
// 어디서도 업데이트 되지 않을 경우
const INFO = {
a: 'a',
b: 'b',
};
return <MyComp info={INFO} />
};
이 변수를 언제 참조하고 기억할지를 정하지 않음
- 해결법
- 리액트 상태로 변경
const [info, setInfo] = useState([a: 'a', b: 'b',])
- 아예 리액트 컴포넌트 외부로 추출
-
플래그 값 : 제어를 위한 조건을 boolean으로 나타내는 값
-
Case 1: useEffect를 한데 모으기
const [isLogin, setIsLogin] = useState(false);
useEffect(():void => {
if(
hasToken &&
hasCookie &&
//.. 생략
) {
setIsLogin(true);
}
})
- Case 2: 조건문 개선
const [isLogin, setIsLogin] = useState(false);
useEffect(():void => {
const isLogin =
hasToken &&
hasCookie &&
//.. 생략
if(isLogin) {
setIsLogin(true);
}
})
- Case3: 불필요한 상태 제거
const isLogin =
hasToken &&
hasCookie &&
//.. 생략
// 굳이 상태 없이도 로직 완성
return <div>{isLogin && "HI"}</div>;
즉 상태를 위한 useState, 분기문, 조건 입력 필요 없이 플래그로 정의 가능.
- 불필요한 상태 만들기
결국 리액트에 의해 관리되는 값이 증가
렌더링에 영향 주는 값도 증가 = 관리 포인트의 증가
- EX (useState 활용)
// 초기 상태 선언
const [userList, setUserList] = useStaet(MOCK_DATA);
// 변경할 상태 선언
const [compuserList, compsetUserList] = useStaet(MOCK_DATA);
useEffect(():void => {
const newList = compuserList.fliter((user):boolean => user.completed === true);
setUserList(newList);
}, [userList]);
- 내부의 변수를 만들어서 해결(const 활용)
렌더링될때마다 고유의 값이 만들어진다면, 내부 변수에 담아서 사용
const compuserList = compuserList.fliter((user):boolean => user.completed === true)
컴포넌트 내부 변수 = Computed Value(렌더링마다 고유의 값을 가지는 계산된 값)
리렌더링 방지가 필요할 경우
컴포넌트의 전체적 수명과 동일하게 지속적 정보를 일관적으로 제공하는 경우
- useState 활용
useState를 통해 만든 값들은 setState가 동작하며 원치않는 리렌더링이 발생가능
export const component() => {
const [isMount, setIsMount] = useState(false);
useEffect(() => {
if(isMount){
setIsMount(true)l
}
}, [isMount])
}
- useRef로 개선 (가변 컨테이너) 컴포넌트의 생명주기가 동일한 리렌더링 되지 않는 상태를 만들 수 있음
export const component() => {
const isMount = useRef(false);
useEffect(() => {
isMount.current = true;
return () => (isMount.current = false);
}, []);
};
-
KISS : Keep It Simple Stupid = 한번에 이해할 수 있게!
-
기존 loading, finish 상태가 다 연관된 상황
const [isLoading, setIsLoading] = useState(false);
const [isFinish, setIsFinish] = useState(false);
- 열거형 데이터로 변경 loding, finish, init을 한데 묶어서 열거형으로
cosnt PROMISE_STATE = {
INIT: "init",
LOADING: "loading",
FINISH: "finish"
};
const [promiseState, setPromiseState] = useState(PROMISE_STATE.INIT);
// 분기처리
if(PROMISE_STATE = PROMISE_STATE.INIT) return <InitComponent />
if(PROMISE_STATE = PROMISE_STATE.LOADING) return <LoadingComponent />
if(PROMISE_STATE = PROMISE_STATE.FINISH) return <FinishComponent />
연관된 상태는 묶어서 처리할 수 있는 방법이 있다. 객체가 아니라 간단히 문자열로 묶을 숟 도 있다.
- 기존
// 두가지 상태가 연관된 상황
const [isLoading, setIsLoading] = useState(false);
const [isFinish, setIsFinish] = useState(false);
- 연관상태를 객체로 묶어서 내보내기
하나의 State 로 변경 가능
const [fetchState, setFatchState] = useState({
isLoading: false,
isFinish: false
});
setFatchState({
isLoading: false,
isFinish: true
})
- 코드 반복까지 개선 이전 상태를 Props로 가져오기
setFatchState((prevState) =>{
...prevState, // 이전상태는 그대로
isFinish: true
})
한가지 상태를 조작하여 나머지 연관된 상태를 관리하는 개념
꼭 1:1 일 필요가 없고 N:1로 표현도 가능하다
하나의 상태가 다른 상태에 영향을 준다면 관련된 상태를 묶는 것도 좋은 방법
- 기존
const [isLoading, setIsLoading] = useState(false);
const [isFinish, setIsFinish] = useState(false);
- useReducer 활용
상태를 구조화 하는 관점(Redux 와 유사)
const INIT_STATE = {
isLoading: false,
isFinish: false
}
// if문도 활용 가능
const reducer = (state, action) => {
switch (action.type) {
case 'IS_LOADING':
return {...state, isLoading : true}
case 'IS_FINISH':
return {...state, isFinish : true}
default: // 기본값 반드시 넣기
return INIT_STATE
}
}
// useReducer 도 get,set 가능
const [state, dispatch] = useReducer(reducer, INIT_STATE);
// 활용
dispatch({type: 'IS_LOADING'});
순수한 JS 코드이기 때문에 hook, libray에 종속되지 않음.
추상적, 선언적 문법으로 바꾸고, 구조적으로 관리할 수도 있음.
화면에 렌더링 되는 부분을 제외하고, 로직만 빼보는 것이 중요하다.
- 기존 상태 로직
const [state, useState] = useState();
useEffect(() => {
const fetchData = () => {
setState(data);
};
fetchData();
},[]);
if(state.isLoading) return <LoadingComponent />;
if(state.isFail) return <FailComponent />;
- Custom Hooks 로 뽑기(use Prefix 지키기)
const [isLoading, ifFail] = useFetchData(data);
if(isLoading) return <LoadingComponent />;
if(ifFail) return <FailComponent />;
updater Function 을 사용하여 Previous State를 고려하기
function test() {
const [age, setAge] = useState(0)
function updateState(){
// 위 state를 참조하여 업데이트 -> 비동기적 처리과정에서 갱신 전 상태를 계속 볼 수도 있음
setAge(age + 1);
setAge(age + 1);
setAge(age + 1);
}
function updaterFuction(){
// 이렇게 이전상태를 가져와서 업데이트 진행해야함.
setAge((prevAge) => a + 1);
setAge((prevAge) => a + 1);
setAge((prevAge) => a + 1);
}
}
- 두번의 interaction, 같은 상태를 바라볼 경우 sideeffect 발생 가능
const handleCardNumber = (cardNumber) => {
setCardState({
...cardState,
cardNumber
})
}
const handleCardCompany = (cardCompany) => {
setCardState({
...cardState,
...cardCompany
})
}
const handleCardNumber = (cardNumber) => {
setCardState((prevState) =>{
...prevState,
cardNumber
});
}
const handleCardCompany = (cardCompany) => {
setCardState((prevState) => {
...prevState,
...cardCompany
});