다음은 Redux 내에 기입된 state 관련 타입들이다.

/**
 * Internal "virtual" symbol used to make the `CombinedState` type unique.
 */
declare const $CombinedState: unique symbol

/**
 * State base type for reducers created with `combineReducers()`.
 *
 * This type allows the `createStore()` method to infer which levels of the
 * preloaded state can be partial.
 *
 * Because Typescript is really duck-typed, a type needs to have some
 * identifying property to differentiate it from other types with matching
 * prototypes for type checking purposes. That's why this type has the
 * `$CombinedState` symbol property. Without the property, this type would
 * match any object. The symbol doesn't really exist because it's an internal
 * (i.e. not exported), and internally we never check its value. Since it's a
 * symbol property, it's not expected to be unumerable, and the value is
 * typed as always undefined, so its never expected to have a meaningful
 * value anyway. It just makes this type distinquishable from plain `{}`.
 */
interface EmptyObject {
  readonly [$CombinedState]?: undefined
}
export type CombinedState<S> = EmptyObject & S

/**
 * Recursively makes combined state objects partial. Only combined state _root
 * objects_ (i.e. the generated higher level object with keys mapping to
 * individual reducers) are partial.
 */
export type PreloadedState<S> = Required<S> extends EmptyObject
  ? S extends CombinedState<infer S1>
    ? {
        [K in keyof S1]?: S1[K] extends object ? PreloadedState<S1[K]> : S1[K]
      }
    : S
  : {
      [K in keyof S]: S[K] extends string | number | boolean | symbol
        ? S[K]
        : PreloadedState<S[K]>
    }

The provided TypeScript code declares a special EmptyObject interface that has a unique symbol property $CombinedState which is used to make the CombinedState type unique. This unique symbol is of type unique symbol and is used to create a unique identifier.

The use of the $CombinedState symbol in the EmptyObject interface allows TypeScript to differentiate EmptyObject from any other object, even though its structure may be very similar. This becomes especially important when checking types in TypeScript, which operates with a structural type system (or "duck typing") - if an object has the same structure, it's of the same type.

By using a unique symbol, the EmptyObject interface becomes distinguishable from just any object {}. As the comment states, the symbol doesn't exist internally and the system never checks its value, but the property is still required for TypeScript to differentiate the EmptyObject type from other object types.

The CombinedState type then takes this EmptyObject and combines it with a generic S type. The intersection & means that a CombinedState<S> is both an EmptyObject (having the unique $CombinedState property) and the structure of S.

So the purpose of the CombinedState type is to allow type-safe use of combineReducers() (from Redux), making sure that the combined state is unique and doesn't conflict with other object types. This CombinedState type is used in Redux for defining the root state type in a store that is created using combineReducers().

Duck typing..?


사실 내가 가장 이해하기 어려웠던 코드는 다음과 같다.

/**
 * Recursively makes combined state objects partial. Only combined state _root
 * objects_ (i.e. the generated higher level object with keys mapping to
 * individual reducers) are partial.
 */
export type PreloadedState<S> = Required<S> extends EmptyObject
  ? S extends CombinedState<infer S1>
    ? {
        [K in keyof S1]?: S1[K] extends object ? PreloadedState<S1[K]> : S1[K]
      }
    : S
  : {
      [K in keyof S]: S[K] extends string | number | boolean | symbol
        ? S[K]
        : PreloadedState<S[K]>
    }

GPT를 통해 물어본 질문은 다음과 같다.

위 코드에서 Required<S>EmptyObject를 상속하지 않는 경우의 예시를 들어 코드 전체를 이해하게 쉽게 설명해줘.

답변은 다음과 같았다.

<aside> 🐚 이 코드는 Redux에 사용되는 PreloadedState 타입을 정의합니다. 이 타입은 Redux 앱의 초기 상태를 묘사하며, 주로 서버 렌더링이나 상태 복원을 위해 사용됩니다. 이 타입은 앱의 상태를 재귀적으로 분석하고, Redux의 combineReducers로 생성된 각 서브트리(각각의 리듀서에 대응하는 상태 부분)에 대해 선택적 상태(Partial)를 허용합니다.

</aside>