Svelte

kirin.log·2021년 9월 15일
2

🪴 Svelte 란?

반응형 App 개발을 위한 새로운 접근법으로서, interactive한 유저 인터페이스를 구축한다는 데 있어 React나 Vue와 같은 JavaScript 프레임워크, 라이브러리와 비슷하다.

그러나 기타 프레임워크(라이브러리)와 다른 차이점은,
(1) 앱을 실행 시점(Run time)에 해석하는 것이 아니라 빌드 시점(Build time)에 JavaScript로 변환한다. 즉, 프레임워크 추상화를 위한 퍼포먼스에 소모적이지 않다는 뜻이다. 그리고 앱의 첫 로드 시에도 빠른 구동이 가능하다.

❗️ Svelte는 Reactive web app(변동되는 데이터의 상태를 실시간으로 브라우저에 반영해주는 Web app)과 interface를 만드는 데 사용되는 Compiler이다.

ex) 오늘 날씨를 보여주는 앱 (에 날씨 정보를 상태 값으로 저장한 후 날씨가 바뀔 때마다 상태 값이 업데이트될 것이고, 날씨가 바뀌어 상태 값이 변경되는 즉시 브라우저에 반영해주어야 한다.)

(2) Virtual DOM이 없다. 실제 DOM에 변경 사항을 적용하기 전에 diff를 비교하고 컴포넌트를 그리기 위한 시간을 소비하는 과정을 모두 생략하고 바로 실제 DOM을 변경한다. 또한 어떤 요소에 변화가 일어나는 지 알고 있기 때문에 빠르게 타겟 요소만 변경할 수 있다.
즉, Svelte는 가상 DOM을 사용하지 않고도 React와 비슷한 프로그래밍 모델을 사용할 수 있도록 만들었고, 그 덕분에 매우 빠른 성능적 이점이 있다.

❗️ Virtual DOM
앱의 상태가 업데이트되는 매 순간마다 새로운 가상 DOM 객체가 만들어진다. 프레임워크의 업무는 실제 DOM과 가상 DOM을 비교해서 진짜 필요한 변화만 실제 DOM에 적용하는 것이다.
대부분의 DOM operation은 매우 느리기 때문에 가상 DOM을 이용하여 필요할 때만 실제 DOM을 업데이트하는 방식이 매우 빠르다.

전체 앱을 Svelte로 구축할 수도 있고, 이미 존재하는 코드 베이스에 점진적으로 적용할 수도 있다. 또한 전통적인 프레임워크의 dependency(컴포넌트의 의존성)와 상관없이 어디서든 사용할 수 있는 독립적인 컴포넌트로 만들어 사용할 수도 있다.

Svelte는 SPA(Single Page Application) 전체를 개발하거나 혹은 페이지의 작은 일부분을 개발할 때 사용 가능하다.


🪴 초기 세팅 과정

npx degit sveltejs/template svelte-app
cd my-proejct

npm install

npm run dev // 실행 명령어
(yarn dev)

🪴 Svelte 템플릿의 기본 폴더 구조

- public/
   - build/
      - bundle.js
      - bundle.css
   - index.html
   - global.css
- src/
   - App.svelte
   - main.js
- package.json
- package-lock.json
- rollup.config.js
  • svelte로 작성한 파일들은 하나의 컴포넌트 단위이며, 여러 개의 svelte컴포넌트를 모아서 실제 DOM에 주입하는 방식으로 페이지가 구성된다.
  • public은 마지막 production 코드와 같이 번들링된 Output파일들이 들어갈 곳이다.
    (즉, 배포를 위한 파일들이 들어간다.)
  • src는 svelte컴포넌트 파일들을 묶어서 document.body에 넣어줄 main.js파일이 들어간다.

🪴 Target & Props

main.js파일은 이 앱의 가장 첫 시작파일이다. (React에서 src/index.js파일과 같은 역할)
즉, 가장 최초로 구동되어 세팅되는 파일.

-src/main.js

import App from './App.svelte'

const app = new App({    // 이 컴포넌트의 새로운 instance 객체를 만든다
   target: document.body,   // target 과 props 설정
   props: {
       name: 'word'
   }
});

export default app;
  • target은 이 컴포넌트를 DOM 또는 HTML document에서 정확히 어디에 주입하고 싶은지를 설정하는 property이다.
    위 예제에서는 document의 body 태그를 target으로 설정하고 있다.
    (원하면 Document.querySelector() 등을 이용해 다른 dom을 select할 수도 있다.)

main.js는 결과적으로 index.htmldocument 태그를 셀렉하여 모든 App 템플릿을 주입하는 일을 하고 있는 것이다.

public/index.html이 바로 이 모든 앱을 브라우저에 서빙하는 역할을 하는데, 브라우저의 개발자 도구로 살펴보면 body 태그에 main 태그가 주입된 것을 확인할 수 있다.

❗️ 가장 중요한 포인트는, svelte로 생성되는 컴포넌트들이 main.js 파일에서 target 으로 설정된 DOM에 주입된다는 사실이다.
그러나 앱의 규모가 커져서 많은 컴포넌트들이 생기면 이런 방식으로 DOM에 주입하지 않는다.
top-level 컴포넌트인 App.svelted 컴포넌트에 여러 컴포넌트들을 중첩한 후 이 APP 컴포넌트 하나만 DOM 에 직접 주입한다.

🪴 간단한 문법 예시

 // App.svelte

<script>
    export let name;  // main.js에서 받아온(export)한 name값 
    let color = 'black'; 

    const handleClick = () => {
	color = 'blue';
    };

    const handleInput = (e) => {
	color = e.target.value;
    };
</script>

<main>
    <h1>Hello {name}!</h1>  // Hello world
    <p>{color} jean!</p>    // black jean!

    <button on:click={handleClick}>update jean color</button> 
    // 버튼 누르면 위의 black이 blue로 바뀜

    <input type='text' on:input={handleInput} />
    // input안에 타이핑한 값(value)이 color값(value)로 실시간으로 들어간다
</main>
  • click이벤트 : on:click={ 함수 }
  • inline이벤트 : on:input={ 함수 }

❗️ 위 예시에서는 단방향 데이터 처리 로만 이루어져 있다.

즉, input value(=yellow)가 color 변수의 값에 반영되기는 하나(=yellow jean!),
버튼을 누르면 color 값은 다시 버튼의 on:click 이벤트가 처리하는 값(=blue)로 바뀐다(=blue jean!).
이 때, input창에는 yellow라는 값이 그대로 있다.(color의 값이 변경되어도 input의 value에는 반영이 안되는 단방향 데이터 처리)

만약에 사용자가 버튼을 클릭했을 때 input에 쓰여진 yellow가 blue로 바뀌게하고 싶다면 어떻게 해야 할까?

👉 Two Way Data Binding
bind:value

<script>
    export let name;  // main.js에서 받아온(export)한 name값 
    let color = 'black'; 

    const handleClick = () => {
	color = 'blue';
    };

    // const handleInput = (e) => {
	// color = e.target.value;
    // };
</script>

<main>
    <h1>Hello {name}!</h1>  // Hello world
    <p>{color} jean!</p>    // black jean!

    <button on:click={handleClick}>update jean color</button> 

    <input type="text" bind:value={color} />
    // value값을 bind:value 로 묶어주었다.
    // value 는 color 이고, color가 변동될때마다 자동으로 변환되며, 
    // input에서 타이핑하는 값(value)도 color 에 반영된다.
    // 즉, handleInput 함수가 필요 없게 된다
</main>

bind:value 를 통해 동적인 Data binding이 가능하다

$: Reactive value / 자동 구독 기능

❗️ js 파일에는 $: 기능이 적용되지 않는다. (ex - store.js / connector.js) 👉 대신 subscribe를 이용한다(값의 변경을 관찰하기 위해)
❗️ 오직 svelte 파일 내에서만 사용 가능한 기능이다. subscribe 기능을 대신 한다. 자동으로 구독되어 변경된 값을 바로바로 반영해준다.

 // App.svelte

<script>
    let firstName = 'Jimi';
    let lastName = '';
    let color = 'black';

    // Reactive value - 실시간 반영
    $: fullName = `${firstName} ${lastName}`;

    // Reactive statement - 실시간 반영
    $: console.log(color); 
    $: {
	 console.log(color);
	 console.log(fullName);
       }
</script>

<main>
    <p>{fullName} - {color} jean!</p>  // Jimi Hendrix - black jean!
    <input type='text' bind:value={firstName} />
    <input type='text' bind:value={lastName} />
    <input type='text' bind:value={color} />
</main>

$: 문법과 함께 원하는 변수명을 지어주면 된다. 주로 연산값을 정의하여 변수에 담을 때 붙이는 명령어이다.
$: 뒤에 특정 value 와 함께 statement를 적어주면 그 값이 바뀔때마다 해당 statement가 반영된다. 만약에 여러 줄의 statement를 같이 엮어주고 싶다면, $: 뒤에 { }를 쓰고 여러 줄을 적어주면 된다.

Reactive value를 활용할 때

  • import 된 store 변수를 자동구독(${변수}) 하려면 html 내 ${변수}로 표현한다.
  • store 변수 정의는 최상위 스코프에 있어야 한다.
    (최상위 스코프에 있다는 것은, 블록 안에서 변수가 정의되지 않고 <scirpt> 태그 하위에 바로 정의되어야 하는 것을 의미한다. import 된 store 변수 혹은 최상위 스코프에서 정의된 store 변수는 자동 구독을 할 수 있다.)
<script>
  import { name } from './store.js';   // 가장 상단에 변수 정의 하기, import 하기(최상위 스코프 유지)
</script>

<main>
    hi ${name}  // 달러표시를 붙이고 중괄호로 감싼 뒤 안에 상태값 넣어주기
</main>

Loops : 반복문 {#each} ~ {/each}

 // App.svelted

<script>
   let people = [
	{ name: 'juno', hairColor: 'brown', age: 25, id: 1 },
	{ name: 'mario', hairColor: 'black', age: 45, id: 2 },
	{ name: 'lily', hairColor: 'pink', age: 35, id: 3 },
    ];
</script>
<main>
    {#each people as person (person.id)}
        <div>
          <h4>{person.name}</h4>
          <p>{person.age} years old, {person.hairColor} hair</p>
        </div>
    {:else}
         <p>There are no people to show...</p>
    {/each}
</main>
  • 반복문 열기: { #each 변수 as 변수대체이름, index(변수대체이름.id) }
    • 두 번째 변수로 index 를 설정해 줄 수 있다.
    • 중간에 {:else}(else block) 을 사용해서 만약 데이터가 하나도 없을 경우 보여줄 DOM을 결정할 수 도 있다
  • 반복문 닫기: { /each}

if-else : 조건문 {#if} ~ {:else} ~ {/if}

 // App.svelted

<script>
   let number = 3;
</script>
<main>
    {#if num > 20}                 // {#if 조건}
	<p>Greater than 20</p>     // 참이면  보여줄 view
    {:else if num > 5}             // {:else if 조건}
	<p>Greater than 5</p>      // 충족하면  보여줄 view
    {:else}                        // {:else 조건}
	<p>Not greater than 5</p>  // 충족하면  보여줄 view 
    {/if}
</main>
  • 조건문 열기: { #if 참인 조건 }
  • 상세 조건:
    • {:else if 참이 조건}
    • {:else}
  • 조건문 닫기: { /if}

이벤트 함수 인자

 // App.svelted

<script>
   const handleClick = (event, message) => {
	console.log(message); // 'parameter'
   };
</script>

<main>
    <button on:click={(e) => handleClick(e, 'parameter')}>delete</button>
</main>
  • 이벤트 함수의 첫 번째 인자event 객체 이다
  • 이벤트 함수의 두 번째 인자parameter이다

🪴 Svelte Store

  • 상태 관리 라이브러리 로서, React의 redux, Vue의 vuex 와 같은 기능이다.

  • 기타 프레임워크(라이브러리)와는 다르게, svelte의 store는 내부(svelte/store)에 포함되어 있다.

  • 일반 store는 읽기, 쓰기(수정)가 가능해야 한다. 읽기/쓰기 모두 가능한 store(writable store)를 생성하는 방법은 아래와 같다

// store.js
import { writable } from 'svelte/store';

export const count = writable(0);      // store 를 생성, writable(초기값)

👉 생성된 변수(count)는 set, update 함수, subscribe 함수를 포함하는 객체가 된다

  • 마우스의 위치나, 현재 사용자 위치, 현재 시간 등, 수정이 필요하지 않은 store를 선언해야 할 때가 있기 때문에 store(readable store)를 지원한다. 생성 방법은 아래와 같다.
// store.js
mport { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return function stop() {
    clearInterval(interval);
  };
});



// App.svelte 
<script>
  import { time } from './stores.js';

  const formatter = new Intl.DateTimeFormat('en', {
    hour12: true,
    hour: 'numeric',
    minute: '2-digit',
    second: '2-digit'
  });
</script>

<h1>The time is {formatter.format($time)}</h1>
  • svelte의 storesubscribe 함수, update 함수, set 을 포함하는 단순한 객체이다.
    ❗️ subscribe 함수관찰하고 있는 값이 변경될 때마다 컴포넌트에게 알려주는 콜백 함수 이다.
    ❗️ update 함수관찰하고 있는 값을 업데이트 해주는 콜백 함수 이다.
    ❗️ set(지정값) 은 지정값을 지정하여 값을 바꿔준다
    즉, update 콜백함수는 기본 값을 받고, 그 해당 값을 변경하는데, set 은 아예 새로운 값을 집어넣는다.
    ex) count.update((n) => { n + 1 }); 👉 기본값 count를 받고, 그 값을 변경한다
    ex) count.set(5) 👉 count의 값은 아예 5로 바뀌어버린다
// store.js
import { writable } from 'svelte/store';

export const count = writable(0);      // store 를 생성, writable(초기값)



// App.svelted
<scirpt>
  import { onDestroy } from 'svelte';  // onDestroy 라이프사이클 함수 가져옴
  import { count } from './store.js';  // store 생성하는 파일로부터 store 가져옴
  import Incrementer from './Incrementer.svelte';
  import Decrementer from './Decrementer.svelte';
  import Resetter from './Resetter.svelte';

  let count_value;

  const unsubscribe = count.subscribe(value => {
    count_value = value;
  });

  onDestroy(unsubscribe);  // 관찰중인 함수를 해제하는 라이프사이클 함수
</script>

<main>
    <h1>The count is {count_value}</h1>

    <Incrementer/>
    <Decrementer/>
    <Resetter/>
</main>
  • count가 관찰하고 있는 값이 변경될 때마다 subscribe 함수의 콜백 함수가 실행된다.
  • onDestroy 라이프사이클 함수를 통해 해제를 할 수 있다. 주로 특정 함수가 초기화 될 때 또는 함수가 더이상 필요 없을 때 정리하는 용도로 사용된다. (메모리 누수 방지 효과)
// onDestroy 예제
<script>
	import { onDestroy } from 'svelte';

	let counter = 0;
	const interval = setInterval(() => counter += 1, 1000);

	onDestroy(() => clearInterval(interval));
</script>
// Incrementer.svelte
<script>
  import { count } from './stores.js';

  function increment() {
    count.update(n => n + 1);
  }
</script>

<button on:click={increment}>
  증가버튼
</button>
  • count.updaten현재의 count가 관찰하고 있는 값을 저장한다. 리턴된 값(n+1)으로 count가 관찰하는 값을 업데이트 한다.
  • count는 App.svelte 에서 관찰 중이다(subscribe)
  • 즉, 이전에 관찰하는 값(count) 보다 1이 더 큰 값으로 update된다.
    ❗️ writable(0)으로 count변수를 생성하였기 때문에 , count가 관찰하고 있는 값의 초기값은 0이 된다.
// Decrementer.svelte 
<script>
  import { count } from './stores.js';  // store에서 불러옴

  function decrement() {
    count.update(n => n - 1);
  }
</script>

<button on:click={decrement}>
  감소버튼
</button>
// Resetter.svelte
<script>
  import { count } from './stores.js';

  function reset() {
    count.set(0);
  }
</script>

<button on:click={reset}>
  리셋버튼
</button>
  • count.set(0) 을 호출하면, count 가 관찰하고 있는 값을 0으로 셋팅한다는 의미이다.(값을 새로 넣어줌, 바꿔줌)

https://im-developer.tistory.com/207?category=846746

profile
boma91@gmail.com

0개의 댓글