/**
* 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()
.
/**
* 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>