처음 ELM.× GraphiQL~Elm에서 포켓몬을 재현할 때까지~

54973 단어 GraphQLElmtech
이 글은 Elm 추가 달력 22일째 되는 글이다.
Elm의 시도로 GraphQL Pokemon 데이터만 그리는 프로그램을 만들었다.Elm × 나는 GraphiQL의 입문이 매우 좋다고 생각해서 교과서 형식으로 총결하였다.

만든 물건


GraphQL Pokemon에 조회를 던져 응답을 나타내는 Elm 앱을 제작합니다.

기술 스택은 다음과 같습니다.
  • Elm 0.19.1
  • elm-graphql 5.0.3
  • create-elm-app 5.22.0
  • 이번에 설명한 코드는 모두 여기 창고에 있습니다.움직이지 않으면 확인해 주세요.
    https://github.com/kawamataryo/elm-graphql-pokemon

    1. 프로젝트 제작


    create-react-app의 Elm 버전create-elm-app으로 제작된 프로젝트입니다.
    주요 파일과 구축 환경을 동시에 만들 수 있어 편리하다.
    $ npx create-elm-app my-app
    
    my-app로 이동하면 디렉터리는 다음과 같습니다.
    my-app/
    ├── .gitignore
    ├── README.md
    ├── elm.json
    ├── elm-stuff
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   ├── logo.svg
    │   └── manifest.json
    ├── src
    │   ├── Main.elm
    │   ├── index.js
    │   ├── main.css
    │   └── serviceWorker.js
    └── tests
        └── Tests.elm
    
    애플리케이션을 시작합니다.
    npx elm-app start
    
    조금만 기다리면localhost 프로그램이 시작됩니다.
    Elm의 환경 구축이 완료되었습니다.

    화면을 제작하는 단계에서는 조형을 수월하게 하기 위해 먼저 엘엠에 CSS 프레임Bulma과 볼마의 등급을 추가해 안전하게 사용형ahstro/elm-bulma-classes을 적용한다.
    https://github.com/ahstro/elm-bulma-classes
    $ yarn add bulma
    $ elm install ahstro/elm-bulma-classes
    
    src/index.js에 다음 내용을 보충해 주십시오.이렇게 구성할 때 Bullma의 CSS를 읽습니다.
    index.js
    import 'bulma/css/bulma.min.css';
    

    2.elm-graphiql &CodeGenerate 증가


    Elm의 GraphQL 클라이언트는 여러 가지가 있지만 이번에는 GraphQL 조회를 안전하게 쓰려고 GraphQL 모드를 이용해 유형과 관련 함수dillonkearns/elm-graphql를 자동으로 생성한다.
    https://github.com/dillonkearns/elm-graphql
    먼저 설치합니다.
    elm-graphiql은 elm의 포장뿐만 아니라 npm의 포장도 추가하는 것이 중점이다.
    elm-graphiql의 Code Generatr 스크립트를 계속 시작합니다.제이슨에 기입하다.
    여기GraphQL Pokemon의 URL을 지정합니다.
    package.json
    $ elm install dillonkearns/elm-graphql
    $ elm install elm/json
    $ elm install krisajenkins/remotedata
    $ yarn add -D @dillonkearns/elm-graphql
    
    이 상태에서 yarn code:generate를 실행하면 다음과 같은 src/Pokemons 파일이 자동으로 생성됩니다.
      "scripts": {
        "code:generate": "elm-graphql https://graphql-pokemon2.vercel.app/ --base Pokemon"
      }
    
    Elm에서 GraphiQL을 안전하게 사용하는 간이다.

    3. 설치


    호스트가 설치됩니다.
    코드가 길기 때문에 요점만 발췌합니다.
    전체 코드는 아래를 보세요.
    GraphQLClient.elm
    src/GraphqlClient.elm
    src/Pokemon/
    ├── Enum
    ├── InputObject
    ├── InputObject.elm
    ├── Interface
    ├── Interface.elm
    ├── Object
    │   ├── Attack.elm
    │   ├── Pokemon.elm
    │   ├── PokemonAttack.elm
    │   ├── PokemonDimension.elm
    │   └── PokemonEvolutionRequirement.elm
    ├── Object.elm
    ├── Query.elm
    ├── Scalar.elm
    ├── ScalarCodecs.elm
    ├── Union
    ├── Union.elm
    ├── VerifyScalarCodecs.elm
    └── elm-graphql-metadata.json
    
    Main.elm
    src/Main.elm
    module GraphQLClient exposing (makeGraphQLQuery)
    
    import Graphql.Http
    import Graphql.Operation exposing (RootQuery)
    import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
    
    
    graphql_url : String
    graphql_url =
        "https://graphql-pokemon2.vercel.app/"
    
    
    makeGraphQLQuery : SelectionSet decodesTo RootQuery -> (Result (Graphql.Http.Error decodesTo) decodesTo -> msg) -> Cmd msg
    makeGraphQLQuery query decodesTo =
        query
            |> Graphql.Http.queryRequest graphql_url
            |> Graphql.Http.send decodesTo
    

    GraphQL Client


    GraphiQL 요청을 보낸 고객입니다.
    GraphiQL Pokemon은 인증이 필요하지 않기 때문에, 단점의 URL을 elm-graphiql에서 제공하는 함수에 전달하면 됩니다.
    src/GraphQLClient.elm
    module Main exposing (..)
    
    import Browser
    import Bulma.Classes as Bulma
    import GraphQLClient exposing (makeGraphQLQuery)
    import Graphql.Http
    import Graphql.Operation exposing (RootQuery)
    import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
    import Html exposing (Html, div, figure, h1, img, p, text)
    import Html.Attributes exposing (class, src)
    import List
    import Pokemon.Object
    import Pokemon.Object.Pokemon as Pokemon
    import Pokemon.Query as Query exposing (PokemonsRequiredArguments)
    import Pokemon.ScalarCodecs
    import RemoteData exposing (RemoteData)
    
    
    
    ---- MODEL ----
    
    
    type alias Pokemon =
        { id : Pokemon.ScalarCodecs.Id
        , name : Maybe String
        , image : Maybe String
        }
    
    
    type alias Pokemons = Maybe (List (Maybe Pokemon)) 
    
    type alias PokemonData =
        RemoteData (Graphql.Http.Error Pokemons) Pokemons
    
    
    type alias Model =
        { pokemons : PokemonData }
    
    
    init : ( Model, Cmd Msg )
    init =
        ( { pokemons = RemoteData.Loading
          }
        , fetchPokemons 151
        )
    
    
    
    ---- UPDATE ----
    
    
    type Msg
        = FetchDataSuccess PokemonData
    
    
    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
        case msg of
            FetchDataSuccess response ->
                updatePokemonsData response model Cmd.none
    
    
    updatePokemonsData : PokemonData -> Model -> Cmd Msg -> ( Model, Cmd Msg )
    updatePokemonsData data model cmd =
        ( { model | pokemons = data }, cmd )
    
    
    
    ---- VIEW ----
    
    
    view : Model -> Html Msg
    view model =
        div [ class Bulma.container ]
            [ img [ src "https://i.gyazo.com/480551bded5134ddacf08616b2595717.png" ] []
            , h1 [ class Bulma.title, class Bulma.is4, class Bulma.mb6 ] [ text "Pokemons with elm-graphql" ]
            , renderPokemonList model.pokemons
            ]
    
    
    renderPokemonList : PokemonData -> Html Msg
    renderPokemonList pokemonData =
        let
            renderPokemon pokemon =
                div [ class Bulma.card ]
                    [ div [ class Bulma.cardImage ]
                        [ figure [ class Bulma.image, class Bulma.is16by9, class Bulma.mx5, class Bulma.mt5 ]
                            [ img [ src (pokemon.image |> Maybe.withDefault "") ] []
                            ]
                        ]
                    , div [ class Bulma.cardContent ]
                        [ p [ class Bulma.isSize4 ] [ text (pokemon.name |> Maybe.withDefault "") ]
                        ]
                    ]
    
            renderPokemons maybePokemons =
                maybePokemons
                    |> Maybe.withDefault []
                    |> List.map
                        (\maybePokemon ->
                            div [ class Bulma.column, class Bulma.is3 ]
                                [ maybePokemon
                                    |> Maybe.map renderPokemon
                                    |> Maybe.withDefault (text "")
                                ]
                        )
                    |> div [ class Bulma.columns, class Bulma.isMultiline ]
        in
        case pokemonData of
            RemoteData.NotAsked ->
                p [ class Bulma.isSize4, class Bulma.hasTextCentered ] [ text "not" ]
    
            RemoteData.Success maybePokemons ->
                renderPokemons maybePokemons
    
            RemoteData.Loading ->
                p [ class Bulma.isSize4, class Bulma.hasTextCentered ] [ text "loading..." ]
    
            RemoteData.Failure err ->
                p [ class Bulma.isSize4, class Bulma.hasTextCentered ] [ text "Error" ]
    
    
    
    
    ---- GraphQL API ----
    
    
    pokemonsRequiredArguments : Int -> PokemonsRequiredArguments
    pokemonsRequiredArguments num =
        { first = num }
    
    
    pokemonListSelection : SelectionSet Pokemon Pokemon.Object.Pokemon
    pokemonListSelection =
        SelectionSet.map3 Pokemon
            Pokemon.id
            Pokemon.name
            Pokemon.image
    
    
    fetchPokemonsQuery : Int -> SelectionSet Pokemons RootQuery
    fetchPokemonsQuery num =
        Query.pokemons (pokemonsRequiredArguments num) pokemonListSelection
    
    
    fetchPokemons : Int -> Cmd Msg
    fetchPokemons num =
        makeGraphQLQuery (fetchPokemonsQuery num) (RemoteData.fromResult >> FetchDataSuccess)
    
    
    
    ---- PROGRAM ----
    
    
    main : Program () Model Msg
    main =
        Browser.element
            { view = view
            , init = \_ -> init
            , update = update
            , subscriptions = always Sub.none
            }
    

    Type alias & Model


    Main.엘엠의 type alias와 모델입니다.
    GraphiQL 요청의 응답은 krisajenkins/remotedataRemoteData로 처리됩니다.init에서 포켓몬을 가져오는 GraphiQL 조회를 실행 중입니다.
    main.elm
    graphql_url : String
    graphql_url =
        "https://graphql-pokemon2.vercel.app/"
    
    
    makeGraphQLQuery : SelectionSet decodesTo RootQuery -> (Result (Graphql.Http.Error decodesTo) decodesTo -> msg) -> Cmd msg
    makeGraphQLQuery query decodesTo =
        query
            |> Graphql.Http.queryRequest graphql_url
            |> Graphql.Http.send decodesTo
    

    GraphQL Query


    이것은 GraphiQL의 조회 부분입니다.pokemonsRequiredArguments에서 GraphiQL이 질의할 때의 매개변수입니다.조립pokemonListSelection에서 얻은 필드입니다.fetchPokemonsQuery에서 어떤 조회를 호출할지 지정합니다.이것은 Query.pokemons 자동으로 생성된 함수다.
    마지막으로elm-graphql에서 방금 제작한fetchPokemons을 사용하여GraphiQL 요청을 수행하는 함수를 제작합니다.
    elm-graphiql에서 제공하는 형식 함수를 사용하여 형식을 완전히 준수한 상태에서GraphiQL 조회를 할 수 있습니다.대단해!
    Main.elm
    type alias Pokemon =
        { id : Pokemon.ScalarCodecs.Id
        , name : Maybe String
        , image : Maybe String
        }
    
    
    type alias Pokemons =
        Maybe (List (Maybe Pokemon))
    
    
    type alias PokemonData =
        RemoteData (Graphql.Http.Error Pokemons) Pokemons
    
    
    type alias Model =
        { pokemons : PokemonData }
    
    init : ( Model, Cmd Msg )
    init =
        ( { pokemons = RemoteData.Loading
          }
        , fetchPokemons 151
        )
    

    Update


    Update를 통해 상태를 변경합니다.
    GraphiQL 조회를 실행한 후 FetchDataSuccess의 Msg를 통해 응답 업데이트 모델입니다.
    Main.elm
    pokemonsRequiredArguments : Int -> PokemonsRequiredArguments
    pokemonsRequiredArguments num =
        { first = num }
    
    
    pokemonListSelection : SelectionSet Pokemon Pokemon.Object.Pokemon
    pokemonListSelection =
        SelectionSet.map3 Pokemon
            Pokemon.id
            Pokemon.name
            Pokemon.image
    
    
    fetchPokemonsQuery : Int -> SelectionSet Pokemons RootQuery
    fetchPokemonsQuery num =
        Query.pokemons (pokemonsRequiredArguments num) pokemonListSelection
    
    
    fetchPokemons : Int -> Cmd Msg
    fetchPokemons num =
        makeGraphQLQuery (fetchPokemonsQuery num) (RemoteData.fromResult >> FetchDataSuccess)
    

    View


    마지막은 View입니다.makeGraphQLQuerypokemonData의 RemoteData 값에 따라 변경 처리.
    결과의 HTML은 RemoteData가 Success인 경우에만 표시됩니다.
    RemoteData를 사용하면 팟캐스트할 때 로드 문자열을 쉽게 보낼 수 있어 좋습니다.
    GraphiQL Pokemon의 반응은 등급별 메이브형이지만 메이브형을 제거하면서 HTML을 조립하는 과정에 익숙하지 않아 막힌다.마지막으로 여기.의 문장renderPokemonsMaybe.withDefault을 참고하여 쓰겠습니다.
    메이비형을 좀 더 스마트하게 다룰 수 있는 방법이 있다면 알고 싶어요🙏
    2010/12/2220:00 추기
    Elm의 선교사들의 권유로 "let in"을 사용한 표기법을 팩스로 전송했습니다.감사🙏
    type Msg
        = FetchDataSuccess PokemonData
    
    
    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
        case msg of
            FetchDataSuccess response ->
                updatePokemonsData response model Cmd.none
    
    
    updatePokemonsData : PokemonData -> Model -> Cmd Msg -> ( Model, Cmd Msg )
    updatePokemonsData data model cmd =
        ( { model | pokemons = data }, cmd )
    
    이상 종료.
    다음 명령을 실행하면 포켓몬이 나타날 거예요!
    완성🎉
    view : Model -> Html Msg
    view model =
        div [ class Bulma.container ]
            [ img [ src "https://i.gyazo.com/480551bded5134ddacf08616b2595717.png" ] []
            , h1 [ class Bulma.title, class Bulma.is4, class Bulma.mb6 ] [ text "Pokemons with elm-graphql" ]
            , renderPokemonList model.pokemons
            ]
    
    
    renderPokemonList : PokemonData -> Html Msg
    renderPokemonList pokemonData =
        let
            renderPokemon pokemon =
                div [ class Bulma.card ]
                    [ div [ class Bulma.cardImage ]
                        [ figure [ class Bulma.image, class Bulma.is16by9, class Bulma.mx5, class Bulma.mt5 ]
                            [ img [ src (pokemon.image |> Maybe.withDefault "") ] []
                            ]
                        ]
                    , div [ class Bulma.cardContent ]
                        [ p [ class Bulma.isSize4 ] [ text (pokemon.name |> Maybe.withDefault "") ]
                        ]
                    ]
    
            renderPokemons maybePokemons =
                maybePokemons
                    |> Maybe.withDefault []
                    |> List.map
                        (\maybePokemon ->
                            div [ class Bulma.column, class Bulma.is3 ]
                                [ maybePokemon
                                    |> Maybe.map renderPokemon
                                    |> Maybe.withDefault (text "")
                                ]
                        )
                    |> div [ class Bulma.columns, class Bulma.isMultiline ]
        in
        case pokemonData of
            RemoteData.NotAsked ->
                p [ class Bulma.isSize4, class Bulma.hasTextCentered ] [ text "not" ]
    
            RemoteData.Success maybePokemons ->
                renderPokemons maybePokemons
    
            RemoteData.Loading ->
                p [ class Bulma.isSize4, class Bulma.hasTextCentered ] [ text "loading..." ]
    
            RemoteData.Failure err ->
                p [ class Bulma.isSize4, class Bulma.hasTextCentered ] [ text "Error" ]
    
    

    끝말


    이상× GraphiQL”.
    예전에 Elm의 학습회에 참가한 적이 있어요. 이렇게 했지만 결국 Elm을 접할 기회가 없어서 Elm advent 달력 기한 4일 전에 갑자기 이걸 했어요.😅
    아직 Elm에 익숙하지 않아서 이상한 점이 있으면 편하게 메모를 남겨주시면 좋을 것 같아요.
    솔직히 처음에는 아무리 해도 번역 오류를 바로잡을 수 없었고 완전히 꽉 차서 새로운 언어를 끊임없이 모색하면서 공부하는 과정이 재미있었다.
    이번 앱 제작은 엘엠과 조금 친해진 것 같아서 천천히 쓸 기회를 보고 싶었다.

    참고 자료

  • [Elm] 메이비를 활용한 - Qiita
  • halfzebra/create-elm-app: 🍃 Create Elm apps with zero configuration
  • Client Side Elm Setup | GraphQL Elm Tutorial
  • dillonkearns/elm-graphql: Autogenerate type-safe GraphQL queries in Elm.
  • 좋은 웹페이지 즐겨찾기