createEntityAdapter로 normalized data 만들고 사용하기

🔗 참고 자료

Normalized Data 란?

참고: Normalizing State Shape

Reducer를 간단하게 만들거나, store 상에 중복된 데이터를 만들지 않기 위해서 데이터를 Normalizing 하는게 매우 중요하다.

간단하게 말하면, 아래와 같이 id들의 배열, key를 id, value는 data로 저장한다면 Normalized Data 라고 볼 수 있다. 조금 더 살펴보자.

{
  ids: ['project1', 'project2'], // 각 데이터(Record)들의 id 들의 배열
  entities: {
    project1: { name: '첫 프로젝트'},
    project2: { name : '두번째 프로젝트'}
  } // key는 id, value는 data로 한 entities
}

특징

  • 각 type의 data는 state 내에서 자신만의 table 이 있어야 한다.
  • 이 table에 데이터를 저장할 때는 항상 key가 id이고 value는 data인 형태로 저장한다.
  • 각 item의 reference는 항상 id여야 한다.
  • id들의 array는 정렬 순서를 나타내야 한다.

💡 한 Entity의 data가 여러 곳에서 사용되는 경우 Normalized Data 형태로 저장하는 게 이후에 중복된 데이터를 만들지 않을 수 있다.

배열 형태로 저장된 데이터의 경우 특정 item을 찾기 위해서는 매번 array의 loop을 돌아서 찾아야한다. 이는 수백, 수천개의 데이터가 있지 않는 이상 성능에 크게 영향을 주진 않지만 특정 item을 찾는 작업은 매우 빈번하게 일어나기도 하고 해당 데이터를 업데이트 하는 로직인 reducer도 간단하게 만들 수 있게 된다.

Normalized Data의 경우, 각 item들의 reference가 id이니 id를 안다면 item을 쉽게 찾을 수 있다.

Redux의 createEntityAdapter

createEntityAdapter 를 사용하면 data를 Normalized 한 형태로 저장할 수 있다. 이 Adapter는 고맙게도 기본 CRUD reducerselector를 제공해주는데 이를 이용한다면 데이터를 mutate 하는 경우에도 쉽게 Normalized 형태를 유지할 수 있다.

Initial Data 설정

initialState에 mock data 추가하기

const mockData = [
  {
    projectId: 'projectId1',
    name: 'First Project',
    numDocuments: 3,
  },
  {
    projectId: 'projectId1',
    name: 'First Project',
    numDocuments: 4,
  },
]

const emptyInitialState = projectAdapter.getInitialState()
const filledState = projectAdapter.upsertMany(emptyInitialState, mockData)

const projectSlice = createSlice({
  name: 'projects',
  initialState: filledState,
  reducers: {},
})

add, remove 후에도 정렬된 상태 유지하려면?

sortComparer 사용하기

const booksAdapter = createEntityAdapter({
  // allIDs array가 항상 title을 기준으로 정렬되도록 한다.
  sortComparer: (a, b) => a.title.localeCompare(b.title),
})

두 Entity를 받아서 Array.sort 사용시 기준 값이 되는 -1, 0, 1 을 리턴한다.

  • 만약 이 함수가 주어진다면 entity objects 값을 항상 이 기준대로 정렬해서 state.ids 배열을 정렬 상태로 유지한다.
  • sortComparer를 설정해놓지 않으면 state.ids는 정렬 상태를 유지 하지 않는다.

여러 데이터 추가하기

createEntityAdapter를 사용하여 하나의 Entity를 { ids: [], entities: {}} 형태로 store에 저장하는 경우, createEntityAdapter가 제공하는 CRUD function으로 쉽게 이 상태를 유지해서 데이터를 추가, 삭제, 업데이트 할 수 있다.

// entities 에 저장되는 데이터 형태
const record = Record<EntityId, T> {
  id,
  name,
  numDocuments
  // 등
}
  • setAll 는 entity content 안에 있는 모든 데이터들을 없애고 payload로 들어온 데이터로 replace 시킨다.

    projectsReceived(state, action){
      projectAdapter.setAll(state, action.payload)
    }
    
    // 컴포넌트에서는
    
    const getProjects = (projects) => {
      const allProjects = await service.getProjects() // 위의 mockData 형태와 동일
      dispatch(projectsReceived(allProjects))
    }

Written by@Seokyung
가끔 개발 일지를 씁니다. 그리고..

GitHub