Svelte ] svelte 사용하기 - Events와 Binding

히징·2023년 1월 18일
0

Events

1. on:

<div on:mousemove={handleMousemove}>
	The mouse position is {m.x} x {m.y}
</div>

2. lnline handlers

<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
	The mouse position is {m.x} x {m.y}
</div>

따옴표는 선택 사항으로 일부 환경에서는 구문 강조 표시로 사용됩니다.

💡 일부 프레임워크에서는 내부 성능상의 이유로 인라인 이벤트 핸들러 사용을 지양하는것이 좋으나 Svelte는 해당하지 않으며 어떤 형식이든 상관없다.
  • 수정자
    <button on:click|once={handleClick}>
    	Click me
    </button>
    • preventDefault

      event.preventDefault()

      : 핸들러를 실행하기 전에 호출하여 핸들러의 이벤트 동작을 중단시킵니다.

    • stopPropagation

      event.stopPropagation()

      다음 요소에 이벤트가 도달하지 못하도록 이벤트 전파를 막습니다.

    • passive

      : 터치/휠 이벤트에서 스크롤 성능을 향상시킵니다.

    • nonpassivepassive: false

      : 명시적으로 설정

    • capture

      : 버블링 단계 대신 캡쳐링단계 에서 이벤트를 실행합니다. ( MDN 문서 ).

    • once

      핸들러가 처음 실행된 후 핸들러를 제거하여 한번만 이벤트가 실행됩니다.

    • self

      : event.target이 요소 자체(self)인 경우에만 핸들러를 실행합니다.

    • trusted

      : event.isTrustedtrue 인 경우에만 핸들러를 트리거합니다. 즉, 이벤트가 사용자 작업에 의해 트리거되는 경우입니다.

      on:click|once|capture={...} ⇒ 여러개의 수정자 연결

3. 컴포넌트에 이벤트 전달

Svelte에서는 이벤트 디스패처(dispatcher)를 사용하여 이벤트를 전달할 수 있습니다. Svelte는 자식 컴포넌트에서 이벤트가 발생하면 해당 이벤트를 부모 컴포넌트에게 전달하고 부모 컴포넌트에서 구현된 이벤트 처리 함수를 실행합니다.

다음 예제는 자식 컴포넌트에서 click 이벤트가 발생하면 부모 컴포넌트에서 구현된 이벤트 처리 함수를 실행합니다.


import Inner from './Inner.svelte';

function handleMessage(event) {
		alert(event.detail.text);
}
</script>

<Inner on:message={handleMessage}/>

import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

function sayHello() {
		dispatch('message', {
			text: 'Hello!'
		});
}
</script>

<button on:click={sayHello}>
	Click to say hello
</button>

자식 컴포넌트는 svelte의 createEventDispatcher 함수를 호출하여 Displatcher를 생성합니다. Dispatcher는 첫번째 매개변수로 이벤트 이름을 작성하고, 두번째로는 이벤트 핸들러에 전달할 인자를 설정합니다.

부모 컴포넌트는 자식 컴포넌트에서 디스패치로 보낸 이벤트 이름인 message와 이벤트 핸들러 함수 (handleMessage)을 연결하여 사용합니다. 이벤트가 발생하면 event의 detail 프로퍼티에서 dispatch함수의 두번째 매개변수로 지정한 데이터 값을 확인할 수 있습니다.

  • 중첩된 컴포넌트에서의 이벤트 전달 DOM 이벤트와 다르게 컴포넌트 이벤트는 버블링되지 않습니다. 따라서 중첩된 컴포넌트에서 이벤트를 수신하기 위해서는 중간 컴포넌트가 이벤트를 전달해야합니다.
    import Inner from './Inner.svelte';
    import { createEventDispatcher } from 'svelte';
    
    const dispatch = createEventDispatcher();
    
    function forward(event) {
    	dispatch('message', event.detail);
    }
    </script>
    
    <Inner on:message={forward}/>
    import Inner from './Inner.svelte';
    </script>
    
    <Inner on:message/>
    //svelte에서 지원하는 줄임말로 '모든 message 이벤트 전달' 의미
    import { createEventDispatcher } from 'svelte';
    
    const dispatch = createEventDispatcher();
    
    function sayHello() {
    	dispatch('message', {
    		text: 'Hello!'
    	});
    }
    </script>
    
    <button on:click={sayHello}>
    	Click to say hello
    </button>
  • Dom 이벤트 전달
    import CustomButton from './CustomButton.svelte';
    
        function handleClick() {
            alert('Button Clicked');
        }
    </script>
    
    <CustomButton on:click={handleClick}/>
    <button on:click>
        Click me
    </button>
    
    <style>
        button {
            background: #E2E8F0;
            color: #64748B;
            border: unset;
            border-radius: 6px;
            padding: .75rem 1.5rem;
            cursor: pointer;
        }
        button:hover {
            background: #CBD5E1;
            color: #475569;
        }
        button:focus {
            background: #94A3B8;
            color: #F1F5F9;
        }
    </style>
    DOM 이벤트에서 이벤트 전달을 할 때는 예제와 같이  <CustomButton> 클릭에 대한 알림을 받고 싶다면, CustomButton.svelte의 <button>요소에 click이벤트를 전달하면 됩니다.

Binding

일반적인 Svelte의 데이터 흐름 방식은 하향식으로 부모에서 자식의 방향으로 흘러갑니다. 그러나 bind 요소를 사용하게 되면 아래 예제의 input 값이 변경되게 되면, h1 태그 속 name 또한 즉각적으로 변경되어 반영됩니다.

let name = 'world';
</script>

<input bind:value={name}>

<h1>Hello {name}!</h1>

1. Inputs Binding

  • input의 number type과 range 타입 값 연결
    <label>
    	<input type=number bind:value={b} min=0 max=10>
    	<input type=range bind:value={b} min=0 max=10>
    </label>

  • checkbox inputs
    <input type=checkbox bind:checked={yes}>
    
    {#if yes}
    ...
    {:else}
    ...
    {/if}
    input.value에 바인딩 하는 대신 input.checked에 바인딩 합니다.
  • bind:group
    : 동일한 값과 관련된 여러 입력이 있는 경우 bind:group 특성을 사용하여 묶을 수 있습니다.
    <label>
    	<input type=radio bind:group={scoops} name="scoops" value={1}>
    	One scoop
    </label>
    
    <label>
    	<input type=radio bind:group={scoops} name="scoops" value={2}>
    	Two scoops
    </label>
    
    <label>
    	<input type=radio bind:group={scoops} name="scoops" value={3}>
    	Three scoops
    </label>
  • textarea
    import { marked } from 'marked';
    let text= `Some words are *italic*, some are **bold**`;
    </script>
    
    {@html marked(text)}
    
    <textarea bind:value={text}></textarea>
    textarea요소는 input과 유사하게 bind:value를 사용합니다. 만약 이름이 일치하는 경우 단축 형식을 사용할 수 있습니다.
    <textarea bind:value></textarea>
    ⇒ svelte에서 지원하는 단축 형식으로 모든 바인딩에 적용됩니다.
  • select select에서도 bind:value를 사용할 수 있습니다.
    <select bind:value={selected} on:change="{() => answer = ''}">
    		{#each questions as question}
    			<option value={question}>
    				{question.text}
    			</option>
    		{/each}
    	</select>
    • select는 multiple 속성을 가질 수 있으며 이 경우 단일 값을 선택하는 대신 배열을 채우게 됩니다.

2. Each 블록 속성에 binding

each 블록 내부의 속성에도 바인딩 할 수 있습니다.

{#each todos as todo}
	<div class:done={todo.done}>
		<input
			type=checkbox
			bind:checked={todo.done}
		>

		<input
			placeholder="What needs to be done?"
			bind:value={todo.text}
		>
	</div>
{/each}

그러나 이러한 input 요소와 사용하게 되면, 배열이 변경되기 때문에 변경할 수 없는 데이터로 작업하는 경우 이벤트 핸들러를 사용하는 것이 좋습니다.

3. 미디어 요소에 binding : ,

<audio>및 <video>요소에는 바인딩할 수 있는 여러 속성이 있습니다 .

<video
	poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
	src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
	on:mousemove={handleMove}
	on:touchmove|preventDefault={handleMove}
	on:mousedown={handleMousedown}
	on:mouseup={handleMouseup}
	bind:currentTime={time}
	bind:duration
	bind:paused>
	<track kind="captions">
</video>
  • 전체 예제 보기
    
    	// These values are bound to properties of the video
    	let time = 0;
    	let duration;
    	let paused = true;
    
    	let showControls = true;
    	let showControlsTimeout;
    
    	// Used to track time of last mouse down event
    	let lastMouseDown;
    
    	function handleMove(e) {
    		// Make the controls visible, but fade out after
    		// 2.5 seconds of inactivity
    		clearTimeout(showControlsTimeout);
    		showControlsTimeout = setTimeout(() => showControls = false, 2500);
    		showControls = true;
    
    		if (!duration) return; // video not loaded yet
    		if (e.type !== 'touchmove' && !(e.buttons & 1)) return; // mouse not down
    
    		const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
    		const { left, right } = this.getBoundingClientRect();
    		time = duration * (clientX - left) / (right - left);
    	}
    
    	// we can't rely on the built-in click event, because it fires
    	// after a drag — we have to listen for clicks ourselves
    	function handleMousedown(e) {
    		lastMouseDown = new Date();
    	}
    
    	function handleMouseup(e) {
    		if (new Date() - lastMouseDown < 300) {
    			if (paused) e.target.play();
    			else e.target.pause();
    		}
    	}
    
    	function format(seconds) {
    		if (isNaN(seconds)) return '...';
    
    		const minutes = Math.floor(seconds / 60);
    		seconds = Math.floor(seconds % 60);
    		if (seconds < 10) seconds = '0' + seconds;
    
    		return `${minutes}:${seconds}`;
    	}
    </script>
    
    <h1>Caminandes: Llamigos</h1>
    <p>From <a href="https://studio.blender.org/films">Blender Studio</a>. CC-BY</p>
    
    <div>
    	<video
    		poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
    		src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
    		on:mousemove={handleMove}
    		on:touchmove|preventDefault={handleMove}
    		on:mousedown={handleMousedown}
    		on:mouseup={handleMouseup}
    		bind:currentTime={time}
    		bind:duration
    		bind:paused>
    		<track kind="captions">
    	</video>
    
    	<div class="controls" style="opacity: {duration && showControls ? 1 : 0}">
    		<progress value="{(time / duration) || 0}"/>
    
    		<div class="info">
    			<span class="time">{format(time)}</span>
    			<span>click anywhere to {paused ? 'play' : 'pause'} / drag to seek</span>
    			<span class="time">{format(duration)}</span>
    		</div>
    	</div>
    </div>
    
    <style>
    	div {
    		position: relative;
    	}
    
    	.controls {
    		position: absolute;
    		top: 0;
    		width: 100%;
    		transition: opacity 1s;
    	}
    
    	.info {
    		display: flex;
    		width: 100%;
    		justify-content: space-between;
    	}
    
    	span {
    		padding: 0.2em 0.5em;
    		color: white;
    		text-shadow: 0 0 8px black;
    		font-size: 1.4em;
    		opacity: 0.7;
    	}
    
    	.time {
    		width: 3em;
    	}
    
    	.time:last-child { text-align: right }
    
    	progress {
    		display: block;
    		width: 100%;
    		height: 10px;
    		-webkit-appearance: none;
    		appearance: none;
    	}
    
    	progress::-webkit-progress-bar {
    		background-color: rgba(0,0,0,0.2);
    	}
    
    	progress::-webkit-progress-value {
    		background-color: rgba(255,255,255,0.6);
    	}
    
    	video {
    		width: 100%;
    	}
    </style>

비디오를 클릭하게 되면 time, duration, paused가 업데이트 됩니다. 이처럼 사용자가 커스텀하여 컨트롤 할 수 있게 됩니다.

  • 읽기 전용 binding
    • duration

      : 비디오의 총 재생 시간(초)

    • buffered

      : {start, end}의 객체 배열

    • seekable

      : {start, end}의 객체 배열

    • played

      : {start, end}의 객체 배열

    • seeking

      : Boolean

    • ended

      : Boolean

      <video>videoWidthvideoHeight 가 추가적으로 있습니다.

  • 양방향 binding
    • currentTime : 비디오의 현재 지점(초)
    • playbackRate1 : 비디오 재생 속도 // 1 = 보통 속도
    • paused : 이것은 자명해야 합니다.
    • volume : 0과 1 사이의 값
    • muted : boolean 값 // true : 음소거

4. Block-레벨 요소의 binding

모든 block-level 요소에는 clientWidth, clientHeight, offsetWidth, offsetHeight 바인딩을 가지고 있습니다.

이 바인딩들은 읽기 전용으로 w , h를 바꾸는 것 이외에는 어떤 효과도 없습니다.

<div bind:clientWidth={w} bind:clientHeight={h}>
	<span style="font-size: {size}px">{text}</span>
</div>

div의 크기를 받아온 값으로 변경해줍니다.

💡 inline 요소와 <canvas>에서는 사용이 불가능합니다.

5. This binding

6. 컴포넌트 binding

DOM 요소의 속성(<div>,<button>등) 에 바인딩 할 수 있는 것처럼 컴포넌트에도 바인딩 할 수 있습니다. 예를 들어 keypad 컴포넌트의 props인 value를 form 요소를 통해 바인딩할 수 있습니다.

<Keypad bind:value={pin} on:submit={handleSubmit}/>
  • keypad 전체 예제 보기
    import Keypad from './Keypad.svelte';
    
    let pin;
    $: view = pin ? pin.replace(/\d(?!$)/g, '•') : 'enter your pin';
    
    function handleSubmit() {
    	alert(`submitted ${pin}`);
    }
    </script>
    
    <h1 style="color: {pin ? '#333' : '#ccc'}">{view}</h1>
    
    <Keypad bind:value={pin} on:submit={handleSubmit}/>
    import { createEventDispatcher } from 'svelte';
    
    export let value = '';
    
    const dispatch = createEventDispatcher();
    
    const select = num => () => value += num;
    const clear  = () => value = '';
    const submit = () => dispatch('submit');
    </script>
    
    <div class="keypad">
    	<button on:click={select(1)}>1</button>
    	<button on:click={select(2)}>2</button>
    	<button on:click={select(3)}>3</button>
    	<button on:click={select(4)}>4</button>
    	<button on:click={select(5)}>5</button>
    	<button on:click={select(6)}>6</button>
    	<button on:click={select(7)}>7</button>
    	<button on:click={select(8)}>8</button>
    	<button on:click={select(9)}>9</button>
    
    	<button disabled={!value} on:click={clear}>clear</button>
    	<button on:click={select(0)}>0</button>
    	<button disabled={!value} on:click={submit}>submit</button>
    </div>
    
    <style>
    	.keypad {
    		display: grid;
    		grid-template-columns: repeat(3, 5em);
    		grid-template-rows: repeat(4, 3em);
    		grid-gap: 0.5em
    	}
    
    	button {
    		margin: 0
    	}
    </style>
  • 컴포넌트의 인스턴스에 바인딩 DOM 요소에 바인딩 하는 것처럼 컴포넌트의 인스턴스에도 바인딩이 가능합니다.
    import InputField from './InputField.svelte';
    
    let field;
    </script>
    
    <InputField bind:this={field}/>
    
    <button on:click={() => field.focus()}>
    Focus field
    </button>
    let input;
    
    export function focus() {
    	input.focus();
    }
    </script>
    
    <input bind:this={input} />
profile
FE DEVELOPER 👩🏻‍💻🤍

0개의 댓글