Svelte 훑어보기

Tei·2020년 12월 13일
8
post-thumbnail

들어가며

최근 프론트엔드 진영에서 아주 핫했던 React 를 버릴 때가 왔다 라는 글이 있었습니다.

페이스북과 여러 커뮤니티에 공유되며 나오는 여러 의견들을 보며 저 또한 많은 생각이 들더군요.

개인적으로는 (아직은) Hooks 를 좋아하고 큰 불만이 없었지만, 생각보다 많은 분들이 Hooks

의 설계에 긍정적이지 않은 반응을 보이셨고, 그 대체제로 svelte 에 대한 이야기가 나와 오늘은 한번 svelte

에 대해 알아보려 합니다.

Svelte 란 뭔가..?

당연하지만, Svelte는 React 같은 프론트엔드 프레임워크입니다.

대표적인 특징을 꼽자면...

  • 가상돔을 사용하지 않는다
  • 번들 크기가 아주 작다
  • 런타임이 아닌 빌드타임에 어느 부분이 변경될지 파악한다.
    • 문서에서는 Truly reactive 라는 표현을 사용했다.
    • react 는 런타임에 가상돔을 비교하여 변경된 내용을 파악하지만, svelte 는 빌드타임에 어느 부분이 변경될 지 파악하고 실제로 화면을 갱신한다.

종합해보자면 작고, 빠른 프론트엔드 라이브러리라고 할 수 있겠습니다.

1.설치

svelte 에서 보일러플레이트를 제공합니다.

웹팩 기반으로 설치해보도록 하겠습니다.

> npx degit sveltejs/template-webpack svelte-app
> npm install

> npm run dev
  • npm run dev 로 잘 실행됩니다.

2. Svelte 컴포넌트의 구성.

스벨트 컴포넌트는

  • script
  • style
  • jsx같은 무언가(...) 로 이루어져있습니다.
//script
<script>
	let name = "Tei"
</script>

//css
<style>
  h1 {
    color: purple;
  }
</style>

//jsx같은 무언가...
<h1>Hello {name}!</h1>

컴포넌트 단위에서 상태와 css 를 관리할 수 있어 (컴파일시에 css 이름을 할당)
CSS in Js 방식과 비슷한 느낌으로 사용가능합니다.
간단한 버튼 컴포넌트를 이벤트 리스너와 함께 구성해보자면,

Button.svelt
<script>
    let content = "확인"
</script>

<style>
	button {
    color:white;
    background:  #01BED6;
    border-radius: 0.3rem;
    width: 6rem;
    height: 2.2rem;
    padding: 0.5rem 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    outline: none;
    border: 0px;
  }
</style>
// 이벤트 리스너는 on:이벤트 로 사용할 수 있다.
<button on:click>{content}!</button>

와 같이 구성할 수 있고,
다른 svelte 파일에서 import 구문을 사용하여 불러올 수 있습니다.

App.svelte
<script>
  	// import 구문으로 불러올 수 있다.
	import Button from './Components/Button.svelte'
</script>

// 다은 컴포넌트를 불러올 수 있다.
<Button on:click={ handleClick }> 버튼 </Button>

특정 컴포넌트에 on:이벤트 를 사용하여 이벤트 핸들러를 등록할 수 있습니다.

  • 랜더링 된 결과

버튼 컴포넌트에 "버튼" 이라고 넘겨줬는데, 실제 랜더링 된 결과는 "확인" 입니다. 버튼 컴포넌트에 하드코딩되어있기 때문인데, 이를 child props로 받아 처리해 봅시다.

slot

React 의 children Props 처럼, svelte 에는 slot 이라는게 있습니다. Button 에. slot 을 적용해서, 부모에서 내려준 children을 버튼의 내용으로 사용하도록 하겠습니다.

<button on:click> 
	<slot/>
</button>

이렇게 해주면 부모 컴포넌트에서

<Button on:click={ handleClick }> 버튼 </Button>

과 같이 children 을 내려줄 수 있습니다.

3. 컴포넌트의 조건부 스타일링

버튼을 만들었는데, 옵션에 따라 다르게 그려주고 싶은 욕심이 있습니다.

styled-components 였다면 props 에 따라 간편하게 조건부 스타일링을
할 수 있는데요, svelte 도 비슷하게 사용 가능합니다.

Round 와 Ghost 옵션에 따라 다르게 그려주는 버튼을 만들어보겠습니다.

Button.svelte
<script>
    let content = "확인"
// export 키워드를 써서 props 임을 알려줍니다.
    export let ghost = false;
    export let round = false;
</script>

<style>
  button {
    color:white;
    background:  #01BED6;
    border-radius: 0.3rem;
    width: 6rem;
    height: 2.2rem;
    padding: 0.5rem 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    outline: none;
    border: 0px;
  }

  .ghost {
    background-color: transparent;
    border: solid gainsboro 1px;
    color: #01BED6;
  }

  .round {
    border-radius: 1.2rem;
  }
</style>

<button class:ghost={ghost} class:round={round} on:click>
    <slot/>
</button>

그리고 해당 컴포넌트를 부를 때 props 로 건내줍니다.

App.svelte
<Button round on:click={ handleClick }>버튼</Button>

round props 를 건내주어 round 버튼이 되는것을 확인할 수 있습니다.
props 를 사용하기 위해서는 export let round = false; 와 같이 export 예약어를 사용해주어야 합니다.(살짝 마음에 들지는 않습니다...)

4. 컴포넌트의 조건부 렌더링

React 에서 특성 state에 따라 다른 컴포넌트를 그려주는 경우가 많습니다.

가령 User의 상태가 로그인상태면 로그아웃 버튼을 보여주고, 로그아웃 상태면 로그인 버튼을 그려주는것과 같이 말입니다.

React 였다면 {loggingIn? <로그아웃/> : <로그인/>} 처럼 Javascript 3항 연산자를 통해 구현하겠지만 svelete 는 이를 위한 Html 템플릿 스러운 문법을 지원합니다.(마치 루비의 erb 같네요)

App.svelte
<script>
import Button from './Components/Button.svelte'
let loggedIn = false;
const toggle = () => loggedIn = !loggedIn;
</script>

// #if 블록을 통해 조건부 랜더링 
{#if loggedIn}
  <Button round on:click={ toggle } > 로그아웃 </Button>
{:else}
  <Button round on:click={ toggle }> 로그인 </Button>	
{/if}

5. each 블록을 이용한 반복 렌더링 구현

React 에서 map 을 사용하여 반복 렌더링을 구현하는것 처럼, Svelte 는 each 블록을 사용해서 반복 렌더링을 구현할 수 있습니다.

카드 컴포넌트를 하나 만들고, each 블록을 통해 그려보도록 하겠습니다.

Card.sevelte
<script>
// props 로 데이터를 받아 그려주기 위해 export 해줌
  export let data;
  let {title,content,status} = data;
</script>

<style>
  .card {
    display: block;
    width:260px;
    margin: 10px;
    padding: 1rem;
    background: #FFFFFF;
    border-radius: 8px;
    padding:1rem;
    box-shadow: 0px 1px 8px 1px #0000000d;
  }

  div{
    margin-bottom: 0.25rem;
    color: rgb(65, 65, 65);
  }
</style>

<div class={'card'}>
    <h3>{title}</h3>
    <div>{content}</div>
    <div>{status}</div>
</div>
App.svelte
<script>
	import Card from "./Components/Card.svelte"
	
	const dataList = [
		{
			title:"12.12(토)",
			content:"우리집 15:00 ~ 19:00",
			status: "서비스가 완료되었습니다."
		},
		{
			title:"12.11(금)",
			content:"우리집 8:00 ~ 12:00 ",
			status: "서비스가 완료되었습니다."
		},
		{
			title:"12.10(목)",
			content:"우리집 8:00 ~ 12:00",
			status: "서비스가 완료되었습니다."
		}
	]
</script>

{#each dataList as data}
	<Card data={data}/>
{/each}
  • 랜더링 결과

6.비동기처리

서버로부터 데이터를 요청하고, 데이터 로딩이 끝나면 데이터를 그려주고 싶을때, #await 블럭을 사용할 수 있습니다.

1500ms 뒤에 done 이라고 출력하는 promise 를 하나 만들고, 결과를 받기전까지 Loading 받은 후에는 Done. 을 출력하는 코드를 작성해 봅니다.

<script>
import Button from './Components/Button.svelte'
let promiseValue = new Promise((res)=>{
	setTimeout(()=>{
		res("DONE!")
	},1500)
});
</script>

{#await promiseValue}
	Loading...
{:then value} 
	{value}
{/await}

간결한 비동기 처리가 가능하네요.

7. bind 를 통한 이벤트 처리 간략화

일반적인 자바스크립트에서 특정한 이벤트 처리를 위해 event.target.value 와 같이 사용합니다만, svelte에서는 더 간략화된 bind 라는 표현이 있습니다.

<script>
let name = 'world';
const handleChange = (e) => {name = e.target.value}
</script>

<input value={name} on:input={handleChange}>
// 아래와 위는 같은 뜻
<input bind:value={name}>

bind 라는 용법이 낯설긴 한데, 익숙해지면 나름대로 편할것도 같다는 생각이 듭니다.

8. LifeCycle

React 의 useEffect 나 componentdidmount 같이 svelte 에도 lifecycle 관련 함수들이 있습니다.

그중 onMount 를 한번 알아보도록 하겠습니다.

onMount 함수는 컴포넌트가 최초 돔에 렌더링 된 후에 불립니다. react 에서 사용했던것과 같이 익숙한 방식으로 사용해주시면 됩니다.

onMount 를 사용하기 위해 svelte 로부터 import 해주고, script 태그 안에서 사용해봅시다.

<script>
import Button from './Components/Button.svelte'
import Card from "./Components/Card.svelte"

//onMount 불러오기
import { onMount } from 'svelte';

let dataList = [];
let promiseValue = new Promise((res)=>{
  setTimeout(()=>{
    res(dataListFromServer)
  },1500)
});

// 콜백함수로 async 함수 등록.
onMount(async () => {
  dataList = await promiseValue;
});

const dataListFromServer = [
  {
    title:"12.12(토)",
    content:"우리집 15:00 ~ 19:00",
    status: "서비스가 완료되었습니다."
  },
  {
    title:"12.11(금)",
    content:"우리집 8:00 ~ 12:00 ",
    status: "서비스가 완료되었습니다."
  },
  {
    title:"12.10(목)",
    content:"우리집 8:00 ~ 12:00",
    status: "서비스가 완료되었습니다."
  }
]
</script>
  
{#each dataList as data}
	<Card data={data}/>
{:else}
	Loading...
{/each}

더 알아보기

여기까지 배운 요소들만으로도 대부분의 웹 사이트를 구현하는데 큰 문제가 없습니다. 다른 프레임워크를 사용하듯 사용해주시면 됩니다.

다만 svelte 에는 여기서 소개하지 않은 기능이 아직 많은데요, 대표적으로 전역 상태관리를 위한 Stores 라는 기능이 있습니다.

상태관리 라이브러리를 사용해보셨던 분이라면 비슷하게 사용 가능하니 공식 문서를 통해 더 알아보시는것도 좋을것 같습니다,

감상

스벨트의 공식문서를 보며 꼭 필요한 부분만을 살펴보았습니다.

첫 인상을 말해보자면 사실 음....? 입니다.

svelte 에 큰 정이 가지 않는 첫번째 이유는 마치 html 템플릿 엔진을 사용하는것 처럼 느껴졌기 때문입니다.

#{each} 블록이나 #{await} 블록은 아주 매력적이었지만 순수한 javascript 를 사용한다는 느낌이 들지 않아 약간의 거부감이 들었습니다.

(JSX 가 순수한 자바스크립트냐! 라고 하신다면 할 말이 없지만 javascript 에서 익숙한 array 메소드들을 바로 사용할 수 있다는게 느낌상 다른듯 합니다)

이 또한 익숙해지면 당연해질꺼라 생각하지만 map 으로 처리해도 될것을
굳이? 라는 생각이 드는것도 사실입니다.

하지만 개발하면서 느낀점은 아주 간결하고, 익히기 쉽다는 점이었습니다.

react를 사용할 필요 없는 간단한 웹사이트 제작에는 한번 써보는것도 어떨까 하는 생각이 드는 프레임워크 였네요. 최근 본 글중 웹앱에는 React 웹사이트에는 Svelte 가 스탠다드가 될것이다 라는 글이 있었는데(https://www.swyx.io/svelte-sites-react-apps/), 그 말처럼 Svelte 가 인터랙션이 많지 않은 사이트에서 유효한 대안이 될 수 있을것이라는 생각도 듭니다.

참고

그럼 더 좋은 글로 찾아뵙겠습니다.

profile
Being a service developer

2개의 댓글

comment-user-thumbnail
2020년 12월 14일

역쉬 풀스디 선생님 스벨트까지 섭렵해노으셔따

답글 달기
comment-user-thumbnail
2022년 1월 10일

Button.svelt 끝에 e 빠졌어요.

답글 달기