[Redux Toolkit] type 'string' can't be used to index type 'WritableDraft'

aborile·2023년 9월 8일
0

삽질기

목록 보기
7/9
post-thumbnail
import { createSlice } from "@reduxjs/toolkit";

interface MyReduxState {
  text: string;
  count: number;
}
const initialState: MyReduxState = {
  text: "initial text",
  count: 1,
};

const mySlice = createSlice({
  name: "my-slice",
  initialState,
  reducers: {
    resetAllState: (state) => {
      state.text = initialState.text;
      state.count = initialState.count;
    },
  },
});

redux-toolkit을 사용하여 전역 state를 관리하고 있었는데, 특정 state를 일괄 초기화하는 함수를 추가해야할 일이 생겼다. 위에서 짠 코드처럼 그냥 state key 별로 일일이 재할당을 해주면 간단하게 끝낼 수 있었지만... 실제로는 해당 slice 내에서 관리하고 있는 state가 19개가 되는 바람에 일일이 재할당하자니 코드가 너무 길어질 것 같았고, 개발 당시 state가 계속 늘어나거나 변경(이름이나 용도 등이)되고 있어서 개발 도중에 누락시키는 일이 발생할 것 같았다.

그래서 생각한 수정 방향은 Object keys/entries 등의 함수를 활용하여 key를 추출한 뒤 loop을 돌아서 재할당하는 것이었다.

Object.entries(initialState).forEach(([key, initialValue]) => {
  state[key] = initialValue;
});

간단하네^^ 하고 마무리하려는 찰나... 타입 에러가 발생했다.

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'WritableDraft<MyReduxState>'.

No index signature with a parameter of type 'string' was found on type 'WritableDraft<MyReduxState>'. ts(7053)

OMG...

혹시나 싶어

Object.keys(state).forEach((key) => {
  state[key] = initialState[key];
});

위와 같이 state를 기준으로 생성해도 어차피 Object로 생성하면 key가 string으로 추출되기 때문에 똑같은 에러가 그대로 발생한다.

object key를 만들고 변수로 다룰 때 늘 하던 대로 keyof를 활용해서 타이핑을 해봐도, 애초에 일반적인 object가 아닌 redux 내의 WritableDraft라는 형태로 생성된 거라 역시 먹히지 않는 듯하다. (이 경우 다른 에러가 발생한다.)

Object.entries(initialState).forEach(([key, initialValue]) => {
  state[key as keyof MyReduxState] = initialValue;
  // or
  state[key as keyof typeof initialState] = initialValue;
});

Type 'any' is not assignable to type 'never'

사실 타입 에러일 뿐이라 이대로 두어도 코드는 의도대로 돌지만 아무리 이리저리 고민하고 수정해 봐도 도무지 타입 에러가 사라지지 않아서 정말 많은 글을 찾아 헤맸다.

인덱스 시그니처

가장 처음 찾은 방법은 인덱스 시그니처를 추가해 주는 방안이다.

interface MyReduxState {
  text: string;
  count: number;
  // 인덱스 시그니처
  [key: string]: any;
}

key가 string 타입임을 명시해 주어서 타입 체크를 하는 방식인데, any를 사용해야 한다는 점이 마음에 들지 않아 다른 방법을 찾기로 했다.
(위 예시에서는 그냥 string | number로만 해주면 되긴 하지만, 실제로 사용하고 있는 state은 여러 타입과 인터페이스를 사용하고 있어서 모든 타입을 나열하기 현실적으로 어렵기도 하고, 타입 에러 때문에 불필요한 인덱스 시그니처를 넣어야 하는 것이 오히려 더 비효율적인 것 같다.)

Object.assign

수소문하던 끝에 찾아낸 또 다른 방법은 바로 Object.assign을 사용하는 방식이다.

Object.entries(initialState).forEach(([key, initialValue]) => {
  Object.assign(state, { [key]: initialValue });
});

생각보다 훨씬 코드가 깔끔하고, 간단하고, 또 원하는 대로 풀려서 오히려 맥이 빠졌다. 혹시 redux 원칙에 어긋나는 할당 방식인 건 아닌가 싶어 대충 찾아봤는데 다행히 redux toolkit 공식 문서에서도 언급되고 있는 패턴 중 하나였다.

References

본문에서 해결방안으로 직접 참고한 글

읽고 시도해 봤지만 도움은 되지 않은 글

profile
기록하고 싶은 것을 기록하는 저장소

0개의 댓글