상태 관리와 Context API
💡 상태 관리의 필요성
props drilling 방지
상태 관리 라이브러리를 사용함으로써 props drilling을 방지할 수 있다.
props drilling 은 컴포넌트가 특정값을 사용하기 위해 여러 상위 컴포넌트에서 props를 정의하고 전달받는 과정을 말한다. 이는 여러 컴포넌트에서 사용하지도 않는 props를 정의해야하고 필요없는 코드를 증가시킬 수 있다.
때문에 이러한 필요없는 코드를 줄이고 해당 데이터를 사용하는 컴포넌트에서만 끌어와서 쓸 수 있도록 할 수 있는 역할이 상태 관리 라이브러리이다. 상태 관리 라이브러리에서는 store라는 state 저장소에서 사용하고 싶은 state만 끌어와 사용할 수 있기 때문에 props drilling을 피할 수 있다.
💡 Context
언제 context를 써야 할까?
context는 React 컴포넌트 트리 안에서 **전역적(global)**이라고 볼 수 있는 데이터를 공유할 때 사용한다. 데이터의 예시로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있다.
React.createContext로 context 생성하기
Context 객체를 만든다. Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재값을 읽는다.
const Context = React.createContext(values);
Context.Provider
Context 오브젝트에 포함된 React 컴포넌트인 Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다. Provider 컴포넌트는 value prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다. Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop가 바뀔 때마다 다시 렌더링 된다.
function Provider({ children }) {
return (
<Context.Provider value={values}>
{children}
</Context.Provider>
);
}
Context.Consumer
context 변화를 구독하는 React 컴포넌트이다. 이 컴포넌트를 사용하면 함수 컴포넌트안에서 context를 구독할 수 있다.
Context.Consumer의 자식은 함수여야한다. 이 함수는 context의 현재값을 받고 React 노드를 반환한다. 이 함수가 받는 value 매개변수 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Provider의 value prop과 동일하다.
<Context.Consumer>
{value => /* context 값을 이용한 렌더링 */}
</Context.Consumer>
🎁 Context API를 사용해서 페이크 로그인 구현하기
💡 AuthContext
AuthContext 에서 관리하고 싶은 데이터 정의 후 createContext로 객체 생성.
사용자 이름을 MyAccountPage에서도 꺼내써야하기 때문에 username까지 넣어줬다.
interface AuthContextType {
authToken: string | null; //토큰
isAuthenticated: boolean; //인증 유무
loading: boolean; // 로딩 상태
login: (id: string) => void; //로그인
logout: () => void; //로그아웃
username: string | null; //사용자이름
}
export const AuthContext = createContext<AuthContextType>({
authToken: null,
isAuthenticated: false,
loading: true,
login: () => {},
logout: () => {},
username: null,
});
loading은 새로고침시 인증 정보가 초기화되는 문제를 해결하기 위해 정의했다.
세션 스토리지에 authToken이 있으면 state들을 set해준다.
useEffect(() => {
const token = sessionStorage.getItem('authToken');
if (token) {
setAuthToken(token);
setIsAuthenticated(true);
setUsername(token);
}
setLoading(false);
}, []);
페이크 로그인, 로그아웃도 setItem과 removeItem으로 간단히 구현했다. loginForm에서 id값을 props로 받아와 설정해주도록 했다.
const login = (id: string) => {
sessionStorage.setItem('authToken', id);
setAuthToken(id);
setIsAuthenticated(true);
setUsername(id);
navigate(-1);
};
const logout = () => {
sessionStorage.removeItem('authToken');
setIsAuthenticated(false);
navigate('/');
};
이제 Provider를 생성하자.
<AuthContext.Provider value={{ authToken, isAuthenticated, login, logout, username, loading }}>
{children}
</AuthContext.Provider>
💡 로그인이 된 사용자에게만 특정 페이지에 대한 접근을 부여하려면 어떻게 해야할까?
AuthRoute를 사용하는 방법
AuthRoute 컴포넌트를 MyAccoutPage의 가장 상위에 감싸주어 로그인된 사용자의 경우에만 접근이 가능하도록 로직을 설정했다.
const MyAccountPage = () => {
const { username, logout } = useAuthContext();
return (
<AuthRoute>
...
</AuthRoute>
);
};
사용자 인증 여부(로그인 여부)는 앞서 작성한 AuthContext에서 isAuthenticated 값을 가져와주기만 하면 된다.
const { isAuthenticated, loading } = useAuthContext();
if (loading) {
return;
}
if (!isAuthenticated) {
navigate('/login');
}
AuthRoute 작성법에 대한 고민
- Navigate를 사용하는 방법
export const AuthRoute: React.FC<Props> = ({ children }) => {
const { isAuthenticated, loading } = useAuthContext();
if (loading) {
return null;
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
};
- useEffect와 useNavigate 훅을 사용하는 방법
export const AuthRoute: React.FC<Props> = ({ children }) => {
const { isAuthenticated, loading } = useAuthContext();
const navigate = useNavigate();
useEffect(() => {
if (loading) {
return;
}
if (!isAuthenticated) {
navigate('/login');
}
}, [isAuthenticated, navigate, loading]);
return <>{children}</>;
};
React.FC 사용을 지양하자
💡 React.FC 사용을 지양해야 하는 이유
- React.FC 사용시 props는 children을 암시적으로 가지게 된다.
- 타입스크립트의 제네릭 문법을 지원하지 않는다.
코드 수정 진행함.
React SPA 프로젝트에서 React Router의 역할
💡 리액트 라우터(React Router)는 리액트 기반의 애플리케이션에서 라우팅을 구현하기 위한 라이브러리이다. SPA에서는 페이지 이동 없이 URL을 변경하고, 해당 URL에 맞는 컨텐츠를 동적으로 렌더링해야 한다.
만약 SPA에서 React Router가 존재하지 않는다면, 몇가지 문제가 발생한다.
- React Router는 내부적으로 브라우저의 History API를 사용하여 URL 변경을 관리하고, 해당 경로에 맞는 컴포넌트를 렌더링해준다. 이를 사용하지 않으면 사용자가 페이지 전환을 했을 때 상태가 올바르게 유지되지 않을 수 있다**.** 따라서 페이지 전환시 수동으로 상태를 관리해야 하고, 각 페이지의 렌더링 조건을 직접 구현해야 하므로 코드가 복잡해지며 오류 발생 가능성이 높아진다.
- SEO(검색 엔진 최적화) 문제 React Router를 사용하여 서버 사이드 렌더링을 구현함으로써, SPA의 SEO 문제를 개선할 수 있다. 하지만 React Router 없이 SPA를 구성할 경우, 검색 엔진이 페이지를 제대로 인덱싱하지 못할 수 있다.
Local Storage, Session Storage, Cookie
💡 Local Storage, Session Storage 와 Cookies의 차이
- Local Storage(영구 저장소): key-value 쌍으로 데이터를 저장하는 웹 스토리지. 브라우저를 종료한 후 다시 들어와도 저장 기록이 남아있다.
- Session Storage(임시 저장소): local storage와 다르게 브라우저를 종료할 때 브라우저에 저장된 데이터가 삭제된다. 즉, local storage와 session storage 의 차이점은 데이터가 어떤 범위 내에서 얼마나 오래 보존되는지에 있다.
- Cookie: 페이지에서 쿠키를 설정하면 이후 모든 웹 요청은 쿠키정보를 포함하여 서버로 전송된다는 점이다. 쿠키를 통해서 서버와 클라이언트가 지속적으로 데이터 교환을 하게 된다. 쿠키와 웹스토리지의 가장 큰 차이점이기도 하다.
참고
'FE > 코딩일기' 카테고리의 다른 글
🌳7월9일 TIL🎶 (0) | 2024.07.09 |
---|---|
🌳7월8일 TIL🎶 (0) | 2024.07.09 |
🌳7월4일 TIL🎶 (0) | 2024.07.05 |
🌳7월3일 TIL🎶 (0) | 2024.07.03 |
🌳7월2일 TIL🎶 (0) | 2024.07.03 |