데이터를 정규화하기 위해 유형이 안전한 감속기를 작성하십시오.
17860 단어 reacttypescriptfunctional
배경
중첩된 객체가 있는 다음 JSON 데이터가 주어지면 Redux 스토어에서 사용하기에 가장 좋은 데이터 구조는 무엇입니까?
[
{
"id": "xlkxhemkuiam",
"title": "voluptates sequi et praesentium eos consequatur cumque omnis",
"body": "Beatae distinctio libero voluptates nobis voluptatem...",
"createdAt": "Tue, 22 Sep 2020 16:28:53 GMT",
"user": {
"id": "lswamlcggqlw",
"handle": "Payton_Carter",
"imgUrl": "https://s3.amazonaws.com/uifaces/faces/twitter/dawidwu/128.jpg"
},
"comments": [
{
"id": "jsyrjkxwtpmu",
"body": "Sint deserunt assumenda voluptas doloremque repudiandae...",
"createdAt": "Fri, 25 Sep 2020 18:03:26 GMT",
"user": {
"id": "hqhhywrxpprz",
"handle": "Orlo97",
"imgUrl": "https://s3.amazonaws.com/uifaces/faces/twitter/ponchomendivil/128.jpg"
}
}
]
},
...
]
가장 쉽고 일반적인 방법은 블로그 게시물 배열을 받은 그대로 저장하는 것입니다. ID가 주어진 특정 게시물에 대한 데이터를 표시하려면 일치하는 게시물을 찾을 때까지 배열을 반복해야 합니다. 또한 Redux 스토어에서 upsert 작업을 수행하려면 다시 반복에 의존해야 합니다. 분명히 두 작업 모두 O(n)의 시간 복잡성으로 인해 데이터를 정규화하고 결과적으로 복잡성을 O(1)로 줄일 수 있습니다.
You don't always have to deal with data the same format the server gives you.
예, 이 아이디어는 수년 동안 사용되어 왔으며 이를 지원하는 normalizr과 같은 인기 있는 도구가 있습니다. 그러나 이러한 도구로 쉽게 구문 분석할 수 없는 깊이 중첩된 데이터가 있는 경우에는 어떻게 해야 합니까? 여기서는 몇 가지 인기 있는 React Typescript fp 라이브러리fp-ts , io-ts , monocle-ts를 사용하여 유형이 안전한 맞춤형 리듀서 함수를 구축할 수 있는 한 가지 가능한 접근 방식을 제시합니다.
이것은 단계별 가이드라기보다는 빠른 실행에 가깝습니다. 관심이 있다면 소스 코드를 살펴보는 것이 좋습니다. 라이브 데모here도 볼 수 있습니다.
한스호프만 / fp-데이터-정규화
fp-ts를 사용한 유형 안전 데이터 정규화
정규화하자
시작하기 전에 O(1) 조회를 허용하는 방식으로 정규화된 데이터의 모양을 지정해 보겠습니다.
export type AppState = {
entities: {
comments: NormalizedComments;
posts: NormalizedPosts;
users: NormalizedUsers;
};
};
1 단계
io-ts
를 사용하여 도메인 유형을 선언하면 컴파일 시간과 런타임 유형 안전성을 모두 얻을 수 있습니다. 예를 들어, 우리의 Post
:/**
* Composite types
*/
export const Post = t.type({
id: IdString,
title: NonEmptyString,
body: NonEmptyString,
createdAt: UtcDateString,
user: User,
comments: Comments,
});
/**
* Static types
*/
export type Post = t.TypeOf<typeof Post>;
사용자 지정 유형을 지정하여 기본 문자열을 사용하는 대신 몇 가지 제약 조건을 추가할 수 있습니다. 예를 들어
IdString
는 주어진 문자열의 길이가 정확히 12자이고 숫자를 포함하지 않도록 합니다. "jsyrjkxwtpmu"./**
* Type guards
*/
const isIdString = (input: unknown): input is string => {
return typeof input === "string" && /[A-Za-z]{12}/g.test(input);
};
/**
* Custom codecs
*/
const IdString = new t.Type<string, string, unknown>(
"idString",
isIdString,
(input, context) => (isIdString(input) ? t.success(input) : t.failure(input, context)),
t.identity,
);
2 단계
이제 도메인 정적 유형을 사용하여 예기치 않은 API 응답으로 인해 React 앱이 충돌하는 것을 방지할 수 있습니다. 또한 도메인 로직의 모든 불필요한 오류 검사를 하나의 간단한 검사로 높였습니다. 디코더 감사합니다! 🎉
const fetchPosts = (): Posts => {
const result = Posts.decode(data);
return pipe(
result,
E.fold(
() => {
console.warn(PathReporter.report(result));
return [];
},
(posts) => posts,
),
);
};
이것은 정말 멋진 부분입니다! API 응답에 잘못된 형식의 ID가 포함되어 있거나 완전히 누락된 경우 감속기 기능에 들어가기 전에 이를 포착할 수 있습니다. 잠시만요... 내부 API도 발밑에서 변경되거나 손상된 데이터가 유입될 수 있습니다. 우리는 이로부터 앱을 보호할 수 있습니다. 직접 조작
data.json
하고 실제로 작동하는 것을 확인하십시오.The ability to declare types once and get both compile and runtime safety is a joy worth experiencing.
io-ts
디코더에서 반환된 어느 쪽이든 유형은 지적할 가치가 있는 한 가지 흥미로운 부작용을 생성합니다. 실패 시 빈 배열을 전달하여 결과적으로 React 앱에서 렌더링된 블로그 게시물이 없게 됩니다. 이것은 멋진 UX를 제공합니까? 확실히 우리 앱이 충돌하지 않는 것이 대안보다 낫지만 행복한 매체를 찾고 일부 데이터를 렌더링할 수 있을까요?나는 여전히 이것을 스스로 해결하고 있습니다. 몇몇 동료는
fp-ts
These을 조사할 것을 제안했고 한 명은 PR을 제출했습니다! 직접 확인해보세요.3단계
마지막으로 상태에서 엔티티를 추가하거나 업데이트하려고 할 때 지저분하고 오류가 발생하기 쉬운 JS 개체 확산을 수행하는 대신
monocle-ts
를 사용하여 렌즈를 정의하면 삶이 더 쉬워집니다. 아래에서 upsert 함수는 먼저 주어진 사용자가 이미 저장되어 있는지 확인하여 특정 사용자 속성(예: 사용자 ID)이 삽입되면 업데이트될 수 없도록 합니다. 또한 사용자는 내 예제에서 핸들과 프로필 이미지를 변경할 수 있으므로 이러한 속성을 업데이트할 수 있습니다./**
* Optics
*/
const usersLens = Lens.fromPath<AppState>()(["entities", "users"]);
const atUser = (id: IdString) => Lens.fromProp<NormalizedUsers>()(id);
/**
* Upserts
*/
const upsertUser = (user: User) => (state: AppState): AppState => {
return pipe(
state,
R.lookup(user.id),
O.fold(
() => {
return pipe(
state,
usersLens.compose(atUser(user.id)).set({
id: user.id,
handle: user.handle,
imgUrl: user.imgUrl,
}),
);
},
(_user) => {
return pipe(
state,
usersLens.compose(atUser(user.id)).modify(
(prevUser): UserEntity => ({
...prevUser,
handle: user.handle,
imgUrl: user.imgUrl,
}),
),
);
},
),
);
};
결론
렌즈와 디코더를 사용하여 데이터를 정규화하려면 약간의 노력이 필요하지만 그렇게 한 것에 대한 보상을 보여주었기를 바랍니다. 이와 같은 안전한 형식의 코드를 보면 얼굴에 미소가 지어지지 않습니까? 😎
추신 — 더 우아하거나 관용적인 방법이 있으면 알려주세요! 나는 모두 귀입니다.
Reference
이 문제에 관하여(데이터를 정규화하기 위해 유형이 안전한 감속기를 작성하십시오.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/hansjhoffman/write-your-own-type-safe-reducer-to-normalize-your-data-1mpn텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)