웹 페이지의 최적화에대해서 알아보는 중
redux
를 사용할 때 업데이트 되지 않아도 object 내에 다른 정보가 수정되면 지속해서 함수가 재실행되어 리렌더링이 되게 만들어지는 것을 방지하기 위해 reselect를 사용하는 방법이 있다는 것을 확인하였다. 그래서 이번 프로젝트간 사용 하려고 했던redux-toolkit
에서는 어떻게 적용을 시켜야하나 알아보니 이미redux-toolkit
에서는 createSeelector를 통해 리렌더링을 방지할 수 있었다. 이처럼 여러 기능과 라이브러리들을redux-toolkit
을 통해 사용할 수 있었는데, 어떤 부분들이 있는지 구체적으로 알아보자
redux-toolkit 공식사이트를 참고하면 redux-toolkit
이 만들어진 이유가 제일 상단에 나와있다.
redux
스토어 구성의 복잡성redux
를 사용할 때 많은 패키지 추가의 필요성redux
사용시 많은 상용 코드 필요간단하게 이야기하면 지속해서 제시되었던 redux
가 대규모 프로젝트들에서 편리하기는 하지만, 배보다 배꼽이 더 크다할 정도로 redux
를 사용하게되면 코드 작성양이 늘어나고 필요로하는 라이브러리들이 굉장히 많다는 단점을 보완하기 위해서 출시되었다고 할 수 있다.
const increment = createAction("INCREMENT");
const decrement = createAction("DECREMENT");
function counter(state = 0, action) {
switch (action.type) {
case increment.type:
return state + 1;
case decrement.type:
return state - 1;
default:
return state;
}
}
const store = configureStore({
reducer: counter
});
document.getElementById("increment").addEventListener("click", () => {
store.dispatch(increment());
});
위와 같이 createAction
을 통해 액션 타입을 정의할 수 있지만 createSlice
를 사용하면 createAction
의 사용없이 자동으로 action 타입을 정의해 준다.
interface CartState {
products?: Product[];
}
const initialState: CartState = {
products: [],
};
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
initializeCart: () => {},
addCart: () => {},
removeCart: () => {},
},
});
export const { initializeCart, addCart, removeCart } = cartSlice.actions;
export default cartSlice.reducer;
redux
는 store에서 값을 가져와 객체의 값 중 일부만 가져올 때, 리렌더링을 일으키지 않도록 도와준다.
필요로하는 state를 첫번째 인자로 넣고, 그 state에서 어떤 값을 필요로 하는지 필터링 해줄 콜백함수를 넣어준다.
기존에는 reference만을 비교하여, 값이 바뀌지 않고 참조하는 object의 reference 값만 바뀌어도 함수가 다시 실행되고, 리렌더링이 되었지만 createSelector
를 통해 값을 가져올 시에는 그 값이 캐싱되어 그 값이 바뀌었는 지 여부를 확인하게 되어 최적화를 시킬 수 있다.
const productsState = (state: RootState) => state.products.products;
export const getElectronicsProducts = createSelector(
productsState,
(products) => {
return products.filter((product) =>
product.category.==='electronics'
);
}
);
redux
에서도 react에서와 마찬가지로 reference 값 비교(얕은 비교)를 통해 상태를 확인한다. 그래서redux
에서는 상태를 직접 변경해서는 안되고 상태를 직접변경하게되면 아래와 같은 문제점들을 야기한다.
redux-toolkit
에서 createReducer API는 내부적으로 자동으로 immer
를 사용한다. 그래서 아래와 같이 직접 값을 push하는 것도 가능하다.
const todosReducer = createReducer([], (builder) => {
builder.addCase('todos/todoAdded', (state, action) => {
// "mutate" the array by calling push()
state.push(action.payload)
})
})
이때 우리는 redux-toolkit
을 사용하게 되면 createReducer
를 직접 쓸 상황은 없을 텐데 createSlice
내의 reducers 안에서 createReducer
를 내부적으로 사용하기 때문이다.
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
initializeCart: () => {},
addCart: () => {},
removeCart: () => {},
},
});
immer
에서는 기존 draft 상태 값을 변경하려는 시도를 추적하지만, 기존상태를 변경했음에도 불구하고 상 상태값을 직접 반환하면 오류가 발생한다.const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
// ❌ ERROR: mutates state, but also returns new array size!
brokenReducer: (state, action) => state.push(action.payload),
// ✅ SAFE: the `void` keyword prevents a return value
fixedReducer1: (state, action) => void state.push(action.payload),
// ✅ SAFE: curly braces make this a function body and no return
fixedReducer2: (state, action) => {
state.push(action.payload)
},
},
})
일반적인 실수는 state = someValue를 직접 할당하는 경우 지역 상태 변수는 다른 참조로 가리킨다. 이는 메모리의 기존 상태 개체/배열을 변경하거나 완전히 새로운 값을 반환하지 않으므로 Immer
는 실제 변경을 수행하지 않는다.
Flux 디자인 패턴을 사용하지 않으면 무조건 적으로 에러를 띄우며 대표적인 예로 값은 action.payload를 통해서만 접근해야한다.
export interface Action<Payload> extends AnyAction {
type: string;
payload: Payload;
error?: boolean;
meta?: Meta;
}
아래와 같이 type에 관한 정의 들을 내장 타입으로 지원하여 type에 대해 신경써야하는 번거로움이 사라졌다.
export const store = configureStore({
reducer: {
user: userSlice,
products: productSlice,
cart: cartSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
추가적으로 redux-thunk
를 따로 설치해서 사용했던것을 공식적으로 지원한다.createAsyncThunk
를 이용해서 그 전의 thunk와 같이 사용할 수 있다. 다른 비동기 라이브러리들이 존재하지만 thunk를 공식적으로 지원하게 된 이유는 공식 홈페이지에서 자세하게 찾아볼 수 있었다.
redux-toolkit
redux-toolkit을 소개합니다.
redux-toolkit을 사용하여 간단하게 상태관리하기
Redux Toolkit: Immer와 함께 사용하기
js참조에 대한 시각적 가이드
introduction to immer