@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; 와 같을때, activetrue로 변경되면 <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>`;
        }
      }
      



바인딩(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.valueinput 요소의 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 접근이 안전하지 않을 수 있어, firstUpdatedupdated를 사용


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);
    }