Svelte 3, XState 및 IntersectionObserver를 사용한 무한 스크롤

28371 단어 sveltexstatewebdev

소개



1년 전에 저는 Svelte 및 IntersectionObserver를 사용하여 무한 스크롤과 관련된 것을 만들었습니다. 이번에는 XState를 사용하여 유사한 프로젝트(두 번째 버전이라고 할 수 있음)를 보여 드리겠습니다.

If you want to try it out go to this Live Demo



프로젝트 폴더 생성



Svelte 템플릿을 사용하려면 터미널에서 이러한 명령을 하나씩 실행하십시오.

# Install a Svelte project using sveltejs/template
$ npx degit sveltejs/template infinite-scroll

# Change working directory
$ cd infinite-scroll

# Install npm dependencies
$ npm install

# Install XState
$ npm install xstate

# Run the dev server
$ npm run dev


구성 요소 생성



src one 안에 components 라는 폴더를 만든 다음 Character.svelte , Footer.svelte , Header.svelte , Loader.svelte 4개의 구성 요소를 만들고 Svelte의 파일 확장자.svelte를 사용해야 합니다.

구성 요소는 기본적으로 이전 게시물과 동일하며 코드here를 찾을 수 있습니다.

유틸리티 함수 생성



src 1 안에 lib라는 폴더를 만든 다음 index.js 파일을 만듭니다. 따라서 우리의 삶을 더 쉽게 만들기 위해 몇 가지 util 함수를 구현해 보겠습니다.

export const transformCharacter = character => {
  const {
    id,
    name,
    status,
    species,
    created,
    image,
    gender,
    origin,
    location,
  } = character
  return {
    id,
    name,
    image,
    status,
    species,
    gender,
    created,
    origin: origin.name,
    location: location.name,
  }
}

// Format created date.
export const relativeTime = created => {
  const createdYear = new Date(created).getFullYear()
  const currentYear = new Date().getFullYear()
  const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
  return rtf.format(createdYear - currentYear, 'year')
}

// Util function to fetch the list of Characters.
export const fetchCharacters = async (context) =>  {
try {
    const endpoint = context.characters.length > 0 ? context.pageInfo.next : 'https://rickandmortyapi.com/api/character'
    const blob = await fetch(endpoint)
    const { results, info, error } = await blob.json()
    if (!blob.ok) {
      return Promise.reject(error)
    }
    const characters = results.map(result => transformCharacter(result))
    return Promise.resolve({ characters, info })
  } catch (error) {
    return Promise.reject(error.message)
  }
}


상태 머신 생성



lib 폴더에 scrollMachine.js 라는 파일을 만들고 XState를 사용하여 상태 머신을 구현해 보겠습니다.

import { createMachine, assign } from 'xstate'

const scrollMachine = createMachine({
  id: 'scrollMachine',
  context: {
    characters: [],
    pageInfo: {},
    error: '',
  },
  initial: 'idle',
  states: {
    idle: {
      on: {
        FETCH: {
          target: 'loading',
        },
      },
    },
    loading: {
      invoke: {
        id: 'characters',
        src: 'fetchCharacters',
        onDone: {
          target: 'success',
          actions: 'setCharacters',
        },
        onError: {
          target: 'failure',
          actions: 'setError',
        },
      },
    },
    loadMore: {
      invoke: {
        src: 'fetchCharacters',
        onDone: {
          target: 'success',
          actions: 'setMoreCharacters',
        },
        onError: {
          target: 'failure',
          actions: 'setError',
        },
      },
    },
    success: {
      on: {
        FETCH_MORE: {
          target: 'loadMore',
          cond: 'hasMoreCharacters',
        },
      },
    },
    failure: {
      type: 'final',
    },
  },
}, {
  guards: {
    hasMoreCharacters: ({ pageInfo }) => pageInfo.next,
  },
  actions: {
    setCharacters: assign({
      characters: (_, event) => event.data.characters,
      pageInfo: (_, event) => event.data.info,
    }),
    setMoreCharacters: assign({
      characters: ({ characters }, { data }) => [...characters, ...data.characters],
      pageInfo: (_, { data }) => data.info,
    }),
    setError: assign({
      error: (_, event) => event.data,
    }),
  },
})

export default scrollMachine


앱 구성 요소 업데이트




<script>
  import { onMount, onDestroy } from 'svelte'
  import { interpret } from 'xstate'
  import { fetchCharacters } from './lib'
  import scrollMachine from './lib/scrollMachine'
  import Header from './components/Header.svelte'
  import Loader from './components/Loader.svelte'
  import Character from './components/Character.svelte'
  import Footer from './components/Footer.svelte'

  // Let's pass the fetchCharacters function
  // as a service to our state machine.
  const machine = scrollMachine.withConfig({
    services: { fetchCharacters },
  })

  // Interpret our machine and start it ("create an instance").
  const service = interpret(machine).start()

  // Create options to our IntersectionObserver instance.
  let options = {
    root: document.getElementById('scrollArea'),
    rootMargin: '0px',
    threshold: 0.5,
  }

  // Handle intersection and send `FETCH_MORE` event to
  // our state machine if there are more characters to load.
  let observer = new IntersectionObserver(event => {
    const [entries] = event
    if (!entries.isIntersecting || !$service.context.pageInfo.next) {
      return
    }
    service.send({ type: 'FETCH_MORE' })
  }, options)

  // Fetch characters on component mounts.
  onMount(() => {
    service.send({ type: 'FETCH' })
    observer.observe(document.querySelector('footer'))
  })

  // Remove observer from our target to avoid potential memory leaks.
  onDestroy(() => {
    observer.unobserve(document.querySelector('footer'))
  })
</script>

<style>
  .container {
    min-height: 50vh;
    background-color: var(--text-color);
  }
  .inner {
    max-width: 80em;
    margin: 0 auto;
    padding: 3rem 0;
    display: grid;
    grid-gap: 20px;
    justify-items: center;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  }
  .loader,
  .error {
    padding-top: var(--padding-lg);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .error {
    color: var(--orange-color);
    font-size: 18px;
  }
</style>

<Header />
<section class="container" id="scrollArea">
  {#if $service.matches('success') || $service.matches('loadMore')}
    <div class="inner">
      {#each $service.context.characters as character (character.id)}
        <Character {character} />
      {/each}
    </div>
  {/if}
  {#if $service.matches('failure')}
    <div class="error"><span>{$service.context.error}</span></div>
  {/if}
  {#if $service.matches('loading') || $service.matches('loadMore')}
    <div class="loader">
      <Loader />
    </div>
  {/if}
  <Footer />
</section>


메모:
Rick and Marty API 문서는 다음에서 찾을 수 있습니다. here
GitHub 저장소: here
유튜브 비디오(스페인어):

즐거운 코딩 👋🏽

좋은 웹페이지 즐겨찾기