Electron Adventures: 에피소드 84: 고성능 16진수 편집기

61~69화에서 헥스 에디터를 만들었는데, 큰 파일을 다룰 때 상당히 느렸습니다.

그럼 우리가 에피소드 69에서 했던 것부터 시작해서 정말 빠르게 만들어 봅시다.

성능 문제



Hex Editor의 성능 스토리는 두 부분으로 나뉩니다.

처음에는 앱이 모든 행에 대해 DOM을 생성했기 때문에 시작 속도가 매우 느렸지만 이후에는 업데이트가 더 이상 필요하지 않아 매우 순조로웠습니다.

변경 후 앱은 모든 행에 대해 빈 자리 표시자 DOM 항목을 만든 다음 스크롤이 발생할 때마다 데이터를 표시하는 데 필요한 행(화면)과 비어 있을 수 있는 행(화면 외부)을 확인했습니다. 초기 렌더링은 훨씬 빨랐지만 여전히 놀랍지는 않았습니다. 이제 Svelte가 업데이트에 필요한 앱을 파악해야 했기 때문에 스크롤 속도가 느렸습니다.

새로운 솔루션



음, 그런데 왜 자리 표시자 요소를 만드는 데 신경을 써야 할까요? 그래서 여기에 새로운 아이디어가 있습니다. 모든 요소에 맞게 컨테이너 크기를 늘린 다음 필요한 요소만 만듭니다. 구현을 단순화하기 위해 모든 행의 높이를 16px로 설정했습니다.

src/Slice.svelte




<script>
  import { printf } from "fast-printf"
  import AsciiSlice from "./AsciiSlice.svelte"

  export let offset
  export let rowNumber
  export let data
</script>

<div class="row" style={`top: ${16*rowNumber}px`} class:even={rowNumber % 2}>
  <span class="offset">{printf("%06d", offset)}</span>
  <span class="hex">
    {#each {length: 16} as _, i}
      <span data-offset={offset + i}>
        {data[i] !== undefined ? printf("%02x", data[i]) : "  "}
      </span>
    {/each}
  </span>
  <AsciiSlice {data} />
</div>

<style>
  .row {
    position: absolute;
    width: 100%;
    height: 16px;
  }
  .even {
    background-color: #555;
  }
  .offset {
    margin-right: 0.75em;
  }
  .hex span:nth-child(4n) {
    margin-right: 0.75em;
  }
</style>


몇 가지만 변경하면 되었습니다.
  • 전체if visible 논리
  • 를 제거했습니다.
  • 모든 행은 rowNumber(지금은 항상 offset/16이지만 둘 다 전달하는 것이 더 논리적으로 보입니다)
  • 행은 16픽셀이고 절대적으로 rowNumber에 따라 배치됩니다.
  • even/odd 논리를 수행하기 위해 CSS에 의존할 수 없습니다. 처음 실제로 보이는 요소가 홀수인지 짝수인지 알 수 없기 때문에 .even 클래스를 직접 관리해야 합니다
  • .

    src/MainView.svelte




    <script>
      import Slice from "./Slice.svelte"
      import { createEventDispatcher } from "svelte"
    
      export let data
    
      let dispatch = createEventDispatcher()
      let slices
      let main1
      let main2
      let firstVisible = 0
      let lastVisible = 200
    
      $: {
        slices = []
        for (let i=0; i<data.length; i+=16) {
          slices.push({
            rowNumber: i/16,
            offset: i,
            data: data.slice(i, i+16),
          })
        }
      }
    
      $: visibleSlices = slices.slice(firstVisible, lastVisible+1)
      $: totalHeight = `height: ${16*slices.length}px`
    
      function onmouseover(e) {
        if (!e.target.dataset.offset) {
          return
        }
        dispatch("changeoffset", e.target.dataset.offset)
      }
    
      function setVisible() {
        let rowHeight = 16
        firstVisible = Math.floor(main1.scrollTop / rowHeight)
        lastVisible = Math.ceil((main1.scrollTop + main1.clientHeight) / rowHeight)
        main2.focus()
      }
    
      function init1(node) {
        main1 = node
        setVisible()
      }
      function init2(node) {
        main2 = node
      }
    </script>
    
    <div
      class="main1"
      on:scroll={setVisible}
      use:init1
      >
      <div
        class="main2"
        on:mouseover={onmouseover}
        style={totalHeight}
        use:init2
        tabindex="-1"
      >
        {#each visibleSlices as slice (slice.offset)}
          <Slice {...slice} />
        {/each}
      </div>
    </div>
    
    <svelte:window on:resize={setVisible} />
    
    <style>
      .main1 {
        flex: 1 1 auto;
        overflow-y: auto;
        width: 100%;
      }
      .main2 {
        position: relative;
      }
    </style>
    


    아마도 가장 깔끔한 코드는 아닐 것입니다. 외부main1 스크롤 가능 뷰포트 div(사용 가능한 공간에 맞게 크기가 조정됨)와 내부main2 div 크기가 모든 행에 맞도록 조정되었습니다.

    여기에 몇 가지 트릭이 있습니다. 내부tabindex="-1"main2를 추가하고 스크롤할 때마다 계속 실행main2.focus()해야 합니다. 그렇지 않으면 키보드 탐색이 작동하지 않습니다. 이전 버전에서 초점이 맞춰진 것은 개별 행이었지만 이제는 삭제하므로 초점을 main2로 이동하는 대신 완전히 제거합니다. 초점을 강제로 유지하면main2 키보드 탐색이 작동합니다. 이것은 가장 우아한 솔루션은 아니지만 선택할 수 있는 다른 것이 없으므로 작동합니다. 더 복잡한 앱에서는 삭제될 행에 속한 경우에만 포커스를 훔쳐야 합니다.
    {#each visibleSlices as slice (slice.offset)} 로 반복할 때 루프 인덱스 대신 slice.offset 로 행을 식별하도록 Svelte에 지시해야 합니다. 그렇지 않으면 AsciiSlice 구성 요소가 지금처럼 생성 시에만 데이터를 계산하는 대신 매번 데이터를 다시 계산하도록 지시해야 합니다.

    물론 main2 구성 요소 중 position: relative 가 기본 창이 아니라 position: absolute 를 기반으로 한다는 것을 브라우저에 알리려면 Slicemain2 로 태그 지정해야 합니다.

    결과



    결과는 다음과 같습니다.



    다음 에피소드에서 우리는 몇 가지 게임을 작성합니다.

    평소와 같이 all the code for the episode is here .

    좋은 웹페이지 즐겨찾기