Svelte 입문 강의 - A부터 Z까지 5

5️⃣

🖥 애니메이션

12 - 1. 애니메이션 사용하기

// src/App.svelte
<script>
	import { flip } from 'svelte/animate'

	let items = [1];
	function addItem(){
		items.unshift(Math.max(...items) + 1);
		items = items;
	}

	function removeItem(){
		items.shift();
		items = items;
	}
</script>

<button on:click={addItem}>add</button>
<button on:click={removeItem}>remove</button>

{#each items as item (item)}
	<p animate:flip=>{item}</p>
{/each}

12 - 2. 커스텀 애니메이션

animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
  delay?: number,
  duration?: number,
  easing?: (t: number) => number,
  css?: (t: number, u: number) => string,
  tick?: (t: number, u: number) => void
}

DOMRect {
  bottom: number,
  height: number,
  ​​left: number,
  right: number,
  ​top: number,
  width: number,
  x: number,
  y: number
}

12 - 3. 애니메이션 사용시 주의사항

기존 unshift 일때는 제거될때도 애니메이션이 동작했지만, pop 을 이용했더니 제거될때 애니메이션이 먹지 않는다..! 여기서 트리거란 ? 트리거(Trigger)란 영어로 방아쇠라는 뜻인데, 방아쇠를 당기면 그로 인해 총기 내부에서 알아서 일련의 작업을 실행하고 총알이 날아갑니다. 이처럼 데이터베이스에서도 트리거(Trigger)는 특정 테이블에 INSERT, DELETE, UPDATE 같은 DML 문이 수행되었을 때, 데이터베이스에서 자동으로 동작하도록 작성된 프로그램입니다. 즉! 사용자가 직접 호출하는 것이 아니라, 데이터베이스에서 자동적으로 호출하는 것이 가장 큰 특징입니다. 출처 : https://limkydev.tistory.com/154

🖥 액션

13 - 1. 액션 만들기

action = (node: HTMLElement, parameters: any) => {
  update?: (parameters: any) => void,
  destroy?: () => void
}
// src/App.svelte
<script>
	function longpress( node, duration){
		let timer;

		function handleMouseDown(){
			timer = setTimeout(()=>{
				node.dispatchEvent(new CustomEvent('longpress'))
			}, duration)
		}

		function handleMouseUp(){
			clearTimeout(timer)
		}

		node.addEventListener('mouseDown', handleMouseDown)
		node.addEventListener('mouseUp', handleMouseUp)
		// longpress 액션이 정의된 html 요소가 mount 됬을때 호출되는 부분 

		return {
			update(newValue){
				duration = newValue
			},
			destroy(){
				node.removeEventListener('mouseDown', handleMouseDown)
				node.removeEventListener('mouseUp', handleMouseUp)
			}
		}
	}
	let duration = 200;

	function handleLongpress(){
		alert('longpress')
	}
</script>

<input type="range" bind:value={duration} min='100' max="2000">

<button 
	use:longpress={duration}
	on:longpress={handleLongpress}
	>
	Click
</button>

🖥 Slot

14 - 1. Slot 사용하기

// src/Box.svelte
<div class="box">
    <slot></slot>
</div>

// src/App.svelte
<script>
	import Box from './Box.svelte'
</script>

<Box>
	<!-- box컴포넌트 안에 slot을 넣어줬기 때문에, 자식컴포넌트를 가질  있게 된다. -->
	<h1>Box component</h1>
	<p>Hello</p>
</Box>

14 - 2. Default Slot

// src/Box.svelte
<div class="box">
    <slot>
        <p>값이 없습니다!</p>
    </slot>
</div>

// src/App.svelte
<script>
	import Box from './Box.svelte'
</script>

<Box></Box>

14 - 3. Named Slot

// src/Box.svelte
<div>
    <slot name="title"></slot>
    <slot name="content"></slot>
    <slot name="detail"></slot>
</div>

// src/App.svelte
<script>
	import Box from './Box.svelte'
</script>

<Box>
	<p slot="title">title</p>
	<p slot="content">content</p>
	<p slot="detail">detail</p>
</Box>

<Box>
	<p slot="content">content</p>
	<p slot="detail">detail</p>
	<p slot="title">title</p>
</Box>

<!--  이렇게 순서를 섞어도, 출력 결과물은 같다. 이유는 box component에서 정한 위치값이 있기때문에, 기준이 box component이 된다. -->

14 - 4. Slot props

// src/Hoverable.svelte
<script>
    let hovering;
    function enter (){
        hovering = true;
    }
    function leave (){
        hovering = false;
    }

</script>

<div 
    on:mouseenter={enter}
    on:mouseleave={leave}
> 
    <slot hovering={hovering}></slot>
</div>

// src/App.svelte
<script>
	import Hoverable from './Hoverable.svelte'
</script>

<Hoverable let:hovering={active}>
	<!-- actvie라는 변수가 정의되어있지 않음을 확인할  있는데, 해당 태그 안에서만 사용   있다. -->
	<!-- 그렇기 때문에  Hoverable 태그가  있다고 하더라도, active는 독립적이다. -->
	<div class:active>
		{#if active}
		<p>active가 활성화 되었습니다.</p>
		{:else}
		<p>active가 비활성화 되었습니다.</p>
		{/if}
	</div>
</Hoverable>

<style>
	div {
		padding: 1em;
		background-color: #eee;
	}
	.active {
		background-color: hotpink;
		color: white;
	}
</style>

🖥 Context API

15 - 1. Context 사용하기

// src/context.js
const key = {}

export {
    key
}

// src/Box.svelte
<script>
    import { key } from './context.js'
    import { setContext } from 'svelte'

    const context = {
        name: 'Bill',
        job: 'actor',
        age: 30
    }

    setContext(key, {
        context
    })
</script>

<div>
    <slot></slot>
</div>

// src/Child.svelte
<script>
    import { key } from './context.js'
    import { getContext } from 'svelte'

    const { context } = getContext(key);
</script>

<p>{context.name}</p>
<p>{context.job}</p>
<p>{context.age}</p>

//src/App.svelte
<script>
	import Box from './Box.svelte'
	import Child from './Child.svelte'
</script>

<Box>
	<Child></Child>
</Box>

15 - 2. Context와 Store

🖥 Svelte 요소

16 - 1.

16 - 2. [실습] - JSON parser 만들기

// src/jsonItem.svelte
<script>
    export let json;
    export let key = 'JSON';

    const obj = JSON.parse(json)
    let expended = false;
    const entries = Object.entries(obj)
    // Object.entries -> [key, value] 쌍의 배열을 반환합니다
    const type = Object.prototype.toString.call(obj)
    // 어떤 타입인지 확인 ! 
    let value;
    if(type === '[object Array]'){
        value = `[${entries.length}]`
    }else if(type === '[object Object]'){
        value = `{${entries.length}}`
    }else {
        value = json
    }
</script>

<div>
    <span>{key}</span>: <span on:click={()=> expended = !expended}>{value}</span>
    {#if expended}
        {#each entries as [key, value], index (index)}
        <svelte:self {key} json={JSON.stringify(value)}></svelte:self>
        {/each}
    {/if}
</div>

// src/App.svelte
<script>
	import JsonItem from './JsonItem.svelte'
	let json = JSON.stringify({
		a: 1,
		b: [
			1,
			2,
			3
		],
		c: {
			d: 1,
			e: 2
		},
		f : 'test'
	})
</script>

<JsonItem {json}></JsonItem>

16 - 3.

// src/App.svelte

<script>
	import Green from './Green.svelte'
	import Blue from './Blue.svelte'
	import Red from './Red.svelte'

	const options = [
		{color: 'green', component: Green},
		{color: 'blue', component: Blue},
		{color: 'red', component: Red},
	]

	let selected = options[0]
</script>

<select bind:value={selected}>
	{#each options as option (option.color)}
	<option value={option}>{option.color}</option>
	{/each}
</select>

<!-- {#if selected.color === 'red'}
<Red />
{:else if selected.color === 'green'}
<Green />
{:else if selected.color === 'blue'}
<Blue />
{/if} -->

<!-- 위와 같은 코드를 component  이용하면  간결히 표현할  있다. -->

<svelte:component this={selected.component}/>

<!-- 똑같은 화면이 구현된다. -->

// 다른 컴포넌트에서는 각 div 와 배경색을 준 상태

16 - 4.

// src/App.svelte
<script>
	let key;
	let keyCode;
	let innerWidth;
	let innerHeight;
	let scrollY;
	function handleKeyDown(event){
		key = event.key
		keyCode = event.keyCode
	}
</script>

<svelte:window 
	on:keydown={handleKeyDown}
	bind:innerWidth={innerWidth}
	bind:innerHeight={innerHeight}
	bind:scrollY={scrollY}
/>

<p>{innerWidth}, {innerHeight}</p>
<div>
	{key}, {keyCode}
</div>

<button on:click={()=> scrollY = 0}>TOP({scrollY})</button>

<style>
	div {
		height: 200vh;
	}
	button {
		position: fixed;
		bottom: 0;
		right: 0;
	}
</style>

16 - 5.

// src/App.svelte
<script>
	let isEnter;
	function handleMouseenter(){
		isEnter = true;
	}
	function handleMouseleave(){
		isEnter = false;
	}
</script>

<svelte:body
	on:mouseenter={handleMouseenter}
	on:mouseleave={handleMouseleave}
/>

{isEnter}
// 이렇게 바디에 들어오면 true, 나가면 false 를 찍는것을 볼 수 있다.

16 - 6.

// src/App.svelte
<svelte:head>
	<meta name="James">
</svelte:head>

16 - 7.

// src/App.svelte
<script>
	import Todo from './Todo.svelte'

	let todos = [
		{id:1, done: false, text: '공부하기'},
		{id:2, done: false, text: '밥먹기'},
		{id:3, done: false, text: '운동하기'},
	]

	function toggle(toggled){
		todos = todos.map( x => {
			return x === toggled ? {id: x.id, done: !x.done, text: x.text} : x
		})
	}
</script>

{#each todos as todo (todo.id)}
	<Todo {todo} on:click={()=> toggle(todo)}></Todo>
{/each}

// src/Todo.svelte
<script>
    import { afterUpdate } from 'svelte'
    export let todo;

    let updateCount = 0;
    afterUpdate(()=> {
        updateCount += 1;
    })
</script>

<svelte:options immutable={true}/>
<!-- 위에처럼 정의해주지 않으면, 하나만 변경되도 횟수가 모두 업데이트된다. -->

<div on:click>
    <!-- 부모 요소로 클릭이벤트를 전달해준다. -->
    {todo.done}, {todo.text} / {updateCount}</div>

🖥 Module context

17 - 1. Module context

// src/App.svelte
<script>
	import Child from './Child.svelte'
</script>

<Child></Child>
<Child></Child>

// src/Child.svelte
<script context="module">
    //  context="module" -> 스크립트 태그내에 정의된 태그는 동일한 컴포넌트로 생성된 인스턴스 내에서 공유된다.
    //  반응형으로 동작하지는 않지만, 콘솔로그에서는 변경되는것을 확인할 수 있다.
    let count = 0;
</script>

<script>
    function handleClick(){
        count += 1;
    }
</script>

{count}
<button on:click={handleClick}>add</button>

17 - 2. 코드 내보내기

// src/App.svelte
<script>
	import Child, { printCount } from './Child.svelte'
</script>

<Child></Child>
<Child></Child>
<button on:click={printCount}>Show</button>

// src/Child.svelte
<script context="module">
    let count = 0;
    export function printCount(){
        alert(count);
    }
</script>

<script>
    function handleClick(){
        count += 1;
    }
</script>

{count}
<button on:click={handleClick}>add</button>