Lit 사용법 정리
알고보면 쉬운 lit
@customElement
@customElement("my-element")
- LitElement 를
<my-element>
처럼 사용할 수 있도록 정의해주는 역할 - 이름을 지정할때 ‘-‘사용은 필수!
customElements.define('my-element', MyElement);
와 동일한 문법-
엘리먼트의 타입을 선언해 줌으로써 다른 파일에서 해당 웹 컴포넌트를 찾을 수 있게한다.
declare global { interface HTMLElementTagNameMap { "my-element": MyElement; } }
- 자동으로 import 불가하여, 다른 파일에서 사용할 시 import 필요
@property
- 타입스크립트 사용시 프로퍼티 선언과 초기화 역할
- 값이 변경될 때마다 컴포넌트가 자동으로 다시 렌더링된다. -> 리액트의
useState
와 비슷한 개념 @property() name = "juju";
- 프로퍼티 선언시 옵션을 줄 수 있다.
- type
- 속성의 데이터 타입을 지정한다.
String
,Number
,Boolean
,Array
,Object
- 따로 설정하지 않으면 기본값 String 할당
- attribute
- 이 속성이 attribute로 사용될지 여부를 지정한다.
-
boolean string 으로 값 따로 지정 가능 - 따로 설정하지 않으면 기본값 true 할당
@property({ type: String, attribute: false }) hiddenText = 'Secret';
- ->
<my-element></my-element>
으로 외부에서 hiddenText 속성이 노출되지 않는다. - 조작하려면 JavaScript 코드에서 속성 값을 설정하거나 변경할 수 있다.
- reflect
- 속성 값이 변경될 때 해당 값이 HTML 속성에 반영된다.
@property({ type: Boolean, reflect: true }) active = false;
와 같을때,active
가true
로 변경되면<my-element active>
가 되고,false
로 변경되면<my-element>
- hasChanged : 속성 값이 변경될 때, 컴포넌트를 다시 렌더링할지 여부를 결정
@property({ type: Number, hasChanged: (newVal, oldVal) => newVal > oldVal }) counter = 0;
counter
속성이 오직 새로운 값이 현재 값보다 클 때만 변경된 것으로 간주되도록 설정
-
converter
- 값을 변환할 수 있는 옵션
- fromAttribute: HTML 속성에서 가져온 값을 내부 속성 값으로 변환
<my-element mydata='{"name":"Alice","age":30}'>
처럼mydata
속성이 지정되면,fromAttribute
메서드가 이 JSON 문자열을 JavaScript 객체로 변환하여myData
속성에 저장한다.
-
toAttribute: 내부 속성 값을 HTML 속성 값으로 변환
- 이 객체를 다시 JSON 문자열로 변환하여
mydata
속성에 반영한다.
// <my-element mydata='{"name":"Alice","age":30}'></my-element> const jsonConverter = { fromAttribute(value) { return value ? JSON.parse(value) : null; }, toAttribute(value) { return value ? JSON.stringify(value) : null; }, }; class MyElement extends LitElement { @property({ converter: jsonConverter }) myData = {}; render() { return html`<p>Name: ${this.myData.name}, Age: ${this.myData.age}</p>`; } }
- 이 객체를 다시 JSON 문자열로 변환하여
바인딩(binding)
Attribute
<input type="text" value="${this.value}" />
- HTML 속성에 값을 설정
- JavaScript에서
this.value
가 변경되면,input
요소의value
HTML 속성은 자동으로 업데이트되지 않는다. - 문자열 값 가능
- Property
<input type="text" .value="${this.value}" />
- HTML 요소의 JavaScript 프로퍼티에 값을 설정
this.value
가input
요소의value
프로퍼티와 동기화되어, JavaScript에서this.value
가 변경되면,input
요소의value
프로퍼티도 자동으로 업데이트된다.- .(dot)을 사용하여 바인딩
- 모든 유형의 값 가능
Boolean
<input type="checkbox" ?checked="${this.isChecked}" />
- ?(물음표)를 사용하여 바인딩
- Event Listener
<button @click="${this.handleClick}">Click me</button>
- 외부에서 사용시 :
<my-element .handleClick="${(event) => alert('Button clicked!')}"></my-element>
빌트인 디렉티브 (built in directive)
데이터가 비동기적으로 변하는 동적 컨텐츠를 처리하거나, 특정 조건에 따라 컨텐츠를 다르게 렌더링한다.
비동기 데이터 처리 함수
asyncReplace : 기존 값을 새로운 값으로 교체
async function* countUp() {
let i = 0;
while (true) {
yield i++;
await wait(1000);
}
}
///
///
render() {
return html`
<span>${asyncReplace(countUp())}</span>`
}
asyncAppend : 새로운 값을 기존 값 뒤에 추가
async function textStream() {
let count = 1;
while (true) {
yield `Item ${count++}`;
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
///
render() {
return html`
<span>${asyncAppend(textStream())}</span>`
}
cache : 템플릿의 일부 결과를 캐시하여 동일한 데이터가 반복될 때 재계산을 방지
const items = ["Item 1", "Item 2", "Item 3"];
render(
html`
<ul>
${cache(items.map((item) => html`<li>${item}</li>`))}
</ul>
`
);
// `items` 배열이 변경되면 템플릿이 다시 렌더링되는 구조
classMap : 객체를 사용하여 CSS 클래스 이름을 동적으로 적용한다. 객체의 키가 클래스 이름이 되고, 값이 true
일 때만 해당 클래스가 적용된다.
const classes = { active: this.isActive, disabled: this.isDisabled };
return html` <div class="${classMap(classes)}">
This is a dynamically styled element.
</div>`;
ifDefined : 값이 정의된 경우에만 HTML 속성이나 프로퍼티를 설정한다.
<my-element text=${ifDefined(this.text)}><my-element>
// text가 정의되지 않았다면 <my-element></my-element> 로만 쓰이게됨
guard : 렌더링 성능 최적화하는 데 사용
- 첫 번째 인수로 “종속성 배열”을 받고, 두 번째 인수로 업데이트할 함수(템플릿)를 받는다.
- 종속성 배열에 있는 값이 변경될 때만 업데이트 함수가 호출된다.
${guard([this.items], () => this.items.map(item => html`<li>${item}</li>`))}
live : 데이터의 실시간 업데이트를 보장
.value="${live(this.value)}"
repeat : 리스트 항목을 반복하여 렌더링
this.items = ['Item 1', 'Item 2', 'Item 3'];
//
${repeat(this.items, item => html`<li>${item}</li>`)}
styleMap : 객체를 사용하여 인라인 스타일을 적용한다. 객체의 키가 CSS 속성이 되고, 값이 해당 속성의 값
render() {
const styles = { color: this.color, fontSize: this.fontSize };
return html` <div style="${styleMap(styles)}"> Styled text </div> `;
}
templateContent : 템플릿의 콘텐츠를 다른 템플릿에 삽입할 때 사용
render() {
const myTemplate = html` <span>This is reusable content!</span> `;
return html` <div> ${templateContent(myTemplate)} </div> `;
}
unsafeHTML : HTML 문자열을 직접 삽입하여 DOM에 렌더링할 때 사용
unsafeSVG : SVG 문자열을 직접 삽입하여 DOM에 렌더링할 때 사용
보안 취약점이 발생할 수 있으므로 주의가 필요하다는 의미에서 unsafe가 붙음
until : 비동기 값이 해결될 때까지 대체 콘텐츠를 렌더링합니다. 비동기 데이터 로딩 상태를 처리하는 데 유용\
async fetchData() {
await new Promise(resolve => setTimeout(resolve, 1000));
return 'Loaded data';
}
render() {
return html`
<div> ${until(this.fetchData(), html`<span>Loading...</span>`)} </div> `;
}
라이프사이클
constructor()
- 클래스의 인스턴스가 생성될때 호출
- 객체가 메모리에는 생성되었지만, 아직 브라우저 DOM에 추가되지 않았기 때문에 constructor() 에서 DOM을 조작하려하면 오류 발생
- 첫번째 업데이트 전에 수행해야 하는 초기화 작업 수행
- @property 데코레이터를 사용하지 않는 경우 속성의 기본값 설정 및 이벤트 리스너 바인딩
-
LitElement
를 상속받는 컴포넌트의constructor
에서super()
를 호출하지 않으면 부모 클래스의 초기화가 이루어지지 않아, 자식 클래스에서 정상적으로 작동하지 않을 수 있다. super() 필수constructor() { super(); this.foo = 'foo'; this.bar = 'bar'; }
connectedCallback()
- 컴포넌트가 DOM에 추가될때 호출
- 서버에서 데이터를 불러오거나, 타이머를 시작하거나, 전역 이벤트 리스너를 추가할 때 유용
- 너무 일찍 호출되기 때문에 DOM 접근이 안전하지 않을 수 있어,
firstUpdated
나updated
를 사용
disconnectedCallback()
- 컴포넌트가 DOM에서 제거될 때 호출
- DOM에서 제거될 때 더 이상 필요하지 않은 리소스를 정리하는 역할
-
컴포넌트가 DOM에서 제거되었지만, 여전히 등록된 이벤트 리스너, 타이머 또는 다른 리소스가 남아 있으면, 브라우저가 이를 메모리에 계속 유지하므로 메모리 누수가 발생할 수 있다.
connectedCallback() { console.log('MyElement가 DOM에 추가되었습니다!'); this.addEventListener('click', this.handleClick); } handleClick() { alert('MyElement가 클릭되었습니다!'); } disconnectedCallback() { this.removeEventListener('click', this.handleClick); }
attributeChangedCallback()
attributeChangedCallback(name, oldValue, newValue)
- 컴포넌트의 특정 속성(attribute)이 추가, 제거, 또는 변경될 때마다 호출되어 컴포넌트의 상태나 동작을 업데이트하는 데 사용
-
observedAttributes()
-
attributeChangedCallback
이 동작하려면, 컴포넌트가 어떤 속성을 감시해야 하는지 알려줘야 하기때문에 여기에 정의 필요// 감시할 속성 목록을 정의 static get observedAttributes() { return ['data-title', 'data-count']; } // 속성 값이 변경될 때 호출 attributeChangedCallback(name, oldValue, newValue) { console.log(`속성 "${name}"가 ${oldValue}에서 ${newValue}로 변경`); }
-
shouldUpdate(changedProperties)
- 컴포넌트가 다시 렌더링되어야 하는지 여부를 결정합니다
- 기본적으로는 속성이나 상태가 변경될 때마다 컴포넌트가 다시 렌더링되지만, 해당 메서드를 오버라이드하여 특정 조건에서만 렌더링 가능하게 함
-
반환값이 true일때만 리렌더링 되어 불필요한 렌더링을 방지
@property({ type: String }) firstProperty = 'First'; @property({ type: String }) secondProperty = 'Second'; shouldUpdate(changedProperties) { // changedProperties: 변경된 속성들의 Map 객체 return changedProperties.has('firstProperty'); } _changeProperties() { this.firstProperty = 'One'; this.secondProperty = 'Second'; }
willUpdate()
- 컴포넌트의 속성 값이 변경되고 렌더링되기 직전에 호출
-
렌더링 전에 데이터나 상태를 업데이트하거나 특정 작업을 수행하여 최적화하거나 상태를 맞출 수 있다
@property({ type: String }) userId = ''; @property({ type: String }) userName = ''; @property({ type: Boolean }) isLoading = false; willUpdate(changedProperties) { if (changedProperties.has('userId')) { this.isLoading = true; this._fetchUserName(this.userId).then(name => { this.userName = name; this.isLoading = false; }); } } async _fetchUserName(userId) { const response = await fetch(`https://api.example.com/users/${userId}`); const data = await response.json(); return data.name; } /// <button @click=${() => this.userId = 'newUserId'}>Change User</button>
render()
- 컴포넌트의 상태나 속성에 따라 동적으로 HTML을 생성하고, 이를 DOM에 반영하는 기능
- 컴포넌트의 속성이나 상태가 변경될 때 자동으로 render() 호출하여 업데이트
firstUpdated()
- 컴포넌트가 처음으로 DOM에 추가된 후, 즉 초기 렌더링이 완료된 후에 실행
-
render()
메서드가 호출되어 DOM이 업데이트된 후 처음 한번만 호출firstUpdated() { console.log('The component has been added to the DOM!'); this.shadowRoot.querySelector('p').style.fontWeight = 'bold'; // 스타일 조작은 DOM 요소의 직접적인 조작으로 렌더링 사이클과는 별개임 }
updated(changedProperties)
- firstUpdated() 와 비슷하지만
changedProperties
라는 매개변수를 가지기 때문에 어떤 속성이 변경되었는지 추적할 수 있음 -
LitElement
에서updated()
메서드를 오버라이드할 때, 기본 동작을 보존하고 제대로 작동하게 하려면super
호출이 권장됨updated(changedProperties) { super.updated(changedProperties); if (changedProperties.has('message')) { console.log(`Message changed to: ${this.message}`); } }
updateComplete
- 컴포넌트의 템플릿이 완전히 렌더링되고, 모든 DOM 업데이트가 완료될 때까지 기다린다.
-
Promise를 반환하여,
await
와 함께 사용하여 렌더링 완료를 비동기적으로 기다린다.async _changeMessage() { this.message = 'Message Changed!'; await this.updateComplete; console.log('Rendering complete. Message is now:', this.message); }