데이터 획득은 TypeScript, io 및 fp-ts가 지원하는 기능으로 수행됩니다.


지난 며칠 동안 나는 React 응용 프로그램을 개발해 왔다.그것은 데이터베이스가 필요 없을 정도로 간단한 응용 프로그램이다.그러나, 나는 모든 내용을 응용 프로그램의 JSX에 끼워 넣고 싶지 않다. 왜냐하면 그 중 일부는 자주 업데이트되기 때문이다.그래서 나는 몇 개의 간단한 JSON 파일을 사용하여 내용을 저장하기로 결정했다.
이 응용 프로그램은 회의 사이트입니다. 아래와 같은 페이지를 만들고 싶습니다.

이전 이미지와 유사한 페이지를 만들기 위해 다음 JSON 파일에 데이터를 저장합니다.
[
    { "startTime": "08:00", "title": "Registration & Breakfast", "minuteCount": 60 },
    { "startTime": "09:00", "title": "Keynote", "minuteCount": 25 },
    { "startTime": "09:30", "title": "Talk 1 (TBA)", "minuteCount": 25 },
    { "startTime": "10:00", "title": "Talk 2 (TBA)", "minuteCount": 25 },
    { "startTime": "10:30", "title": "Talk 3 (TBA)", "minuteCount": 25 },
    { "startTime": "10:55", "title": "Coffee Break", "minuteCount": 15 },
    { "startTime": "11:10", "title": "Talk 4 (TBA)", "minuteCount": 25 },
    { "startTime": "11:40", "title": "Talk 5 (TBA)", "minuteCount": 25 },
    { "startTime": "12:10", "title": "Talk 6 (TBA)", "minuteCount": 25 },
    { "startTime": "12:35", "title": "Lunch, Networking & Group Pic", "minuteCount": 80 },
    { "startTime": "14:00", "title": "Talk 7 (TBA)", "minuteCount": 25 },
    { "startTime": "14:30", "title": "Talk 8 (TBA)", "minuteCount": 25 },
    { "startTime": "15:00", "title": "Talk 9 (TBA)", "minuteCount": 25 },
    { "startTime": "15:25", "title": "Coffee Break", "minuteCount": 15 },
    { "startTime": "15:40", "title": "Talk 10 (TBA)", "minuteCount": 25 },
    { "startTime": "16:10", "title": "Talk 11 (TBA)", "minuteCount": 25 },
    { "startTime": "16:40", "title": "Talk 12 (TBA)", "minuteCount": 25 },
    { "startTime": "17:10", "title": "Closing Remarks", "minuteCount": 25 }
]

문제


JSON 파일을 사용하면 내 생활이 더욱 수월해지지만,React의 데이터 획득은 매우 중복되고 무미건조한 작업이다.만약 이것이 아직 나쁘지 않다면, HTTP 응답에 포함된 데이터는 우리가 예상한 것과 완전히 다를 수 있습니다.
TypeScript 사용자에게 호출된 유형을 가져오는 것은 특히 위험하다. 왜냐하면 이것은 TypeScript의 많은 장점을 손상시켰기 때문이다.그래서 나는 약간의 실험을 해서 좋은 자동화 해결 방안을 제시하기로 결정했다.
지난 몇 달 동안 나는 함수 프로그래밍과 범주 이론에 관한 지식을 많이 배웠다. 왜냐하면 나는 줄곧 Hands-On Functional Programming with TypeScript라는 책을 썼기 때문이다.
나는 이 박문에서 범주 이론에 대해 너무 많은 탐구를 하지 않을 것이다.그러나 나는 기본 원리를 설명해야 한다.범주 이론은 부작용을 처리할 때 특히 유용한 유형을 정의했다.
범주 이론 유형은 우리가 유형 시스템을 사용하여 잠재적인 문제를 표현할 수 있고 유익하다. 왜냐하면 코드가 컴파일할 때 부작용을 정확하게 처리하도록 하기 때문이다.예를 들어 Either 유형은 한 유형Left 또는 다른 유형Right을 나타낼 수 있다.Either 유형은 우리가 오류가 발생할 수 있음을 표시하고자 할 때 매우 유용하다.예를 들어 fetch 호출은 오류 (왼쪽) 나 일부 데이터 (오른쪽) 를 되돌려줍니다.

A) 오류가 처리되었는지 확인

fetch 호출된 반환이 Either 실례라는 것을 확인하고 싶습니다. 우선 응답이 오류가 아니라는 것을 보장하지 않은 상황에서 우리는 데이터에 접근하지 않을 것입니다.
나는 운이 좋다. 왜냐하면 나는 Either 유형을 실현할 필요가 없기 때문이다.반대로 나는 fp-ts 개원 모듈에 포함된 실현을 간단하게 사용할 수 있다.Either 유형은 fp ts에서 다음과 같이 정의합니다.
declare type Either<L, A> = Left<L, A> | Right<L, A>;

B) 데이터 검증


내가 해결하고 싶은 두 번째 문제는 데이터를 되돌려 달라고 해도 프로그램이 원하는 형식이 아닐 수도 있다는 것이다.응답을 검증하기 위해 실행 시 검증 메커니즘이 필요합니다.나는 처음부터 실행할 때 검증 메커니즘이 아니라 다른 소스 라이브러리 io-ts 를 사용할 수 있어서 다시 한 번 행운을 느꼈다.

솔루션


TL;DR This section explains the implementation details of the solution. Feel free to skip this part and jump into "The result" section if you are only interested in the final consumer API.


iots 모듈은 실행할 때 검증을 수행할 수 있는 모드를 설명할 수 있습니다.또한 주어진 모드에서 io T를 사용하여 유형을 생성할 수 있습니다.다음 코드 세그먼트는 두 가지 기능을 보여 줍니다.
import * as io from "io-ts";

export const ActivityValidator = io.type({
    startTime: io.string,
    title: io.string,
    minuteCount: io.number
});

export const ActivityArrayValidator = io.array(ActivityValidator);

export type IActivity = io.TypeOf<typeof ActivityValidator>;
export type IActivityArray = io.TypeOf<typeof ActivityArrayValidator>;
우리는 decode 방법을 사용하여 일부 데이터가 패턴에 부합되는지 검증할 수 있다.decode이 반환한 검증 결과는 Either 실례입니다. 이것은 우리가 검증 오류 (왼쪽) 또는 유효한 데이터 (오른쪽) 를 얻게 된다는 것을 의미합니다.
나의 첫 번째 단계는 포장 fetch API이기 때문에 fpts와iots를 사용하여 응답이 오류 (왼쪽) 또는 유효한 데이터 (오른쪽) 를 나타내는 Either 인지 확인합니다.이렇게 함으로써 fetch 돌아온 약속은 영원히 거절되지 않을 것이다.반면 항상 Either 인스턴스로 해석됩니다.
import { Either, Left, Right } from "fp-ts/lib/Either";
import { Type, Errors} from "io-ts";
import { reporter } from "io-ts-reporters";

export async function fetchJson<T, O, I>(
    url: string,
    validator: Type<T, O, I>,
    init?: RequestInit
): Promise<Either<Error, T>> {
    try {
        const response = await fetch(url, init);
        const json: I = await response.json();
        const result = validator.decode(json);
        return result.fold<Either<Error, T>>(
            (errors: Errors) => {
                const messages = reporter(result);
                return new Left<Error, T>(new Error(messages.join("\n")));
            },
            (value: T) => {
                return new Right<Error, T>(value);
            }
        );
    } catch (err) {
        return Promise.resolve(new Left<Error, T>(err));
    }
}
그리고 Remote라는 React 구성 요소를 만들었습니다. 이 구성 요소는 Either 실례를 일부 렌더링 함수와 함께 속성 중 하나로 사용합니다.데이터는 null | Error 또는 특정한 유형의 값T이 될 수 있다.
데이터가 loading일 때 호출null 함수, 데이터가 error일 때 호출Error, 데이터가 success 유형의 값일 때 호출T 함수:
import React from "react";
import { Either } from "fp-ts/lib/either";

interface RemoteProps<T> {
  data: Either<Error | null, T>;
  loading: () => JSX.Element,
  error: (error: Error) => JSX.Element,
  success: (data: T) => JSX.Element
}

interface RemoteState {}

export class Remote<T> extends React.Component<RemoteProps<T>, RemoteState> {

  public render() {
    return (
      <React.Fragment>
      {
        this.props.data.bimap(
          l => {
            if (l === null) {
              return this.props.loading();
            } else {
              return this.props.error(l);
            }
          },
          r => {
            return this.props.success(r);
          }
        ).value
      }
      </React.Fragment>
    );
  }

}

export default Remote;
위의 구성 요소는 렌더링 Either 실례에 사용되지만, 데이터 가져오기 작업을 실행하지 않습니다.반대로 저는 Fetchable라는 두 번째 구성 요소를 실현했습니다. 이 구성 요소는 urlvalidator, 그리고 선택할 수 있는 RequestInit 설정과 렌더링 함수를 사용합니다.이 구성 요소는 fetch 패키지와 validator 데이터를 가져와 검증합니다.그런 다음 생성된 Either 인스턴스를 Remote 어셈블리에 전달합니다.
import { Type } from "io-ts";
import React from "react";
import { Either, Left } from "fp-ts/lib/Either";
import { fetchJson } from "./client";
import { Remote } from "./remote";

interface FetchableProps<T, O, I> {
    url: string;
    init?: RequestInit,
    validator: Type<T, O, I>
    loading: () => JSX.Element,
    error: (error: Error) => JSX.Element,
    success: (data: T) => JSX.Element
}

interface FetchableState<T> {
    data: Either<Error | null, T>;
}

export class Fetchable<T, O, I> extends React.Component<FetchableProps<T, O, I>, FetchableState<T>> {

    public constructor(props: FetchableProps<T, O, I>) {
        super(props);
        this.state = {
            data: new Left<null, T>(null)
        }
    }

    public componentDidMount() {
        (async () => {
            const result = await fetchJson(
                this.props.url,
                this.props.validator,
                this.props.init
            );
            this.setState({
                data: result
            });
        })();
    }

    public render() {
        return (
            <Remote<T>
                loading={this.props.loading}
                error={this.props.error}
                data={this.state.data}
                success={this.props.success}
            />
        );
    }

}

결과


나는 이미 앞의 모든 원본 코드를 react-fetchable 라는 모듈로 발표했다.다음 명령을 사용하여 모듈을 설치할 수 있습니다.
npm install io-ts fp-ts react-fetchable
그런 다음 다음과 같이 구성 요소 Fetchable 를 가져올 수 있습니다.
import { Fetchable } from "react-fetchable";
이제 시작 페이지에서 설명할 수 있습니다.
import React from "react";
import Container from "../../components/container/container";
import Section from "../../components/section/section";
import Table from "../../components/table/table";
import { IActivityArray, ActivityArrayValidator } from "../../lib/domain/types";
import { Fetchable } from "react-fetchable";

interface ScheduleProps {}

interface ScheduleState {}

class Schedule extends React.Component<ScheduleProps, ScheduleState> {
  public render() {
    return (
      <Container>
        <Section title="Schedule">
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit,
            sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
          </p>
          <Fetchable
            url="/data/schedule.json"
            validator={ActivityArrayValidator}
            loading={() => <div>Loading...</div>}
            error={(e: Error) => <div>Error: {e.message}</div>}
            success={(data: IActivityArray) => {
              return (
                <Table
                  headers={["Time", "Activity"]}
                  rows={data.map(a => [`${a.startTime}`, a.title])}
                />
              );
            }}
          />
        </Section>
      </Container>
    );
  }
}

export default Schedule;
나는 URL/data/schedule.json을 검증기Fetchable와 함께 ActivityArrayValidator 구성 요소에 전달할 수 있다.그런 다음 어셈블리는 다음과 같이 됩니다.
  • 렌더링Loading...
  • 데이터 얻기
  • 데이터가 유효하면 표
  • 를 표시합니다.
  • 오류가 발생하면 데이터를 불러올 수 없습니다. 검증기에 맞지 않습니다
  • 나는 이 해결 방안에 대해 매우 만족한다. 왜냐하면 그것은 유형이 안전하고 성명적이기 때문에 몇 초만 있으면 시작하고 운행할 수 있기 때문이다.나는 네가 이미 이 문장이 매우 재미있다는 것을 발견했으면 좋겠다. 너는 한번 해 보아도 된다 react-fetchable .
    또한 함수식 프로그래밍이나 TypeScript에 관심이 있다면 곧 출판될 책 Hands-On Functional Programming with TypeScript 을 보십시오.

    좋은 웹페이지 즐겨찾기