드럼 시퀀서에서 느릅나무 배우기(1부)

게시 됨: 2022-03-10
빠른 요약 ↬ 프론트 엔드 개발자인 Brian Holt는 독자들에게 Elm에서 드럼 시퀀서를 구축하는 과정을 안내합니다. 2부로 구성된 이 시리즈의 1부에서 그는 Elm의 구문, 설정 및 핵심 개념을 소개합니다. 간단한 애플리케이션을 만들기 위해 Elm 아키텍처로 작업하는 방법을 배우게 됩니다.

단일 페이지 애플리케이션(SPA)의 발전을 따르는 프론트엔드 개발자라면 Redux에 영감을 준 기능적 언어인 Elm에 대해 들어봤을 것입니다. 그렇지 않은 경우 React, Angular 및 Vue와 같은 SPA 프로젝트와 비교할 수 있는 JavaScript로 컴파일하는 언어입니다.

이와 마찬가지로 코드를 유지 관리하고 성능을 향상시키는 것을 목표로 가상 돔을 통해 상태 변경을 관리합니다. 개발자 행복, 고품질 도구 및 단순하고 반복 가능한 패턴에 중점을 둡니다. 주요 차이점 중 일부는 정적으로 유형이 지정되고 매우 유용한 오류 메시지이며 기능적 언어(객체 지향과 반대)라는 점입니다.

Elm의 창시자인 Evan Czaplicki가 프론트엔드 개발자 경험에 대한 자신의 비전과 Elm에 대한 비전에 대한 강연을 통해 소개했습니다. 누군가 프론트 엔드 개발의 유지 관리 가능성과 유용성에 중점을 두었기 때문에 그의 연설은 정말 공감이 되었습니다. 나는 1년 전 보조 프로젝트에서 Elm을 시도했고 처음 프로그래밍을 시작한 이후로 경험하지 못한 방식으로 Elm의 기능과 도전을 계속 즐기고 있습니다. 나는 다시 초보자입니다. 또한 Elm의 많은 사례를 다른 언어로 적용할 수 있습니다.

의존성 인식 개발

의존성은 어디에나 있습니다. 이를 줄이면 가장 다양한 시나리오에서 가장 많은 사람들이 귀하의 사이트를 사용할 수 있는 가능성을 높일 수 있습니다.관련 기사 읽기 →

두 부분으로 구성된 이 기사에서는 Elm에서 드럼 비트를 프로그래밍하는 단계 시퀀서를 구축하면서 언어의 가장 좋은 기능을 소개합니다. 오늘 우리는 Elm의 기본 개념, 즉 시작하기, 유형 사용, 뷰 렌더링 및 상태 업데이트를 살펴보겠습니다. 이 기사의 두 번째 부분에서는 대규모 리팩터링을 쉽게 처리하고 반복되는 이벤트를 설정하고 JavaScript와 상호 작용하는 것과 같은 고급 주제를 다룹니다.

여기에서 최종 프로젝트를 플레이하고 여기에서 코드를 확인하세요.

동작하는 스텝 시퀀서
다음은 완성된 스텝 시퀀서의 실제 모습입니다.

느릅나무 시작하기

이 기사를 따라가려면 브라우저 내 Elm 개발자 환경인 Ellie를 사용하는 것이 좋습니다. Ellie를 실행하기 위해 아무것도 설치할 필요가 없으며 Ellie에서 모든 기능을 갖춘 응용 프로그램을 개발할 수 있습니다. 컴퓨터에 Elm을 설치하려면 공식 시작 안내서를 따르는 것이 가장 좋은 설정 방법입니다.

점프 후 더! 아래에서 계속 읽기 ↓

이 기사 전체에서 진행 중인 Ellie 버전에 링크할 것입니다. 비록 로컬에서 시퀀서를 개발했지만. CSS는 전적으로 Elm으로 작성할 수 있지만 저는 이 프로젝트를 PostCSS로 작성했습니다. 스타일을 로드하려면 로컬 개발을 위해 Elm Reactor에 약간의 구성이 필요합니다. 간결함을 위해 이 기사의 스타일은 다루지 않겠지만 Ellie 링크에는 축소된 CSS 스타일이 모두 포함되어 있습니다.

Elm은 다음을 포함하는 독립형 생태계입니다.

  • 느릅나무 만들기
    Elm 코드를 컴파일합니다. Webpack은 다른 자산과 함께 Elm 프로젝트를 생산하는 데 여전히 인기가 있지만 필수는 아닙니다. 이 프로젝트에서는 Webpack을 제외하고 elm make 를 사용하여 코드를 컴파일하도록 선택했습니다.
  • 느릅나무 패키지
    커뮤니티 생성 패키지/모듈을 사용하기 위한 NPM에 필적하는 패키지 관리자.
  • 느릅나무 반응기
    자동 컴파일 개발 서버를 실행하기 위한 것입니다. 더 주목할만한 것은 Time Traveling Debugger가 포함되어 있어 애플리케이션 상태를 쉽게 단계별로 살펴보고 버그를 재생할 수 있다는 것입니다.
  • 느릅나무 리플
    터미널에서 간단한 Elm 표현식을 작성하거나 테스트합니다.

모든 Elm 파일은 modules 로 간주됩니다. 모든 파일의 시작 라인에는 FileName 이 리터럴 파일 이름이고 functions 는 다른 모듈에서 액세스할 수 있도록 하려는 공용 함수인 FileName module FileName exposing (functions) 이 포함됩니다. 모듈 정의 직후 외부 모듈에서 가져옵니다. 나머지 기능은 다음과 같습니다.

 module Main exposing (main) import Html exposing (Html, text) main : Html msg main = text "Hello, World!"

Main.elm 이라는 이 모듈은 단일 함수인 main 을 노출하고 Html 모듈/패키지에서 Htmltext 를 가져옵니다. main 기능은 유형 주석 과 실제 기능의 두 부분으로 구성됩니다. 유형 주석은 함수 정의로 생각할 수 있습니다. 그들은 인수 유형과 반환 유형을 명시합니다. 이 경우 우리는 main 함수가 인수를 취하지 않고 Html msg 를 반환한다고 명시하고 있습니다. 함수 자체는 "Hello, World"가 포함된 텍스트 노드를 렌더링합니다. 함수에 인수를 전달하려면 함수의 등호 앞에 공백으로 구분된 이름을 추가합니다. 또한 인수의 순서대로 유형 주석에 인수 유형을 추가하고 화살표가 뒤따릅니다.

 add2Numbers : Int -> Int -> Int add2Numbers first second = first + second

JavaScript에서 다음과 같은 기능은 비슷합니다.

 function add2Numbers(first, second) { return first + second; }

TypeScript와 같은 Typed 언어에서는 다음과 같습니다.

 function add2Numbers(first: number, second: number): number { return first + second; }

add2Numbers 는 두 개의 정수를 가져와 정수를 반환합니다. 모든 함수는 값을 반환 해야 하므로 주석의 마지막 값은 항상 반환 값입니다. add2Numbers 2 3 처럼 5를 얻기 위해 2와 3을 사용하여 add2Numbers 를 호출합니다.

React 컴포넌트를 바인딩하는 것처럼 컴파일된 Elm 코드를 DOM에 바인딩해야 합니다. 바인딩하는 표준 방법은 모듈에서 embed() 를 호출하고 DOM 요소를 모듈에 전달하는 것입니다.

 <script> const container = document.getElementById('app'); const app = Elm.Main.embed(container); <script>

우리 앱은 실제로 아무 것도 하지 않지만 Elm 코드를 컴파일하고 텍스트를 렌더링하기에 충분합니다. Ellie에서 확인하고 26행에서 인수를 add2Numbers 로 변경해 보십시오.

Elm 앱은 추가된 숫자를 렌더링합니다.
화면에 추가된 숫자를 렌더링하는 기본적인 Elm 앱입니다.

유형을 사용한 데이터 모델링

JavaScript 또는 Ruby와 같은 동적으로 유형이 지정된 언어에서 제공되는 유형은 불필요해 보일 수 있습니다. 이러한 언어는 런타임 중에 전달되는 값에서 함수 유형을 결정합니다. 함수 작성은 일반적으로 더 빠른 것으로 간주되지만 함수가 서로 적절하게 상호 작용할 수 있도록 보장하는 보안을 잃게 됩니다.

대조적으로 Elm은 정적으로 유형이 지정됩니다. 함수에 전달된 값이 런타임 전에 호환되는지 확인하기 위해 컴파일러에 의존합니다. 이것은 사용자에게 런타임 예외가 없음을 의미하며 Elm이 "런타임 예외 없음"을 보장할 수 있는 방법입니다. 많은 컴파일러의 유형 오류가 특히 애매할 수 있는 경우 Elm은 이를 이해하고 수정하기 쉽게 만드는 데 중점을 둡니다.

Elm은 유형을 매우 친숙하게 시작합니다. 사실, Elm의 유형 추론은 매우 뛰어나서 주석에 익숙해질 때까지 주석 작성을 건너뛸 수 있습니다. 유형이 처음이라면 직접 작성하려고 하기보다 컴파일러의 제안에 의존하는 것이 좋습니다.

유형을 사용하여 데이터 모델링을 시작해 보겠습니다. 우리의 스텝 시퀀서는 특정 드럼 샘플이 언제 재생되어야 하는지에 대한 시각적 타임라인입니다. 타임라인은 트랙 으로 구성되며, 각 트랙에는 특정 드럼 샘플과 일련의 단계 가 할당됩니다. 단계 는 시간 또는 비트로 간주될 수 있습니다. 단계가 활성 상태이면 재생 중에 샘플이 트리거되어야 하고 단계가 비활성 상태 이면 샘플이 묵음 상태를 유지해야 합니다. 재생하는 동안 시퀀서는 활성 단계의 샘플을 재생하는 각 단계를 통해 이동합니다. 재생 속도는 분당 비트 수(BPM) 에 의해 설정됩니다.

일련의 단계가 있는 트랙으로 구성된 최종 애플리케이션
일련의 단계가 있는 트랙으로 구성된 최종 애플리케이션의 스크린샷.

JavaScript로 애플리케이션 모델링

유형에 대한 더 나은 아이디어를 얻기 위해 JavaScript에서 이 드럼 시퀀서를 모델링하는 방법을 고려해 보겠습니다. 트랙 배열이 있습니다. 각 트랙 개체에는 트랙 이름, 트리거할 샘플/클립, 단계 값 시퀀스와 같은 자체 정보가 포함되어 있습니다.

 tracks: [ { name: "Kick", clip: "kick.mp3", sequence: [On, Off, Off, Off, On, etc...] }, { name: "Snare", clip: "snare.mp3", sequence: [Off, Off, Off, Off, On, etc...] }, etc... ]

재생과 중지 사이의 재생 상태를 관리해야 합니다.

 playback: "playing" || "stopped"

재생하는 동안 어떤 단계를 재생해야 하는지 결정해야 합니다. 또한 단계가 증가할 때마다 각 트랙의 각 시퀀스를 순회하는 것보다 재생 성능을 고려해야 합니다. 모든 활성 단계를 단일 재생 시퀀스로 줄여야 합니다. 재생 시퀀스 내의 각 컬렉션은 재생되어야 하는 모든 샘플을 나타냅니다. 예를 들어, ["kick", "hat"] 은 킥과 하이햇 샘플이 재생되어야 함을 의미하는 반면 ["hat"] 은 하이햇만 재생해야 함을 의미합니다. 또한 샘플에 대한 고유성을 제한하기 위해 각 컬렉션이 필요하므로 ["hat", "hat", "hat"] 과 같은 것으로 끝나지 않습니다.

 playbackPosition: 1 playbackSequence: [ ["kick", "hat"], [], ["hat"], [], ["snare", "hat"], [], ["hat"], [], ... ],

그리고 재생 속도 또는 BPM을 설정해야 합니다.

 bpm: 120

느릅나무의 유형을 사용한 모델링

이 데이터를 Elm 유형으로 변환하는 것은 본질적으로 데이터가 무엇으로 만들어질 것으로 기대하는지 설명하는 것입니다. 예를 들어, 우리는 이미 데이터 모델을 model 로 참조하고 있으므로 type alias를 사용하여 이를 호출합니다. 유형 별칭은 코드를 읽기 쉽게 만드는 데 사용됩니다. 부울이나 정수와 같은 기본 유형 이 아닙니다. 그것들은 단순히 우리가 기본 유형 또는 데이터 구조에 부여하는 이름입니다. 하나를 사용하여 모델 구조를 따르는 모든 데이터를 익명 구조가 아닌 모델 로 정의합니다. 많은 Elm 프로젝트에서 주요 구조의 이름은 Model입니다.

 type alias Model = { tracks : Array Track , playback : Playback , playbackPosition : PlaybackPosition , bpm : Int , playbackSequence : Array (Set Clip) }

우리 모델은 JavaScript 객체처럼 보이지만 Elm 레코드를 설명하고 있습니다. 레코드는 관련 데이터를 고유한 유형 주석이 있는 여러 필드로 구성하는 데 사용됩니다. field.attribute 를 사용하여 쉽게 액세스할 수 있으며 나중에 볼 수 있도록 쉽게 업데이트할 수 있습니다. 개체와 레코드는 몇 가지 주요 차이점을 제외하고 매우 유사합니다.

  • 존재하지 않는 필드는 호출할 수 없습니다.
  • 필드는 null 이거나 undefined .
  • thisself 는 사용할 수 없습니다.

트랙 컬렉션은 목록, 배열 및 집합의 세 가지 가능한 유형 중 하나로 구성될 수 있습니다. 간단히 말해서, 목록은 색인이 생성되지 않은 범용 컬렉션이고, 배열은 색인이 생성되며, 집합에는 고유한 값만 포함됩니다. 어떤 트랙 단계가 토글되었는지 알기 위해서는 인덱스가 필요하며 배열이 인덱싱되기 때문에 이것이 최선의 선택입니다. 또는 트랙에 ID를 추가하고 목록에서 필터를 추가할 수 있습니다.

우리 모델에서 track 을 track 의 배열로 조판했습니다. 또 다른 레코드인 tracks : Array Track 입니다. 트랙 자체에 대한 정보가 포함되어 있습니다. name과 clip은 모두 문자열이지만 다른 함수가 코드의 다른 곳에서 참조할 것이라는 것을 알고 있기 때문에 aliased clip을 입력했습니다. 별칭을 지정하여 자체 문서화 코드를 만들기 시작합니다. 유형 및 유형 별칭을 생성하면 개발자가 데이터 모델을 비즈니스 모델로 모델링하여 유비쿼터스 언어를 생성할 수 있습니다.

 type alias Track = { name : String , clip : Clip , sequence : Array Step } type Step = On | Off type alias Clip = String

시퀀스가 켜짐/꺼짐 값의 배열이라는 것을 알고 있습니다. sequence : Array Bool 과 같은 부울 배열로 설정할 수 있지만 비즈니스 모델을 표현할 기회를 놓칠 것입니다! 스텝 시퀀서가 스텝 으로 구성되어 있다는 점을 고려하여 Step 이라는 새로운 유형을 정의합니다. Step은 boolean 에 대한 유형 별칭일 수 있지만 한 단계 더 나아갈 수 있습니다. 단계에는 두 가지 가능한 값(켜기 및 끄기)이 있으므로 이것이 우리가 합집합 유형을 정의하는 방법입니다. 이제 단계는 On 또는 Off만 가능하므로 다른 모든 상태는 불가능합니다.

PlaybackPosition 에 대해 또 다른 유형을 정의하고 Playback 에 대한 별칭을 정의하고 playbackSequence 를 Clips 세트를 포함하는 배열로 정의할 때 Clip을 사용합니다. BPM은 표준 Int 로 할당됩니다.

 type Playback = Playing | Stopped type alias PlaybackPosition = Int

유형을 시작하는 데 약간의 오버헤드가 있지만 코드는 훨씬 더 유지 관리하기 쉽습니다. 자체 문서화되며 비즈니스 모델과 함께 유비쿼터스 언어를 사용합니다. 테스트를 거치지 않고도 미래의 기능이 예상하는 방식으로 데이터와 상호 작용할 것이라는 확신은 주석을 작성하는 데 시간을 할애할 가치가 있습니다. 그리고 컴파일러의 유형 추론에 의존하여 유형을 제안할 수 있으므로 이를 작성하는 것은 복사 및 붙여넣기만큼 간단합니다. 다음은 전체 유형 선언입니다.

Elm 아키텍처 사용

Elm Architecture는 언어에서 자연스럽게 등장한 간단한 상태 관리 패턴입니다. 비즈니스 모델에 초점을 맞추고 확장성이 뛰어납니다. 다른 SPA 프레임워크와 달리 Elm은 아키텍처에 대해 독단적입니다. 모든 애플리케이션이 구조화된 방식이므로 온보딩이 간편합니다. 아키텍처는 세 부분으로 구성됩니다.

  • 애플리케이션의 상태를 포함하는 모델 및 별칭 모델 을 입력하는 구조
  • 상태를 업데이트하는 업데이트 기능
  • 상태를 시각적으로 렌더링하는 보기 기능

실제로 Elm Architecture를 학습하면서 드럼 시퀀서를 구축해 보겠습니다. 먼저 애플리케이션을 초기화하고 보기를 렌더링한 다음 애플리케이션 상태를 업데이트합니다. Ruby 배경에서 온 저는 더 짧은 파일을 선호하고 Elm 기능을 모듈로 분할하는 경향이 있지만 큰 Elm 파일을 갖는 것이 매우 일반적입니다. Ellie에서 시작점을 만들었지만 로컬에서 다음 파일을 만들었습니다.

  • 모든 유형 정의를 포함하는 types.elm
  • 프로그램을 초기화하고 실행하는 Main.elm
  • 상태를 관리하는 업데이트 기능을 포함하는 Update.elm
  • HTML로 렌더링할 Elm 코드가 포함된 View.elm

애플리케이션 초기화

작게 시작하는 것이 가장 좋으므로 켜고 끌 수 있는 단계가 포함된 단일 트랙을 만드는 데 집중하도록 모델을 줄입니다. 이미 전체 데이터 구조 를 알고 있다고 생각 하지만 작게 시작하면 트랙을 HTML로 렌더링하는 데 집중할 수 있습니다. 복잡성을 줄이고 You Ain't Gonna Need It 코드를 작성합니다. 나중에 컴파일러는 모델을 리팩토링하는 과정을 안내합니다. Types.elm 파일에서 Step 및 Clip 유형은 유지하지만 모델과 트랙은 변경합니다.

 type alias Model = { track : Track } type alias Track = { name : String , sequence : Array Step } type Step = On | Off type alias Clip = String

Elm을 HTML로 렌더링하기 위해 Elm Html 패키지를 사용합니다. 서로를 기반으로 하는 세 가지 유형의 프로그램을 만드는 옵션이 있습니다.

  • 초급 프로그램
    부작용을 배제한 축소 프로그램으로 Elm Architecture 학습에 특히 유용합니다.
  • 프로그램
    Elm 외부에 존재하는 데이터베이스 또는 도구 작업에 유용한 부작용을 처리하는 표준 프로그램입니다.
  • 플래그가 있는 프로그램
    기본 데이터 대신 실제 데이터로 자체 초기화할 수 있는 확장 프로그램입니다.

나중에 컴파일러로 변경하기 쉽기 때문에 가능한 가장 간단한 유형의 프로그램을 사용하는 것이 좋습니다. 이것은 Elm에서 프로그래밍할 때 일반적인 관행입니다. 필요한 것만 사용하고 나중에 변경하십시오. 우리의 목적을 위해 우리는 부작용으로 간주되는 JavaScript를 처리해야 한다는 것을 알고 있으므로 Html.program 을 만듭니다. Main.elm에서는 필드에 함수를 전달하여 프로그램을 초기화해야 합니다.

 main : Program Never Model Msg main = Html.program { init = init , view = view , update = update , subscriptions = always Sub.none }

프로그램의 각 필드는 애플리케이션을 제어하는 ​​Elm 런타임에 함수를 전달합니다. 간단히 말해서 Elm 런타임은 다음과 같습니다.

  • init 의 초기 값으로 프로그램을 시작합니다.
  • 초기화된 모델을 view 에 전달하여 첫 번째 뷰를 렌더링합니다.
  • 보기, 명령 또는 구독에서 update 하기 위해 메시지가 전달될 때 보기를 지속적으로 다시 렌더링합니다.

로컬에서 viewupdate 기능은 각각 View.elmUpdate.elm 에서 가져오고 잠시 후에 만들 것입니다. subscriptions 은 업데이트를 유발하는 메시지를 수신하지만 지금은 always Sub.none 을 할당하여 무시합니다. 첫 번째 함수인 init 는 모델을 초기화합니다. init 를 첫 번째 로드의 기본값처럼 생각하십시오. "kick"이라는 단일 트랙과 일련의 Off 단계로 정의합니다. 비동기 데이터를 가져오지 않기 때문에 부작용 없이 초기화하기 위해 Cmd.none 을 사용하는 명령을 명시적으로 무시합니다.

 init : ( Model, Cmd.Cmd Msg ) init = ( { track = { sequence = Array.initialize 16 (always Off) , name = "Kick" } } , Cmd.none )

우리의 init 유형 주석은 우리 프로그램과 일치합니다. 고정된 수의 값을 포함하는 튜플이라는 데이터 구조입니다. 우리의 경우 Model 과 명령. 지금은 나중에 부작용을 처리할 준비가 될 때까지 Cmd.none 을 사용하여 항상 명령을 무시합니다. 우리 앱은 아무 것도 렌더링하지 않지만 컴파일됩니다!

애플리케이션 렌더링

우리의 관점을 구축합시다. 이 시점에서 우리 모델에는 단일 트랙이 있으므로 렌더링해야 하는 유일한 것입니다. HTML 구조는 다음과 같아야 합니다.

 <div class="track"> <p class "track-title">Kick</p> <div class="track-sequence"> <button class="step _active"></button> <button class="step"></button> <button class="step"></button> <button class="step"></button> etc... </div> </div>

뷰를 렌더링하는 세 가지 함수를 빌드합니다.

  1. 하나는 트랙 이름과 시퀀스가 ​​포함된 단일 트랙을 렌더링하는 것입니다.
  2. 시퀀스 자체를 렌더링하는 또 다른 방법
  3. 그리고 시퀀스 내에서 각 개별 단계 버튼을 렌더링하기 위해 하나 더

첫 번째 보기 기능은 단일 트랙을 렌더링합니다. 우리는 유형 주석인 renderTrack : Track -> Html Msg 에 의존하여 통과된 단일 트랙을 적용합니다. 유형을 사용한다는 것은 renderTrack 에 트랙이 있다는 것을 항상 알고 있다는 의미입니다. 레코드에 name 필드가 있는지 또는 레코드 대신 문자열을 전달했는지 확인할 필요가 없습니다. Track 이외의 것을 renderTrack 에 전달하려고 하면 Elm이 컴파일되지 않습니다. 더 좋은 것은 우리가 실수를 해서 실수로 트랙 이외의 다른 것을 함수에 전달하려고 하면 컴파일러가 우리에게 올바른 방향을 알려주는 친근한 메시지를 줄 것이라는 점입니다.

 renderTrack : Track -> Html Msg renderTrack track = div [ class "track" ] [ p [ class "track-title" ] [ text track.name ] , div [ class "track-sequence" ] (renderSequence track.sequence) ]

당연한 것 같지만 HTML 작성을 포함하여 모든 Elm은 Elm입니다. HTML을 작성하기 위한 템플릿 언어나 추상화는 없습니다. 모두 Elm입니다. HTML 요소는 이름, 속성 목록 및 자식 목록을 사용하는 Elm 함수입니다. 따라서 div [ class "track" ] [] 출력 <div class="track"></div> . 목록은 Elm에서 쉼표로 구분되므로 div에 id를 추가하는 것은 div [ class "track", id "my-id" ] [] 같습니다.

div 래핑 track-sequence 는 트랙의 시퀀스를 두 번째 함수인 renderSequence 에 전달합니다. 시퀀스를 가져와 HTML 버튼 목록을 반환합니다. 추가 기능을 건너뛰기 위해 renderSequencerenderTrack 에 유지할 수 있지만 기능을 더 작은 조각으로 나누는 것이 훨씬 더 쉽습니다. 또한 더 엄격한 유형 주석을 정의할 수 있는 또 다른 기회를 얻습니다.

 renderSequence : Array Step -> List (Html Msg) renderSequence sequence = Array.indexedMap renderStep sequence |> Array.toList

시퀀스의 각 단계를 매핑하고 renderStep 함수에 전달합니다. JavaScript에서 인덱스가 있는 매핑은 다음과 같이 작성됩니다.

 sequence.map((node, index) => renderStep(index, node))

JavaScript와 비교할 때 Elm의 매핑은 거의 반대입니다. 지도에 적용할 함수( renderStep )와 매핑할 배열( sequence )의 두 가지 인수를 사용하는 Array.indexedMap 을 호출합니다. renderStep 은 우리의 마지막 함수이며 버튼이 활성인지 비활성인지를 결정합니다. 업데이트 함수에 전달하기 위해 단계 인덱스(ID로 사용하는)를 단계 자체에 전달해야 하기 때문에 indexedMap 을 사용합니다.

 renderStep : Int -> Step -> Html Msg renderStep index step = let classes = if step == On then "step _active" else "step" in button [ class classes ] []

renderStep 은 인덱스를 첫 번째 인수로, 단계를 두 번째 인수로 수락하고 렌더링된 HTML을 반환합니다. let...in 블록을 사용하여 로컬 기능을 정의하고 _active 클래스를 On Steps에 할당하고 버튼 속성 목록에서 클래스 함수를 호출합니다.

일련의 단계가 포함된 킥 트랙
일련의 단계가 포함된 킥 트랙

애플리케이션 상태 업데이트

이 시점에서 앱은 킥 시퀀스의 16단계를 렌더링하지만 클릭해도 단계가 활성화되지 않습니다. 단계 상태를 업데이트하려면 업데이트 함수에 메시지( Msg )를 전달해야 합니다. 메시지를 정의하고 버튼의 이벤트 핸들러에 첨부하여 이를 수행합니다.

Types.elm에서 첫 번째 메시지인 ToggleStep 을 정의해야 합니다. 시퀀스 인덱스에 IntStep 을 취합니다. 다음으로 renderStep 에서 시퀀스 인덱스 및 단계와 함께 버튼의 클릭 이벤트에 ToggleStep 메시지를 인수로 첨부합니다. 이것은 업데이트 기능에 메시지를 보내지만 이 시점에서 업데이트는 실제로 아무 것도 하지 않습니다.

 type Msg = ToggleStep Int Step renderStep index step = let ... in button [ onClick (ToggleStep index step) , class classes ] []

메시지는 일반 유형이지만 Elm의 규칙에 따라 업데이트를 유발 하는 유형으로 정의했습니다. Update.elm에서 모델 상태 변경을 처리하기 위해 Elm 아키텍처를 따릅니다. 업데이트 함수는 Msg 와 현재 Model 을 받아 새 모델과 잠재적으로 명령을 반환합니다. 명령은 2부에서 살펴볼 부작용을 처리합니다. 여러 Msg 유형이 있다는 것을 알고 있으므로 패턴 일치 케이스 블록을 설정합니다. 이것은 우리가 상태 흐름을 분리하면서 모든 경우를 처리하도록 합니다. 그리고 컴파일러는 모델을 변경할 수 있는 경우를 놓치지 않도록 합니다.

Elm에서 레코드를 업데이트하는 것은 JavaScript에서 객체를 업데이트하는 것과 약간 다릅니다. 우리는 this 또는 self 를 사용할 수 없기 때문에 record.field = * 와 같은 레코드의 필드를 직접 변경할 수 없지만 Elm에는 내장 헬퍼가 있습니다. brian = { name = "brian" } 와 같은 레코드가 주어지면 { brian | name = "BRIAN" } { brian | name = "BRIAN" } . 형식은 { record | field = newValue } { record | field = newValue } .

이것은 최상위 필드를 업데이트하는 방법이지만 중첩 필드는 Elm에서 더 까다롭습니다. 우리는 우리 자신의 도우미 함수를 정의해야 하므로 네 개의 도우미 함수를 정의하여 중첩 레코드를 살펴보겠습니다.

  1. 단계 값을 전환하는 하나
  2. 업데이트된 단계 값을 포함하는 새 시퀀스를 반환하는 것
  3. 시퀀스가 속한 트랙을 선택하는 또 다른 항목
  4. 업데이트된 단계 값을 포함하는 업데이트된 시퀀스를 포함하는 새 트랙을 반환하는 마지막 함수

ToggleStep 으로 시작하여 트랙 시퀀스의 단계 값을 켜기와 끄기 사이에서 토글합니다. let...in 블록을 다시 사용하여 case 문 내에서 더 작은 함수를 만듭니다. 단계가 이미 Off인 경우 On으로 만들고 그 반대의 경우도 마찬가지입니다.

 toggleStep = if step == Off then On else Off

toggleStepnewSequence 에서 호출됩니다. 데이터는 함수형 언어에서 변경할 수 없으므로 시퀀스를 수정 하는 대신 실제로 이전 단계 값을 대체하기 위해 업데이트된 단계 값으로 새 시퀀스를 생성합니다.

 newSequence = Array.set index toggleStep selectedTrack.sequence

newSequenceArray.set 을 사용하여 토글하려는 인덱스를 찾은 다음 새 시퀀스를 만듭니다. set이 인덱스를 찾지 못하면 동일한 시퀀스를 반환합니다. 수정할 시퀀스를 알기 위해 selectedTrack.sequence 에 의존합니다. selectedTrack 은 중첩된 레코드에 접근할 수 있도록 사용되는 핵심 도우미 함수입니다. 이 시점에서 우리 모델에는 단일 트랙만 있기 때문에 놀라울 정도로 간단합니다.

 selectedTrack = model.track

마지막 도우미 함수는 나머지 모든 것을 연결합니다. 다시 말하지만 데이터는 변경할 수 없으므로 전체 트랙을 새 시퀀스가 ​​포함된 새 트랙으로 바꿉니다.

 newTrack = { selectedTrack | sequence = newSequence }

newTrack 은 뷰를 다시 렌더링하는 새 트랙을 포함하는 새 모델을 반환하는 let...in 블록 외부에서 호출됩니다. 우리는 부작용을 전달하지 않으므로 Cmd.none 을 다시 사용합니다. 전체 update 기능은 다음과 같습니다.

 update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleStep index step -> let selectedTrack = model.track newTrack = { selectedTrack | sequence = newSequence } toggleStep = if step == Off then On else Off newSequence = Array.set index toggleStep selectedTrack.sequence in ( { model | track = newTrack } , Cmd.none )

프로그램을 실행하면 일련의 단계가 포함된 렌더링된 트랙이 표시됩니다. 단계 버튼 중 하나를 클릭하면 ToggleStep 이 트리거되어 모델 상태를 교체하기 위해 업데이트 기능을 실행합니다.

일련의 활성 단계가 포함된 킥 트랙
일련의 활성 단계가 포함된 킥 트랙

애플리케이션이 확장됨에 따라 Elm Architecture의 반복 가능한 패턴이 상태 처리를 간단하게 만드는 방법을 볼 수 있습니다. 모델, 업데이트 및 보기 기능에 대한 친숙함은 우리가 비즈니스 도메인에 집중하는 데 도움이 되고 다른 사람의 Elm 애플리케이션으로 쉽게 이동할 수 있습니다.

휴식

새로운 언어로 글을 쓰려면 시간과 연습이 필요합니다. 내가 작업한 첫 번째 프로젝트는 Elm 구문, 아키텍처 및 함수형 프로그래밍 패러다임을 배우는 데 사용한 간단한 TypeForm 클론이었습니다. 이 시점에서 이미 유사한 작업을 수행할 수 있을 만큼 충분히 배웠습니다. 열망하는 경우 공식 시작 안내서를 통해 작업하는 것이 좋습니다. Elm의 제작자인 Evan은 실용적인 예를 사용하여 Elm, 구문, 유형, Elm 아키텍처, 확장 등에 대한 동기를 안내합니다.

2부에서는 Elm의 가장 좋은 기능 중 하나인 컴파일러를 사용하여 스텝 시퀀서를 리팩터링하는 방법에 대해 알아보겠습니다. 또한 반복되는 이벤트를 처리하고 부작용에 대한 명령을 사용하고 JavaScript와 상호 작용하는 방법을 배웁니다. 계속 지켜봐 주세요!