반응형
Redux를 사용하고 싶지 않을 때?!
- 리액트만 존재하는 세상에서 살고 싶을 때
- 부가 라이브러리를 설치하기 싫을 때
- 리덕스를 사용하지 않고 전역 state를 관리하고 싶을 때
해결 방안 1 ) context API
장점: 사용하기 간단한 편. 리덕스를 몰라도 됨.
단점: 고빈도 업데이트에는 효율적이지 않음(테마나 인증에는 적합). context에 변경 사항이 있을 시 useContext를 사용하는 모든 컴포넌트가 다시 빌드되고 리렌더링 됨(직접적으로 영향받았는지 상관 안 하고) -> 자주 바뀌는 state의 관리에 최적화되어있지 않음.
https://bbeeyaks-moment.tistory.com/entry/react-context-API-useContext
해결 방안 2 ) custom hook store
리덕스 같은 custom store를 만든다.
1. hooks-store 폴더 만든 후 store.js 파일 생성
import { useState, useEffect } from 'react';
// 전역적으로 선언되어 커스텀 훅을 사용하는 파일이 모두 같은 데이터 사용할 수 있도록 함
// -> 로직 + 데이터를 공유
let globalState = {};// 전역적으로 관리되는 state
// state의 변경사항을 듣고 따라가는 함수 모음
// (호출 했을 때 훅을 사용하는 모든 컴포넌트 global state에 따라 리렌더링 될 수 있도록)
let listeners = [];
let actions = {}; // 액션들 모음 객체
// 커스텀 훅을 사용하는 컴포넌트 모두 리렌더링 될 수 있도록 useState 사용
export const useStore = () => {
// [1] => useState가 반환하는 두번째 값(setState)에만 관심이 있다는 의미
const setState = useState(globalState)[1];
const dispatch = (actionIdentifier, payload) => {
const newState = actions[actionIdentifier](globalState, payload);
// state merge
globalState = { ...globalState, ...newState };
// state update
for (const listener of listeners) {
// setState 호출
listener(globalState);
}
};
// 훅을 사용하는 컴포넌트가 업데이트 될 때마다 하단 로직 실행됨
useEffect(() => {
// 커스텀 훅을 사용하는 모든 컴포넌트가 각각의 setState를 가짐
listeners.push(setState);
// clean-up 함수: listeners 제거(un-mount되면 listeners를 비워줌)
return () => {
listeners = listeners.filter(li => li !== setState);
};
}, [setState]);
return [globalState, dispatch];
};
// store 초기화 => 구체적인 store의 slice를 만들어주기 위해
// (리덕스에서 여러 개의 reducers로 했던 것 처럼)
export const initStore = (userActions, initialState) => {
if (initialState) {
globalState = { ...globalState, ...initialState };
}
actions = { ...actions, ...userActions };
};
2. 콘크리트 store 만들기
//hooks-store/products-store.js
import { initStore } from './store';
const configureStore = () => {
// 액션 설정
const actions = {
TOGGLE_FAV: (curState, productId) => {
const prodIndex = curState.products.findIndex(p => p.id === productId);
const newFavStatus = !curState.products[prodIndex].isFavorite;
const updatedProducts = [...curState.products];
updatedProducts[prodIndex] = {
...curState.products[prodIndex],
isFavorite: newFavStatus
};
return { products: updatedProducts };
}
};
// 이 globalState의 슬라이스에 actions와 초기 state 전달
initStore(actions, {
products: [
{
id: 'p1',
title: 'Red Scarf',
description: 'A pretty red scarf.',
isFavorite: false
},
{
id: 'p2',
title: 'Blue T-Shirt',
description: 'A pretty blue t-shirt.',
isFavorite: false
},
{
id: 'p3',
title: 'Green Trousers',
description: 'A pair of lightly green trousers.',
isFavorite: false
},
{
id: 'p4',
title: 'Orange Hat',
description: 'Street style! An orange hat.',
isFavorite: false
}
]
});
};
export default configureStore;
3. custom store 사용하기
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import configureProductsStore from './hooks-store/products-store';
// store 초기화
configureProductsStore();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// containers/Products.js
import React, { useContext } from 'react';
import ProductItem from '../components/Products/ProductItem';
import { useStore } from '../hooks-store/store';
import './Products.css';
const Products = props => {
// global store data에 접근
const state = useStore()[0];
return (
<ul className="products-list">
{state.products.map(prod => (
<ProductItem
key={prod.id}
id={prod.id}
title={prod.title}
description={prod.description}
isFav={prod.isFavorite}
/>
))}
</ul>
);
};
export default Products;
// ProductItem.js
import React from 'react';
import Card from '../UI/Card';
import { useStore } from '../../hooks-store/store';
import './ProductItem.css';
const ProductItem = props => {
const dispatch = useStore()[1];
const toggleFavHandler = () => {
dispatch('TOGGLE_FAV', props.id);
};
return (
<Card style={{ marginBottom: '1rem' }}>
<div className="product-item">
<h2 className={props.isFav ? 'is-fav' : ''}>{props.title}</h2>
<p>{props.description}</p>
<button
className={!props.isFav ? 'button-outline' : ''}
onClick={toggleFavHandler}
>
{props.isFav ? 'Un-Favorite' : 'Favorite'}
</button>
</div>
</Card>
);
};
export default ProductItem;
4. custom hook store 최적화하기
import { useState, useEffect } from 'react';
let globalState = {};
let listeners = [];
let actions = {};
// 액션을 dispatch하려고 custom hook이 사용되어도(store의 변화에 관심이 없어도) 리렌더링되기 때문에
// 불필요한 리렌더링 방지를 위해 변수 하나 추가하여 최적화해줌
export const useStore = (shouldListen = true) => {
const setState = useState(globalState)[1];
const dispatch = (actionIdentifier, payload) => {
const newState = actions[actionIdentifier](globalState, payload);
globalState = { ...globalState, ...newState };
for (const listener of listeners) {
listener(globalState);
}
};
useEffect(() => {
if (shouldListen) {
listeners.push(setState);
}
return () => {
if (shouldListen) {
listeners = listeners.filter(li => li !== setState);
}
};
}, [setState, shouldListen]);
return [globalState, dispatch];
};
export const initStore = (userActions, initialState) => {
if (initialState) {
globalState = { ...globalState, ...initialState };
}
actions = { ...actions, ...userActions };
};
//ProductItem.js
import React from 'react';
import Card from '../UI/Card';
import { useStore } from '../../hooks-store/store';
import './ProductItem.css';
const ProductItem = React.memo(props => {
console.log('RENDERING');
const dispatch = useStore(false)[1];
const toggleFavHandler = () => {
// toggleFav(props.id);
dispatch('TOGGLE_FAV', props.id);
};
return (
<Card style={{ marginBottom: '1rem' }}>
<div className="product-item">
<h2 className={props.isFav ? 'is-fav' : ''}>{props.title}</h2>
<p>{props.description}</p>
<button
className={!props.isFav ? 'button-outline' : ''}
onClick={toggleFavHandler}
>
{props.isFav ? 'Un-Favorite' : 'Favorite'}
</button>
</div>
</Card>
);
});
export default ProductItem;
오늘은 리덕스를 사용하고 싶지 않을 때 전역 데이터를 관리하는 해결 방안(context API, custom hook store)에 대해 배워보았다.
custom hook store를 사용하는 방법은 useAsync처럼 redux의 store 개념을 추상화 해준다는 개념인 것 같다.. 직접 사용해 본 것이 아니라 개념만 이해되는 수준이지만..! 상태관리 라이브러리를 더 많이 사용할 것 같긴 하지만, 개념은 잘 알아두고 있어야겠다. 성능을 최적화해 준다고도 하니! :)
반응형
'온라인 강의(유데미, 인프런 등) > React 완벽 가이드(유데미)' 카테고리의 다른 글
[test] react 테스트 간단 개념 정리 (0) | 2023.03.11 |
---|---|
[vercel] vercel로 client 배포하기 (0) | 2023.03.10 |
[NextJS] NextJS 개념 정리 (0) | 2023.03.10 |
[typescript] tsconfig.json (0) | 2023.03.09 |
[typescript] typescript 개념 정리(2) (0) | 2023.03.09 |