Topic = Svelte5 Rune API 정리 해보기.
Rune 이란?
Rune은 Svelte 5 부터 도입된 반응성 시스템을 제공하는 구문으로 상태, 상태 이벤트, 종속성 등의 기능을 함수 형태로 캡슐화 하여 제공하는 것 이다.
상태 - $state, $derived
- 상태관리 Rune 으로 정의한 변수는 DOM 반응성을 갖게 되어 변경이 발생하면 자동으로 DOM 이 업데이트 된다.
- $state(), $derived() 를 통해 각각 상태, 파생된 상태를 정의할 수 있다.
변수 값이 1 이었던 count 변수의 값이 2로 바뀌면 기존 DOM 에서 보이던 1 을 2 로 업데이트 해주어야 한다. 이때 변수를 $state() 를 통해 상태로 정의해두면, 해당 상태의 값이 바뀔 때 자동으로 DOM이 업데이트 되게 된다.
[ $state ]
$state 룬을 사용하면 해당 요소가 변경될 때, UI 반응성을 갖는 반응형 상태를 정의할 수 있다,
🌟$state 로 정의된 객체나 배열은 은 기본적으로 Deep Reactive State Proxy 로 관리된다.
- Deep State
- 상태의 중첩된 속성을 포함하여 모든 속성에 대해 반응성을 자동으로 제공한다.
- Proxy
- 내부적으로 JS의 Proxy 객체를 사용하여 속성 접근과 변경을 감지하고 반응성을 관리한다.
- 객체의 중첩된 값이나 배열의 특정 항목이 변경되더라도, 해당 변경 사항을 자동으로 추적하여 반응형 업데이트를 트리거 한다.
[ $state.row() ]
만약, 배열이나 객체의 일부 요소에 대한 변경은 막지만, 다른 배열이나 객체를 재할당 하는 것을 허용하려면, $state.row() Rune을 사용할 수 있다.
[ $derived ]
$derived 는 특정 상태의 종속성을 갖는 파생된 상태를 만드는 것이다. 이는 종속 상태가 변경되면, 파생 상태도 재계산 되어 변경된다.
복잡한 계산로직이 필요한 경우에는 $derived.by( () => {} ) 와 같이 사용할 수도 있다.
🌟 $derived 내부에 선언된 모든 상태는 동기적으로 상태의 종속성이 된다. ( 여러 상태도 ok )
❌ $derived 내부에서 종속 상태 값을 변경할 수 없다 => 종속 상태 값을 변경하면 다시 파생상태 로직이 실행되므로, 순환되는 문제가 발생하기 때문에 컴파일 시점에서 막아두었다. 아래에서 다룰 $effect() 에서는 참조하는 상태의 값을 변경해도 컴파일 시점에 오류가 발생하지 않기 때문에 적어둠.
$props(), $bindable()
$props() 는 컴포넌트의 properties 에 해당하는 속성으로, 상위 컴포넌트에서 하위 컴포넌트 사용 시 생성자로 넘겨주는 인수들을 하위 컴포넌트에서 사용하기 위해 사용하는 속성이다.
참고용...
[ 프로퍼티의 디폴트 값 설정하기 ]
- 상위 컴포넌트에서 프로퍼티를 넘겨주지 않았을 때 기본 값을 설정할 수 있다.
let { userName = "DEFAULT" } = $props()
[ Rest props ]
- 상위 컴포넌트에서 넘겨준 속성들은 Rest props 를 통해 일괄적으로 사용할 수 있다.
let { a, b, c, ...styles } = $props()
[ $bindable() ]
하위 컴포넌트에서 $props 를 통해 받은 속성은 기본적으로 부모 컴포넌트에 영향을 미치지 못한다. 즉, 부모 컴포넌트에서 넘겨준 상태 값 부터는 하위 컴포넌트 개별적인 속성으로 처리된다. 부모 컴포넌트에서 하위 컴포넌트로 내려준 속성의 값이 변경되었을 때, 부모 컴포넌트의 해당 속성도 변경되기를 원한다면, bind: 와 $bindable() 을 통해 양방향 바인딩을 지정해주어야 한다.
$effect()
$effect() 는 상태, 파생 상태를 트래킹하며, 트래킹하는 상태가 액세스 되거나, 상태가 변경되는 등의 이벤트가 발생 시 지정해둔 로직을 실행시키는 기능이다.
기본적으로 $effect() 는 정의된 컴포넌트의 라이프 사이클에 종속된다. 컴포넌트가 Destroy 되면, effect rune 도 실행되지 않게 되는 것이다.
🌟 $effect() 내부에 선언된 모든 상태는 동기적으로 트래킹의 대상이 된다. ( 여러 상태도 ok )
- effect는 컴포넌트가 초기화 될 때 최초 1번 실행된다.
- effect() 내부 함수의 반환 함수는 effect() 가 실행되기 전, 실행 후 실행된다.
[ $effect.pre() ]
$effect.pre() 는 상태가 변동되어 DOM이 업데이트 되기 전에 실행되는 함수이다.
[ $effect.traking() ]
$effect.traking Rune은 실행되는 위치의 현재 코드가 $effect(), $derived, HTML 템플릿과 같이 종속성을 추적이 활성화된 흐름(컨텍스트) 내부에서 실행되고 있는지 상태를 Boolean 형태로 반환한다.
❓HTML 템플릿은 왜 종속성을 추적하는 컨텍스트라고 보는 것일까?
➡️ HTML 템플릿은 상태가 변경될 때, 변경된 상태를 UI 에 업데이트 하기 위해서 DOM 간의 연계를 유지하고, 내부적으로 $effect() 처럼 상태를 추적하는 컨텍스트 이기 때문에 템플릿 내부에서의 $effect.tracking() 를 실행하면 true 값을 반환하게 된다.
전역 상태 관리 [ .svelte.js 모듈, Store ]
상위 컴포넌트 - 하위 컴포넌트 구조가 커지게 되면, 중간 컴포넌트 중에서는 필요하지 않은 속성이 있지만, 하위 컴포넌트에서 $props 로 받아 사용하기 위해 해당 구조 내의 모든 컴포넌트가 속성을 전달 - 전달 하는 것은 번거롭다. 공통적으로 사용하는 전역 상태를 만들 수 있는데. Store 방식과 Rune 을 사용하는 .svelte.js 모듈을 사용하는 방식이 있다.
[ .svelte.js 모듈 With Rune Store ]
- JS 모듈로 부터 import 로 가져온 객체는 기본적으로 읽기 전용이기 때문에 값을 재할당 할 수 없으며, bind: 로 지정이 불가능하기 때문에 객체형태로 값을 정의해야한다.
[ .svelte.js 모듈로 전역 상태 관리하기 ]
- $derived() Rune은 .svelte.js 모듈 내부에서 작성하기 위해서는 export function 내부에 변수로 선언해두고, 해당 변수를 반환하는 식으로 작성해주어야 한다.
🌟 .svelte.js 확장자 내부에서는 상태 Rune 을 사용할 수 있다 ( $state(), $derived() ) 반면에, 상태 이벤트 Rune $effect() 는 .svelte 확장자 파일에서만 사용할 수 있다. 이유는 $effect() 는 컴포넌트의 런타임 환경에서 동작하며, 상태 종속성을 추적하는 것이다. 즉, $effect() 가 정의된 컴포넌트의 라이프 사이클을 따르는 것인데 .svelte.js 파일의 경우 모듈로 인식되어지므로 Svelte 컴포넌트의 라이플 사이클이 존재하지 않게 되므로 내부에서 사용할 수 없다.
- 🟥내부에서 effect() 를 사용할 수 없다는 것은, svelte/store 에서 제공하는 wriable, readble 등의 스토어와 같이 store.js 파일 내부에서 store.subscribe( value => { 로직 } ) 와 같이 작성할 수 없는 것을 의미한다. 아예 $effect() 를 .svelte.js 모듈에 작성할 수 없다는 의미가 아님!
- https://svelte.dev/docs/svelte/$effect#$effect.tracking .svelte.ts 내부에서 $effect() 를 통해 svelte/store 의 readable 기능구현 예시인데 복잡하다.
위에 🟥 항목에서 다룬 것 처럼, svelte/store 의 writable, readable 은 subscribe() 메서드를 통해 store.js 모듈 내부에서 상태 변경에 따른 콜백 이벤트를 내부에서 실행할 수 있었다. 반면, Rune의 $state 는 subscribe 와 같이 변경에 따른 콜백 이벤트를 실행하는 기능은 없는 것 같다. 때문에 다음과 같은 방법으로 시도 해보았다.
1. .svelte.js 모듈 내부에 함수 선언하고 $state() 내부 상태 관리 메서드 안에서 실행 시킨다.
❌ 정상작동은 되지만, 상태 변화를 감지해서 자동으로 콜백을 실행해주는 subscribe 의 형태가 아니여서 실패...
function callbackA(count) {
console.log(count)
}
export const counter = $state(
{
count: 0,
addCount() {
this.count++
callbackA(this.count)
},
deleteCount() {
this.count--
callbackA(this.count)
}
}
);
2. derived()
상태가 변경되면 콜백 이벤트를 실행할 상태에 파생 상태를 만들어 export 한다. $derived.by() 함수 내부에서 콜백 이벤트를 정의하고 실행한다.
❌심각한 문제 ❌ ==> 여러 컴포넌트에서 사용하게 되면, .derived 가 여러번 실행되는 문제가 발생한다.
3. $effect.root 활용
- HTML 템플릿이 빈 svelte 컴포넌트를 만들어서 .svete.js 모듈 내부의 상태를 추적하도록 하고, 변경이 발생하면 실행될 로직을 작성한다.
[ counter.svelte.js ]
export const counter = $state(
{
count: 0
}
);
[ CounterSubscribe.svelte ]
<script>
import {counter} from "./list.svelte.js";
$effect.root( () => {
$effect( () => {
// observe 객체가 변경이 되면 특정 로직이 실행
console.log("ROOT",counter.count)
})
return () => {
console.log("EFFECT END")
}
})
</script>
- 관련 로직이 시작되는 최상위 컴포넌트에 해당 컴포넌트를 등록해둔다...
<script>
import LoginPage from "./components/login/LoginPage.svelte";
import CounterSubscribe from "./store/CounterSubscribe.svelte";
</script>
<CounterSubscribe/>
<LoginPage/>
<style lang="scss">
</style>
하위 컴포넌트에서 count에 다른 값을 할당해도 정상적으로 작동한다.
❌ 이 방법도 상태 관리 변경에 따른 콜백 함수가 많아지면 상위 컴포넌트에 필요없는 컴포넌트들이 덕지덕지 붙게 되는 문제가 발생한다
.svlete.js 모듈 내부의 Rune 활용해서 store.subscribe 처럼 만들기에는 어려운 것 같다... 복잡한 로직이 필요한 경우에는svelte/store 를 사용하도록 하자.
[Reference]
'웹개발 - Front 관련' 카테고리의 다른 글
[ Svelte5 ] - 1. 기본 Svelte 컴포넌트 구성 요소, HTML 템플릿 문법 (0) | 2024.11.26 |
---|---|
[메모] Svelte + SortableJS Dom 중복 업데이트 오류 (0) | 2024.11.24 |
[TIL] CSS / word-break [ 문자열 단어기준 / 글자기준 줄바꿈 속성 ] (2) | 2024.03.31 |
[TIL] JavaScript - InterSectionObserver - 속성 및 기본 사용 편 (0) | 2024.03.30 |
JS Library - Swiper.js (1) 개념, 사용방법, 속성들 (0) | 2024.03.01 |