전자 모험: 33회: 사건 루트

대부분의 인터넷 응용 프로그램은 아주 간단한 이벤트 시스템을 가지고 있다. 어떤 물건을 클릭하거나, 어떤 분야에 관심을 가지고, 그 안에 내용을 입력하는 것이다.이 이벤트는 구성 요소에만 영향을 주거나, 구성 요소가 부모 구성 요소에 보낼 수도 있습니다.
불행히도, 이것은 우리 파일 관리자에게 아직 충분하지 않다.

파일 관리자 이벤트


이벤트는 여러 개의 원본에서 오고 여러 구성 요소에 영향을 주며 이벤트 유형과 목표 사이에 동적 매핑을 할 수 있습니다.사용자가 새 디렉토리를 만들려면 여러 가지 방법이 있습니다.
  • F7을 누르거나 다른 단축키를 누르면
  • 바닥글의 "F7 Mkdir"버튼
  • 을 클릭
  • 명령 팔레트를 열고 목록에서 새 폴더
  • 를 선택합니다.
  • 애플리케이션 메뉴에서 파일 > 새 폴더 를 선택합니다. Windows에서는 창 위쪽, OSX에서는 화면 위쪽
  • 그런 다음 이벤트가 어떤 방식으로 트리거되든 올바른 활성 패널로 이동해야 합니다.대화 상자가 이미 열려 있으면 이 이벤트를 무시해야 할 수도 있습니다.
    그래서 많은 논리가 있는데, 만약 우리가 그것을 전체 코드 라이브러리에 덮어 씌우면, 그것은 커다란 혼란이 될 것이다.대부분의 사건을 보내고 그 사건을 어떻게 처리할지 결정하는 중심 위치가 있어야 한다.
    이것은 로컬 이벤트를 동시에 가질 수 없다는 것을 의미하지 않는다. 예를 들어 단추를 누르거나 필드에 입력한 내용을 하나의 구성 요소로 관리할 수 있다.
    우리는 Svelte stores, Svelte context, 단순 EventBus 클래스를 사용하여 이 모든 것을 관리할 것입니다.

    단순 이벤트 라우팅 응용


    우리는 그것을 파일 관리자 프로그램에 집적할 것이지만, 먼저 더 작은 것에서 실험을 하는 것이 더욱 쉽다.
    다음은 애플리케이션입니다.
  • 4개의 케이스
  • 키 1-4를 눌러 상자 사이를 전환
  • 선택한 상자
  • 에 a-z 또는 a-z 문자를 입력합니다.
  • 선택한 상자의 마지막 문자를 삭제하려면 체크아웃
  • 수정키의 복잡화를 피하기 위해 나는 F1, F2, F3을 현재 상자의 커팅/복사/붙여넣기 텍스트로 사용할 것이다. 이것은 운영체제 클립보드와 무관하고 내부 내용일 뿐이다
  • F10 종료 어플리케이션
  • 더 좋은 측정을 위해 상자마다 선택
  • 및 바닥글의 모든 클릭 가능 버튼
  • 우리는 잠시 후에 프로그램에 프로그램 메뉴와 명령 팔레트를 추가할 것입니다. 그러나 이미 매우 많습니다.

    src/EventBus。js


    첫 번째 행사 버스.이것은 매우 간단한 자바스크립트 대상이다.실례를 만들고 이벤트 처리 프로그램을 등록합니다.
    이벤트 목표, 이벤트 이름, 임의의 매개 변수를 받아들이는 emit 방법이 있습니다.그것 또한 * 특수 이벤트 처리 프로그램을 처리하여 특정 처리 프로그램이 없는 어떤 이벤트도 처리한다.
    현재 특정한 처리 프로그램이나 적당한 목표가 없는 이벤트를 조용히 삭제하지만, 아마도 우리는 console.log에 경고를 해야 할 것 같습니다.이것은 용례에 달려 있다.
    export default class EventBus {
      constructor() {
        this.callbacks = {}
      }
    
      handle(target, map) {
        this.callbacks[target] = { ...(this.callbacks[target] || {}), ...map }
      }
    
      emit(target, event, ...details) {
        let handlers = this.callbacks[target]
        if (handlers) {
          if (handlers[event]) {
            handlers[event](...details)
          } else if (handlers["*"]) {
            handlers["*"](event, ...details)
          }
        }
      }
    }
    
    그것은 전자나 날씬함에 특정된 것이 아니라 아주 간단한 도안일 뿐이다.

    src/App。날씬한 틀


    먼저 템플릿과 스타일을 살펴보겠습니다. 여기에는 특별한 것이 없기 때문입니다.
    <div class="app">
      <Box id="box-1" />
      <Box id="box-2" />
      <Box id="box-3" />
      <Box id="box-4" />
      <Footer />
    </div>
    
    <Keyboard />
    
    <style>
      :global(body) {
        margin: 0;
      }
      .app {
        background-color: hsl(180,100%,20%);
        font-family: monospace;
        color: #333;
        height: 100vh;
        width: 100vw;
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 1fr 1fr auto;
        gap: 10px;
      }
    </style>
    
    이것은 4개의 상자와 꼬리표가 있는 간단한 격자이다.idHTMLDOMid과는 아무런 관계가 없으며, 실제적으로 모든 상자가 이벤트 시스템에 자신을 표시한다.Keyboard는 DOM을 생성하지 않고 일부 이벤트 처리 프로그램을 메인 창에 추가하는 데 자주 사용되지 않는 구성 요소입니다.

    src/App。섬세한 문자


    이제 재미있는 부분으로 넘어가겠습니다.
    <script>
      import { writable } from "svelte/store"
      import { setContext } from "svelte"
    
      import Box from "./Box.svelte"
      import Footer from "./Footer.svelte"
      import Keyboard from "./Keyboard.svelte"
      import EventBus from "./EventBus.js"
    
      let activeBox = writable("box-1")
      let clipboard = writable("")
      let eventBus = new EventBus()
    
      setContext("app", {activeBox, clipboard, eventBus})
    </script>
    
    우리는 여기에 날씬한 상점 두 개를 만들었다. activeBox 현재 활성 상태인 상자와 clipboard 클립보드의 내용을 표시한다.우리는 또한 EventBus 실례를 만들어서 이벤트 처리 프로그램을 등록할 수 있다.

    컨텍스트 및 스토리지


    그리고 우리는 그것들을 상하문 대상의 키 app 아래에 저장할 것이다.세 가지 다른 컨텍스트를 사용할 수도 있습니다.
      setContext("activeBox", activeBox)
      setContext("clipboard", clipboard)
      setContext("eventBus", eventBus)
    
    이것은 사실 별 차이가 없다. 왜냐하면 우리는 같은 곳에서 그것을 설정하기 때문이다. 그러나 만약 우리가 더 복잡한 응용 프로그램이 있다면 여러 개의 상하문이 필요할 수도 있다.
    왜 우리는 상점을 가치가 아니라 상하문에 두어야 합니까?어셈블리를 작성할 때 컨텍스트를 읽고 자동으로 업데이트되지 않습니다.그래서 이것은 정말 쓸모가 없다.
      let activeBox = "box-1"
      let clipboard = ""
      setContext("app", {activeBox, clipboard, eventBus})
    
    이는 다음과 같은 효과를 가져올 수 있습니다.
      let activeBox = "box-1"
      let activeBoxSubscriptions = []
      function changeActiveBox(newValue) {
        activeBox = newValue
        for (let callback of activeBoxSubscriptions) {
          callback(newValue)
        }
      }
      function subscribeToActiveBoxChanges(callback) {
        activeBoxSubscriptions.push(callback)
      }
      setContext("app", { activeBox, subscribeToActiveBoxChanges, ... })
    
    우리가 기억하기만 하면 activeBox부터 changeActiveBox까지.응, 구성 요소가 파괴되었을 때 구독을 취소할 수 있도록 메커니즘을 추가해야 해.
    이런 구독, 구독 취소, 리셋은 값을 바꾸는 등 조작이 번거롭기 때문에 Svelte는 매장을 단축키로 한다.
    구성 요소의 어느 곳에서든 $activeBox 을 사용하면, Svelte는 자동으로 구독 activeBox 을 시도하고, 이러한 리셋을 통해 업데이트 $activeBox 변수를 업데이트합니다.필요할 때 구독을 취소합니다.
    이 변수는 올바른 반응성 변수이기 때문에 모든 변경 사항이 템플릿에 자동으로 적용되거나, 모든 반응성 문장에 적용됩니다.
    우리가 상하문, 저장, 그리고 EventBus 각종 구성 요소에서 사용하는 예를 통해 더욱 뚜렷해져야 한다.

    src/App。가벼운 이벤트 처리 프로그램


    응용 프로그램에는 두 개의 이벤트 처리 프로그램이 있습니다. quit (F10) 창을 닫고, changeBox 어떤 상자가 활성 상태인지 변경합니다.activeBox.set(id) 저장소를 업데이트한 후 모든 구독자App 구성 요소 자체를 포함하여 특별한 점이 없음)에서 리셋을 실행하고 모든 구독자에 설정$activeBox합니다.
      function quit() {
        window.close()
      }
      function changeBox(id) {
        activeBox.set(id)
      }
      eventBus.handle("app", {quit, changeBox})
    
    그리고 하나 더 해야 할 일은 가상 목표 "activeBox" 에 어댑터 리셋을 등록한 다음 현재 실제 활동 상태의 상자에 다시 보내는 것이다.
      function emitToActiveBox(...args) {
        eventBus.emit($activeBox, ...args)
      }
    
      eventBus.handle("activeBox", {"*": emitToActiveBox})
    

    src/바닥글.날씬하다


    응, 너무 많아.다행히도 프로그램의 나머지 부분은 상당히 간단하다.아래는 바닥글입니다.
    <script>
      import { getContext } from "svelte"
      let { eventBus } = getContext("app")
    </script>
    
    <footer>
      <button on:click={() => eventBus.emit("app", "changeBox", "box-1")}>Box 1</button>
      <button on:click={() => eventBus.emit("app", "changeBox", "box-2")}>Box 2</button>
      <button on:click={() => eventBus.emit("app", "changeBox", "box-3")}>Box 3</button>
      <button on:click={() => eventBus.emit("app", "changeBox", "box-4")}>Box 4</button>
      <button on:click={() => eventBus.emit("activeBox", "cut")}>F1 Cut</button>
      <button on:click={() => eventBus.emit("activeBox", "copy")}>F2 Copy</button>
      <button on:click={() => eventBus.emit("activeBox", "paste")}>F3 Paste</button>
      <button on:click={() => eventBus.emit("app", "quit")}>F10 Quit</button>
    </footer>
    
    <style>
      footer {
        grid-column-start: span 2;
        text-align: center;
      }
      button {
        font-size: 24px;
        font-weight: bold;
        color: inherit;
        background-color: hsl(180,100%,40%);
        font-family: inherit;
      }
    </style>
    
    그것은 상하문에서 eventBus 실례를 얻은 다음, 호출된 각종 단추 eventBus.emit(target, event, arguments) 를 누르기만 하면 된다.
    어떻게 그것을 app 자체 또는 정확한 상자에 전달하는지, 스크립트와 무관하다.

    src/키보드.날씬하다


    <script>
      import { getContext } from "svelte"
      let { eventBus } = getContext("app")
    
      function handleKey({key}) {
        if (key.match(/^[1234]$/)) {
          eventBus.emit("app", "changeBox", `box-${key}`)
        }
        if (key.match(/^[a-zA-Z]$/)) {
          eventBus.emit("activeBox", "letter", key)
        }
        if (key === "Backspace") {
          eventBus.emit("activeBox", "backspace", key)
        }
        if (key === "F1") {
          eventBus.emit("activeBox", "cut")
        }
        if (key === "F2") {
          eventBus.emit("activeBox", "copy")
        }
        if (key === "F3") {
          eventBus.emit("activeBox", "paste")
        }
        if (key === "F10") {
          eventBus.emit("activeBox", "quit")
        }
      }
    </script>
    
    <svelte:window on:keydown={handleKey} />
    
    키보드는 또 다른 순수한 이벤트 원본 구성 요소입니다.이것은 실제적으로 DOM에 어떤 내용도 추가하지 않고 주 window 에 자신을 연결했기 때문에 좀 심상치 않을 수도 있다.
    마찬가지로 상하문에서 eventBus를 얻고 keydown 이벤트를 처리하며 누르는 키에 따라 정확한 목표를 향해 정확한 이벤트를 보냅니다.
    이 구성 요소는 처리 수정 키 (예를 들어 Cmd-C 또는 Ctrl-C) 로 확장될 수 있으며, 사용자가 변경할 수 있도록 플랫폼에 특정한 논리가 필요할 수도 있습니다.심지어vim키 귀속일지도 몰라요.모두 한곳에 있다.

    src/Box。날씬하다


    다른 곳에 이렇게 많은 논리가 있기 때문에 Box 구성 요소는 상당히 간단하다.먼저 템플릿과 스타일:
    <div class="box" class:active on:click={onClick}>
      {text}
    </div>
    
    <style>
    .box {
      font-size: 48px;
      font-weight: bold;
      background-color: hsl(180,100%,30%);
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .box.active {
      background-color: hsl(180,100%,40%);
    }
    </style>
    
    여기는 심상치 않은 것이 없다.우리는 text 변수가true이면 active 클래스가 있고, 클릭하면 active 방법이 호출됩니다.
    <script>
      import { getContext } from "svelte"
      let { eventBus, activeBox, clipboard } = getContext("app")
    
      export let id
      let text = "A"
    
      function onClick() {
        eventBus.emit("app", "changeBox", id)
      }
      function letter(key)  {
        text += key
      }
      function backspace() {
        text = text.slice(0, -1)
      }
      function cut() {
        clipboard.set(text)
        text = ""
      }
      function copy() {
        clipboard.set(text)
      }
      function paste() {
        text = $clipboard
      }
    
      eventBus.handle(id, {letter, backspace, cut, copy, paste})
    
      $: active = ($activeBox === id)
    </script>
    
    우리는 onClick 실례에 긴 사건을 등록했다.이벤트 처리 프로그램은 이곳에서 매우 간단하다.
    여기에 작은 기교가 하나 있는데 eventBus가 변화할 때마다active 표지는 반응적인 변화를 일으킨다.모든 구독과 리셋 등은 스웨트가 처리하기 때문에 우리는 아무것도 할 필요가 없다.

    결실


    결과는 다음과 같습니다.

    나는 그것이 상당히 깨끗한 체계 구조로 코드가 매우 간결하고 (Redux와 같지 않으며) 더욱 복잡한 상황을 처리하기 쉽게 확장된다고 생각한다.
    Svelte 상점과 상하문은 Svelte의 표준 부분이지만, activeBox 단지 내가 이 프로그램을 위해 만든 것일 뿐이다.
    당신은 다른 방식으로 그것을 설계할 수 있습니까?그렇다면 다른 방법을 댓글로 알려주세요.
    다음 회에서 프로그램 메뉴를 추가합니다.
    여느 때와 마찬가지로all the code for the episode is here.

    좋은 웹페이지 즐겨찾기