typescript + toolkit/thunk) 여러 개의 Code Editor가 개별적으로 작동하게 만들기

김명성·2022년 7월 21일
0

여러개의 CodeEditor들이 개별적으로 동작하게 만드는 어플리케이션을 구현했다.

각 Editor들이 개별적으로 동작하게 만들기 위해서는 식별할 수 있는 각각의 id입력이 필수였기 때문에, 어떻게 동적으로 키를 입력받아야 하는지 로직을 구현하는 부분이 어려웠다.

먼저 id를 받기 위해서는 타입 지정에 [key:string]을 필수로 입력해야겠다고 생각하고 코드를 작성했다.


Property is not assignable to 'string' index type

이번 오류는 interface 속성에 잘못된 index signiture가 할당되었을 때 발생했다.

예를들어, dispatch의 결과에 따라서 동적인 값을 받는 프로퍼티와 정적인 값을 받는 프로퍼티를 동시에 사용하려 할 때 발생하는 Error다.

잘못되었던 예시

interface BundleState {
    [key:string]: {
      code: string;
      err: string;
    }
	loading: boolean;
}
const initialState: BundleState = {
	loading:boolean
  };
// Type 'boolean' is not assignable to type '{ code: string; err: string; }'.

loading또한 [key:string]:{code:string, err:string}의 형식에 맞게 사용하라는 Error같았다.

수정

interface BundleState {
    [key:string]: {
      loading: boolean;
      code: string;
      err: string;
    }
}


const initialState: BundleState = {};

생각해보니, loading을 따로 바깥으로 뺼 필요 없이, 각각의 Code Editor의 로딩 상황을 의미할 수 있게 동적 프로퍼티 내부에 입력하는게 더 알맞는 것 같아 수정하였다.

이제 action creator를 받아, 각 CodeEditor의 입력된 data를 뿌려줘야 하는데, extraReducer에서의 action을 payload로 받는 것이 불가능하고, meta로 받아서 처리할 수 있었다.

먼저 createAsyncThunk를 작성하고,

export const asyncBundleThunk = createAsyncThunk<
  any,
  BundleCompleteAction,
  { rejectValue: Error }
>('bundle/start', async (action: BundleCompleteAction) => {
  const { id } = action;
  const result = await codeProcessor(action.code);
  const data = {
      id,
      code: result.code,
      err: result.err,
  };

  return data;
});

extraReducer를 작성했다.

extraReducers: (builder) => {
    builder.addCase(asyncBundleThunk.pending, (state, action):BundleState => {
      state[action.meta.arg.id] = {
        loading: true,
        code: '',
        err: ''
      }
      return state
    });

    builder.addCase(asyncBundleThunk.fulfilled, (state, action:PayloadAction<BundleCompleteAction>):BundleState => {
      
    state[action.payload.id] = {
      loading:false,
      code: action.payload.code,
      err: action.payload.err
    }
      return state
  });

    builder.addCase(asyncBundleThunk.rejected, (state, action):BundleState => {
      
      if(action.error.message){
        state[action.meta.arg.id] = {
          loading:false,
          code: '',
          err: action.error.message
        }
      }else{
        state[action.meta.arg.id] = {
          loading:false,
          code: '',
          err: 'unkwown error occured'
        }
      }
      
      return state
    });
  },

위 코드에서 state[action.meta.arg.id]는 BundleState의 [key:string]을 의미한다.

action.meta.arg.id를 통해서 각 id에 맞는 loading,code,err 프로퍼티를 가질 수 있게 되는 것이다.

rejected 처리에서 error.messagestring | undefined이다.
BundleStateerr의 속성은 string만 받을 수 있기 때문에 error.message의 존재 여부에 따라 err에 각기 다른 string 타입이 입력 될 수 있게 narrowing 하였다.

0개의 댓글