Svelte 기본

김정욱·2021년 11월 19일
1

svelte

목록 보기
1/1
post-thumbnail

svelte

[ 사용 추세 ]

  • 출처 : State of JS 2020 조사
  • 2019년 3.0 이 release 되면서 급부상
  • (추가)IE 지원 X -> 추가적인 폴리필등으로 보완 가능

[ 제공 템플릿 ]

  • sveltejs/template
    • rollup 이라는 번들러를 기본으로 사용하는 템플릿
    • svelte는 rollup 번들러를 통해서 생태계를 구성
  • sveltejs/template-webpack
    • webpack 번들러를 사용하는 템플릿
    • 굳이 rollup 번들러를 알고 싶지 않으면 그냥 사용해도 무방할 듯

[ 기존 방식과 차이점 ]

[ 렌더링 전략 ]

  • 가상 DOM (react, vue)
    • 런타임 시점DOM Tree에 대한 스냅샷을 만들고 diffing 과정을 수행 해서 변경된 부분렌더링이 수행
    • 수행 시점 : 런타임
  • svelte 컴파일러
    • 컴파일 시점에 어떤 부분이 변경될 지 미리 파악을 해서 컴팩트한 JS 파일로 변환 후 렌더링
      => Virtual DOM 생성diffing으로 인한 overhead 가 없음
      => vanillaJS로 변환되 때문에 devDependencies로 의존
    • 수행 시점 : 빌드 타임 (정확히는 컴파일 시점)

[ 변경 감지 방식 ]

  • react : setState 함수 호출 통해서 값이 변경됨을 감지
  • svelte
    • svelte 컴파일러가 변수 대입문 코드상태 변경 감지 함수를 호출하는 코드로 변환
    • 그래서, 값을 대입하는 것 만으로도 변경 감지 & 렌더링이 수행
    • 추가적으로 템플릿 엔진 코드컴파일해서 컴팩트한 JS 번들로 변환

[ state 관리 ]

svelte의 상태(state) 관리 방법

  • props / Context API / Store 3가지 방법이 존재

[ 비교 ]

  • 범위
    • Context API : 하위 컴포넌트 관계에서만 state 관리 가능
    • Store : 전역으로 state 관리 가능
  • 반응성
    • Context API : 반응형으로 동작하지 X -> 즉, 추가적인 반응성 구문 코드가 필요
    • Store : store값의 state갱신되면 subscribe 하고있는 모든 컴포넌트 리렌더링 수행

[ 선택 ]

  • 가까운 컴포넌트 간 : props를 통해 관리
  • 전역 state 관리 : svelte의 내장store를 사용

[ 핵심 문법들 ]

[ props ]

/* Button.svelte */
<script>
    import LayerdButton from './LayerdButton.svelte'

    let content = ''
    let isClicked = false
    const onHandlerClick = () => isClicked = !isClicked
   
    $: content = isClicked ? "to close" : "to open"
</script>

<LayerdButton {onHandlerClick} {content} />
/* LayeredButton */
<script>
    // export로 props를 받아올 수 있다
    export let onHandlerClick;
    export let content;
</script>

<button on:click={onHandlerClick}>{content}</button>
  • Button -> LayerdButton 컴포넌트로 변수와 함수를 props로 이동
  • 반응성 구문$: 를 통해 반응에 대한 변화를 지정해주면 연관된 값도 변경된다
    • (장점) svelte에서는 반응성 구문 $: 을 통해서 함수변수에 자유롭게 반응 관계를 만들 수 있다

[ bind ]

  • svelte 장점 중 하나가 바로 bind:키워드를 통해서 양방향 바인딩을 쉽게 할 수 있는 것
    • bind:this : dom element와 양방향 바인딩
    • bind:value : input 태그에서 value와 양방향 바인딩
    • bind:checked : 체크박스에서 배열 state와 양방향 바인딩
    • bind:group : 라디오 타입에서 배열 state와 양방향 바인딩
  • bind:this
    • document.querySelector() 대신, bind:this로 바로 특정 dom에 접근 및 사용 가능
    • REPL 예시
<script>
	import { onMount } from 'svelte';

	let canvasElement;
	onMount(() => {
		const ctx = canvasElement.getContext('2d');
		drawStuff(ctx);
	});
</script>
    /* bind:this 를 통해 dom에 직접 연결 */
<canvas bind:this={canvasElement}></canvas>

[ 반응성 ]

  • .push() / .splice() 같은 메소드 사용은 반응성을 갱신할 수 없다
    • 물론 사용된 원본 값은 바뀌겠지만, svelte는 대입문 = 을 통해 반응성을 반영하기 때문에 리렌더링 X
    • 특정 object에서 여러개 변경이 있을 때 하나라도 = 로 재할당되면 전체적으로 반응성이 반영됨
      -> 반응성 코드로 바뀌면서 객체 전체를 할당하기 때문
    • REPL 예시
  • 반응성 구문인 $: 은 반응성을 계측하는 것일 뿐, 즉각 반영은 아님
    • 조금의 시간 차이가 있어서 연이은 작업이 있을 때 오류가 생길 수 있다
    • 그래서, sveltelifecycle중 하나인, tick 을 이용해서 반응성 반영보장받을 수 있도록 함께 사용
    • REPL 예제

[ if ~ else ]

  {#if count > 3}
    <div>count &gt; 3</div>
  {:else if count === 3}
    <div>count === 3</div>
  {:else}
    <div>count &lt; 3</div>
  {/if}

[ for ]

  • 일반 템플릿 엔진의 반복문과 크게 다르지 않으나, svelte 형식이 다양하다는 것차이점
  • 반복 데이터들에 key값을 할당해서, 변경된 항목만 효율적으로 변경하도록 해줘야 한다
    () 괄호를 이용해서 키(key)를 지정
  • 그 외 다양한 형식의 반복문이 존재
  • REPL 예시
<script>
  let fruits = [
    { id: 1, name: 'Apple' },
    { id: 2, name: 'Banana' },
    { id: 3, name: 'Cherry' },
    { id: 4, name: 'Apple' },
    { id: 5, name: 'Orange' }
  ]
</script>

<section>
  <h2>아이템 고유화(key)</h2>
  <!-- {#each 배열 as 속성, 순서 ()} {/each} -->
  {#each fruits as fruit, index (fruit.id)}
    <div>{index} / {fruit.name}</div>
  {/each}
</section>

[ Event Dispatcher ]

  • 자식에서 부모로 데이터(이벤트)를 전달하는 방법
  • on:이벤트 이름 으로 잡아서 처리할 수 있음
  • 이벤트의 핸들러를 명시하지 않으면 Event Forwarding이 수행됨 (그냥 지나감)
  • REPL 예시
/* Child.svelte */
<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()
  const sendData = '나는 데이터!'
  dispatch('childSendEvent', sendData)
</script>


/* parent.svelte */
<script>
  import Child from './Child.svelte'
</script>
<Child on:childSendEvent={event => {
  console.log(event.detail) // '나는 데이터!' 
}} />

[ writable Store (읽기/쓰기 스토어) ]

  • store 종류
    • writable store
      • 값을 읽고 / 저장할 때 사용
      • 기본 제공 api : set() / update() / subscribe()
      • writable(값, 콜백) 형태를 취하며, 자동이든 수동이든 구독할 때 수행될 콜백 지정 가능
    • readable stroe
      • 값을 읽기만 할 때
    • 존재하는 store로 새로운 store를 만들 때 : Derived store 사용
    • Store docs
  • 사용 방법
    • 수동 구독 : 직접 subscribe하는 코드와 자원을 해제하는 코드를 직접 작성
    • 자동 구독 : 변수 앞에 $를 붙여서 자동으로 구독과 해제를 하도록 해주는 설정
    • 구독 없이 값 읽기 : svelte/store에 있는 get은 구독하지 않고 store의 객체만 얻는 방법이다
/* store.js
   읽고 쓸 수 있는 wirtable store를 사용 */
import { writable } from 'svelte/store';

export const count = writable(0); // 초기값 지정
/* App.svelte */
<script>
  import { count } from './stores.js';
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';
</script>

  /* 변수 앞에 $ 를 붙여 구독 / 해제 과정을 자동으로 수행 */
<h1>The count is {$count}</h1>
/* Incrementer.svelte */ 
<!-- Incrementer.svelte -->
<script>
  import { count } from './stores.js';

  /* update 함수를 통해 값 변경 */
  function increment() {
    count.update(n => n + 1);
  }
</script>

<button on:click={increment}>
  +
</button>

[ Derived Store (계산된 스토어) ]

  • 쓰기가능(writable) 하거나 읽기 전용(readable) 스토어를 통해 새롭게 계산한 값을 가지는 스토어를 생성
  • 사용 패턴이 많아 헷갈릴 수 있지만, derived 매개변수 3개와 콜백이 가지는 매개변수를 이해하면 쉽다
  • REPL 예시
/* derived 의 파라미터 */
derived(스토어, 콜백)
derived(스토어, 콜백, 초깃값)
derived([스토어1, 스토어2], 콜백)
derived([스토어1, 스토어2], 콜백, 초깃값)

/* 2번째 파라미터인 콜백의 파라미터
   => 설명의 편의를 위해 함수로 선언, 사용할때는 화살표 함수 이용하면 편함 */
function ($스토어) { // 통상적으로 스토어의 값을 나타내는 매개변수에 $를 붙여서 표기
  // 계산..
  return 계산된_값
}
function ([$스토어1, $스토어2]) {
  // 계산..
  return 계산된_값
}
// 콜백의 2번째 파라미터인 set을 쓰면, 모든 구독이 취소되었을 때 실행할 함수를 사용하겠다는 뜻
// 즉, 구독 취소를 안쓰면 set을 쓰지 않고, return으로 derived 스토어의 값을 할당하면 된다
// 보통의 경우에 set을 잘 쓸일은 없으니 return으로 값 할당 하자
function ($스토어, set) { 
  // 계산..
  set(계산된_값)
  return 구독이_모두_취소되면_실행할_함수
}

[ use 지시어 ]

  • use 지시어를 통해 연결된 dom이 생성될 때 호출할 함수를 지정할 수 있다
  • DOMContentLoad 나 load 와 같은 리스너와 비슷하지만 더 다양한 기능 제공
  • 해당 요소(dom)를 사용하는 플러그인(모듈)을 제작할 때 유용
/* 사용 */
<요소 use:함수이름></요소>
<요소 use:함수이름={인수}></요소>

/* dom이 생성될 때 실행될 함수 */
function 함수이름(요소, 인수) {
  // Logic..
  return {
    update(인수) {}, // '인수'가 변경되면 실행됩니다.
    destroy() {} // '요소'가 제거되면 실행됩니다.
  }
}

[ svelte의 lifecycle ]

  • 5가지 기본적인 lifecycle을 가진다
    • onMount : 컴포넌트가 돔에 마운트 되면 실행
    • onDestroy : 컴포넌트가 해제된 후 실행
    • beforeUpdate : 컴포넌트가 마운트 되기 전 실행
    • afterUpdate : 컴포넌트가 마운트 된 후 실행
    • tick : 컴포넌트 변경이 완료되면 실행
  • 전체적인 실행 순서
    • beforeUpdate -> onMount -> afterUpdate -> onDestroy
  • 부모와 자식 사이의 생명주기
    • 부모의 beforeUpdate 이후 onMount 사이에 자식의 모든 라이프사이클이 수행
import { onMount, onDestroy, beforeUpdate, afterUpdate, tick } from 'svelte'
...
onMount(async() => {
	console.log('App onMount')
})

onDestroy(async() => {
	console.log('App onDestroy')
})

beforeUpdate(async() => {
  console.log('App beforeUpdate') 
})

afterUpdate(async() => {
	console.log('App afterUpdate')
})

[ head / body ]

  • 현재 문서의 head값인 document.head 를 통해 정보 요소를 삽입해준다
  • document.body 에 특정 이벤트를 추가할 수 있다
/* 외부 script 추가 */
<svelte:head>
  <script src="https://js.stripe.com/v3/" on:load={stripeLoaded}{></script>
<svelte:head>

<svelte:body on:mousemove={e => console.log(e.clientX, e.clientY)} />

[ 부가적인 문법들 ]

[ $$props / $$restProps ]

  • $$props : 컴포넌트가 전달받는 모든 Props의 정보를 가진 객체
  • $$restProps : 명시한 props 제외한 나머지 다룰 수 있는 객체

[ key 블록 ]

  • 특정 state에 따른, 컴포넌트 초기화 및 리렌더링 수행
<script>
  import Count from './Count.svelte'
  let reset = false
</script>

{#key reset}
  <Count />
{/key}

<button on:click={() => reset = !reset}>
  Reset!
</button>

[ 비동기 블록 ]

<input bind:value={title} />
<button on:click={() => promise = searchMovies()}>검색!</button>

{#await promise}
  <!-- pending(대기) -->
  <p style="color: royalblue;">loading...</p>
{:then movies}
  <!-- fulfilled(이행) -->
  <ul>
    {#each movies as movie}
      <li>{movie.Title}</li>
    {:else}
      <li>검색된 결과가 없어요...</li>
    {/each}
  </ul>
{:catch err}
  <!-- rejected(거부) -->
  <p style="color: red;">{err.message}</p>
{/await}

[ 다중 이벤트 핸들링 / 이벤트 수식어 ]

  • 한 요소에 같은 이벤트를 여러개 연결할 수 있음
  • svelte에서는 DOM 이벤트를 위한 이벤트 수식어를 제공
    -> | 기호를 이용해서 체이닝 가능
  • 자세한 예제
<a 
  href="#" 
  on:click|preventDefault={() => console.log('link!')}>
  Internal link..
</a>

<div on:click|preventDefault|capture|self|once={() => console.log('!')}>
  Chaining..
</div>

[ svelte:component ]

  • 컴포넌트를 동적으로 렌더링할 때 사용
  • this 속성에 컴포너트 객체를 연결
  • 컴포넌트 이름과 값을 매핑한다면 쉽게 선택한 특정 컴포넌트를 렌더링 할 수 있다
  • <svelte : component />
<script>
  import Heropy from './Heropy.svelte'
  import Neo from './Neo.svelte'
  import Anderson from './Anderson.svelte'

  let components = [
    { name: 'Heropy', comp: Heropy },
    { name: 'Neo', comp: Neo },
    { name: 'Anderson', comp: Anderson }
  ]
  let index = 2
  let selected = components[index - 1].comp
</script>

{#each components as {name, comp}, i (name)}
  <label>
    <input 
      type="radio" 
      value={comp} 
      bind:group={selected}
      on:change={() => index = i + 1} />
    {name}
  </label>
{/each}

/* 선태된 값이 곧 컴포넌트 이름이기 때문에 쉽게 동적 컴포넌트 렌더링 가능 */
<svelte:component 
  this={selected} 
  {index} />

[ svelte:options - immutable ]

  • 객체나 배열같은 것들은 일부분만 변경되어도, svelte 특성상 전체가 재할당 되어서 불필요한 재 랜더링이 발생할 수 있다
  • options를 통해서 불변성 선언을 하면 실제로 값이 변경된 부분만 재 렌더링을 수행하게 할 수 있다
  • 컴포넌트가 전달받은 props의 데이터 불변성을 선언
  • REPL 예시
/* 기존 props와 새로운 props를 비교해서 값이 다르면 immutable값이 false가 되어 갱신 수행 */
<svelte:options immutable={true} />
<svelte:options immutable />

[ svelte:options - accessors ]

  • 외부에서 컴포넌트의 데이터 혹은 함수에 접근을 허용할 때 사용하는 기능
  • 허용할 데이터가 함수에 export를 한 뒤, <svelte:options accessor /> 선언
  • 이 설정 후, 컴포넌트에 bind:this를 통해서 특정 변수를 지정하면, 함수나 변수에 접근할 수 있다
  • REPL 예시
<svelte:options accessors={true} />
<svelte:options accessors />

svelte 모듈 return 방법

[ 우리가 채택한 방법 ]

  • Element를 반환하는 방법
    • target빈 div 생성 후, 인스턴스 생성 후 직접 dom 꺼내서 반환(return)
import testComponent from './components/SocialShare.svelte'

const SocialShare = new SocialShare({
	target : document.createElement('div'),
}).$$.root.firstChild;
export {
	SocialShare
};

[ 그 외 방법 ]

  • 특정 target element 요소추가하는 방법
    • 모듈을 사용하는 사용자특정 target element생성해둔 뒤, 우리의 모듈import하면 자동으로 그 위치 아래삽입되는 방식
    • svelte 컴포넌트 객체 생성시 target : docmunet.querySelector(~) 로 타겟 지정
      -> 실패 : target을 찾지 못하는 오류 발생하는 것을 보니 rendering issue(추측) ?
  • 특정 target element 요소대체하는 방법
profile
Developer & PhotoGrapher

0개의 댓글