AstroJs는 Islands
라는 개념을 사용해서 컴포넌트를 독립적으로 구성할 수 있다. 그 특징을 이용하면 하나의 페이지에서 react
, vue
, svelte
등 다양한 프레임워크를 혼용해서 사용할 수 있다. 이를 활용해 프레임워크별로 input을 처리하는 방법을 알아보자.
목차
구성
총 5개의 input을 구성했다. 1. vue, 2. react, 3. lit, 4. svelte, 5. vanilla. 로직은 기본적으로 target에 input에 입력이 발생하면, 해당 입력 뒤에 ’!’를 붙여서 상태값을 변경하도록 구성했다.
코드: https://github.com/jinbekim/input
React
기본적으로 useState
를 사용해서 상태값을 선언하고, onChange
이벤트를 통해 상태값을 변경한다. 랜더링은 setState가 실행될때마다 발생한다. 일반적인 html의 change
이벤트와 다르게 리액트의 특성상 input
이벤트인 onInput
과 동일하게 작동하고, 보편적으로 onChange
를 사용한다.
import { useState, type ChangeEvent, type EventHandler } from "react";
export const ReactInput = () => {
const [inputValue, setInputValue] = useState('');
const onInputChange: EventHandler<ChangeEvent<HTMLInputElement>> = (e) => {
const value = e.target.value + '!';
setInputValue(value);
}
return (
<div style={{display: 'inline'}}>
<input
value={inputValue}
onChange={onInputChange}
/>
<span> value: {inputValue} </span>
</div>
);
}
Vue
ref
를 사용해서 상태값을 선언하고, @input
이벤트를 통해 상태값을 변경한다. @input
은 input
이벤트와 동일하게 작동한다.
<template>
<div style="display: inline;">
<input :value="value" name="VueInput" id="VueInput" @input="onInput"
autocomplete="off"
>
<span> value: {{ value }}</span>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
const onInput = (e: Event) => {
value.value = (e.target as HTMLInputElement).value + '!'
}
</script>
Lit
웹컴포넌트를 사용하기 쉽게 만들어진 라이브러리이다. @state
를 사용해서 상태값을 선언하고, @input
이벤트를 통해 상태값을 변경한다. 표준html을 사용하기 때문에 단순하고 빠르게 사용할 수 있다. 코드도 단순한진 모르겠지만, 개인적으로 웹표준에 가깝고, 실제로 가벼운 느낌이라 앞으로 더 사용해 보고 싶다. 단 render
메소드를 구현할때 ide의 도움을 제대로 받지 못하는 것 같아 아쉽다. 뭔가 방법이 있을 것도 같다.
import { html, LitElement, type PropertyValues } from "lit";
import { customElement, state } from "lit/decorators.js";
@customElement('lit-input')
export class LitInput extends LitElement {
@state()
private _value: string = "";
handleInput(e: InputEvent) {
this._value = (e.target as HTMLInputElement).value + '!';
const value = this.shadowRoot?.getElementById('value');
if (value) {
value.textContent = ' value: ' + this._value;
}
}
render() {
return html`<div style="display: inline" >
<input
name="LitInput"
id="LitInput"
autocomplete="off"
.value=${this._value}
@input=${this.handleInput}
/>
<span id="value"> value: </span>
</div>
`;
}
}
Svelte
따로 상태를 선언하는 문법이 있지 않고, let으로 선언하고, on:input
이벤트를 통해 상태값을 변경한다. on:input
은 input
이벤트와 동일하게 작동한다. 단순한 input이라 비교하긴 그렇지만, svelte
코드 작성할때 제일 빨리 작성했다. 사용이 직관적인듯 하다.
<script lang="ts">
import type { FormEventHandler } from "svelte/elements";
let value = '';
const handleInput: FormEventHandler<HTMLInputElement> = (e) => {
value = e.currentTarget.value + '!';
};
</script>
<div style="display: inline;">
<input
autocomplete="off"
value="{value}"
on:input={handleInput}
/>
<span> value: {value}</span>
</div>
Vanilla
<script>
const _input = document.getElementById('VanillaInput') as HTMLInputElement;
let _value = '';
_input?.addEventListener('input', (e) => {
_value = (e.target as HTMLInputElement).value + '!';
_input.value = _value;
const value = document.getElementById('value')!;
value.textContent = 'Value: ' + _value;
});
</script>
<div style="display: inline;">
<input
id="VanillaInput"
autocomplete="off"
name="VanillaInput"
/>
<span id="value" > value: </span>
</div>
실험
테스트 결과 모든 프레임워크에서 input에 입력이 발생하면, 해당 입력 뒤에 ’!’를 붙여서 상태값을 변경하는 것을 확인할 수 있다. 하지만 지우는 경우에는 조금 다르다는걸 볼 수 있다. react
와 vanilla
는 input
의 value
가 지워지지 않는 것을 확인 할 수 있다. 왜냐하면, react와 vanilla는 즉각적으로 input
의 value
를 변경하기 때문이다. 반면에 vue
, lit
, svelte
는 state의 변경이 감지되야 input
의 value
가 변경되기 때문에 지워지는 것을 확인할 수 있다. 단반향 흐름, 양방향 흐름의 차이라고 생각했지만, 그저 랜더링 메커니즘의 차이로 보인다.
만약 vue
와 svelte
에서 bind를 사용하면 어떻게 될까?
Vue input v-model
<input v-model="value" >
Svelte input bind
<input bind:value={value} />
이제 똑같이 지워지지 않는걸 확인할 수 있다.
결론
크게 결론내릴 것은 없지만, 프레임워크별로 input을 처리하는 방법을 알아 볼 수 있었고, 양방향 데이터 바인딩을 모델(데이터)의 변경과, 뷰(input)의 변경의 양방향적인 적용이라고 한다는 것을 알 수 있었다. 이번 실험은 로직에 따라서, 모델과 뷰의 상태가 끊길 수 있음을 알 수 있었다.