lit-element 요약본

카탈로그
  1. 1. lit-element 개발환경
    1. 1.1. lit-element 모듈 설치
    2. 1.2. Javascript 작성
    3. 1.3. 불러오기
      1. 1.3.1. HTML
      2. 1.3.2. Javascript
    4. 1.4. babel 설정
      1. 1.4.1. 설치
      2. 1.4.2. babel.config.js 설정
  2. 2. 주의사항
  3. 3. 사용하기
    1. 3.1. prop
      1. 3.1.1. converter
      2. 3.1.2. type
      3. 3.1.3. attribute (attr -> prop)
      4. 3.1.4. reflect (prop -> attr)
      5. 3.1.5. noAccessor
      6. 3.1.6. hasChanged
    2. 3.2. loop
    3. 3.3. conditionals
  4. 4. 바인딩
    1. 4.1. textContent 바인딩
    2. 4.2. attribute 바인딩
    3. 4.3. boolean attribute 바인딩
    4. 4.4. property 바인딩
    5. 4.5. event handler 바인딩
  5. 5. <slot></slot> light Dom 렌더링
    1. 5.1. 설정(일반)
    2. 5.2. 설정(네이밍)
    3. 5.3. 사용(일반)
    4. 5.4. 사용(네이밍)
  6. 6. 일반적인 엘리먼트 태그 사용하기
    1. 6.1. import 이용시
  7. 7. CSS 사용
    1. 7.1. css``
    2. 7.2. <style>태그 사용
    3. 7.3. 외부 스타일시트 사용
  8. 8. shadow Dom에서의 CSS 작성법
    1. 8.1. :host(...)
    2. 8.2. ::slot
    3. 8.3. 커스텀 엘리먼트명으로 CSS 작성
    4. 8.4. CSS Value 적용
  9. 9. Event
    1. 9.1. @이벤트명을 이용
    2. 9.2. Dom으로 추가되기 전에 받는 이벤트는 constructor()에서 선언
    3. 9.3. firstUpdated()
    4. 9.4. connectedCallback()
    5. 9.5. discconectedCallback()
    6. 9.6. custom event
    7. 9.7. standard event
    8. 9.8. 이벤트 버블링
      1. 9.8.1. 버블링 확인
    9. 9.9. e.target
    10. 9.10. composedPath
    11. 9.11. shadow root 통과
  10. 10. LifeCycle
    1. 10.1. requestUpdate()
    2. 10.2. performUpdate()
    3. 10.3. shouldUpdate(changedProp)
    4. 10.4. update(changedProp)
    5. 10.5. render()
    6. 10.6. firstUpdated(changedProp)
    7. 10.7. updated(changedProp)
    8. 10.8. updateComplete()
  11. 11. npm 모듈로 만들기
    1. 11.0.1. npm packages 가이드에 따라 작성
    2. 11.0.2. 한글 가이드 블로그 글
  • 12. npm 모듈 사용하기
  • 13. Polyfill
  • 실제 써먹을 것 Code만 간략히 적기

    lit-element 개발환경

    lit-element 모듈 설치

    1
    npm install lit-element

    Javascript 작성

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { LitElement, html } from 'lit-element';

    class MyElement extends LitElement {
    // lit-element관련 및 이벤트핸들러 입력 영역

    render(){
    return html`
    <p>A paragraph</p>
    `;
    }

    // 커스텀 함수 입력 영역
    }

    customElements.define('my-element', MyElement);

    불러오기

    HTML

    1
    <script type="module" src="/path/to/my-element.js"></script>

    Javascript

    1
    import './my-element.js';

    babel 설정

    설치

    1
    2
    3
    npm install --save-dev @babel/core
    npm install --save-dev @babel/plugin-proposal-class-properties
    npm install --save-dev @babel/plugin-proposal-decorators

    babel.config.js 설정

    1
    2
    3
    4
    5
    6
    const plugins = [
    '@babel/plugin-proposal-class-properties',
    ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true } ],
    ];

    module.exports = { plugins };

    주의사항

    • lit-element은 virtual Dom과 다르지만, Dom을 완전히 직접 조작하는 것은 비효율적이다.
      (가변형 데이터는 prop으로 관리하라)

    사용하기

    prop

    1
    2
    3
    4
    5
    static get properties() {
    return {
    myProp: String // converter, type, attribute, reflect, noAccessor, hasChanged;
    };
    }

    converter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    prop1: {
    converter: {
    fromAttribute: (value, type) => { // attr 변경시 실행

    },
    toAttribute: (value, type) => { // prop 변경시 실행

    }
    }
    }

    type

    1
    2
    3
    4
    5
    6
    7
    static get properties() {
    return {
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean }
    }
    }

    attribute (attr -> prop)

    1
    2
    3
    4
    5
    6
    7
    8
    // Observed attribute will be called my-prop
    myProp: { attribute: 'my-prop' }

    // No observed attribute for this property
    myProp: { attribute: false }

    // observed attribute for this property (default)
    myProp: { attribute: true }

    reflect (prop -> attr)

    1
    myProp: { reflect: true }

    noAccessor

    1
    2
    3
    static get properties() { 
    return { myProp: { type: Number, noAccessor: true } };
    }

    noAccessor가 true면, prop이 getter와 setter 접근자에 의해 변경된다.
    noAccessor가 false면 getter와 setter가 사용되지 않는다.

    hasChanged

    1
    myProp: { hasChanged: true }

    true가 반환되면, update를 실행한다.
    false가 반환되면, 변화가 없다는 뜻이다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    myProp: {
    type: Number,
    hasChanged(newVal, oldVal) {
    if (newVal > oldVal) {
    console.log(`${newVal} > ${oldVal}. hasChanged: true.`);
    return true;
    }
    else {
    console.log(`${newVal} <= ${oldVal}. hasChanged: false.`);
    return false;
    }
    }
    }};

    loop

    1
    2
    3
    html`<ul>
    ${this.myArray.map(i => html`<li>${i}</li>`)}
    </ul>`;

    conditionals

    1
    2
    3
    4
    5
    html`
    ${this.myBool?
    html`<p>Render some HTML if myBool is true</p>`:
    html`<p>Render some other HTML if myBool is false</p>`}
    `;

    바인딩

    • textContent: <p>${...}</p>
    • attribute: <p id="${...}"></p>
    • boolean attribute: ?checked="${...}"
    • property: .value="${...}"
    • event handler: @event="${...}"

    textContent 바인딩

    1
    html`<div>${this.prop1}</div>`

    attribute 바인딩

    1
    html`<div id="${this.prop2}"></div>`

    속성 값은 항상 문자열 또는 문자열로 반환될 수 있는 값이여야 한다.

    boolean attribute 바인딩

    1
    html`<input type="checkbox" ?checked="${this.prop3}>i like pie</input>"`

    true면 attribute로 추가되고, false면 사라진다.

    property 바인딩

    1
    html`<input type="checkbox" .value="${this.prop4}" />`

    event handler 바인딩

    1
    html`<button @click="${this.clickHandler}">pie?</button>`

    <slot></slot> light Dom 렌더링

    설정(일반)

    1
    2
    3
    4
    5
    6
    7
    render() {
    return html`
    <div>
    <slot></slot>
    </div>
    `;
    }

    설정(네이밍)

    1
    2
    3
    4
    5
    6
    7
    render() {
    return html`
    <div>
    <slot name="one"></slot>
    </div>
    `;
    }

    사용(일반)

    1
    2
    3
    4
    5
    <my-element>
    <p>Render me</p>
    <p>Me too</p>
    <p>Me three</p>
    </my-element>

    사용(네이밍)

    1
    2
    3
    <my-element>
    <p slot="one">slot one</p>
    </my-element>

    일반적인 엘리먼트 태그 사용하기

    예를 들면, <header>, <article>, <footer> 을 이용해 템플릿 구성하는 방법
    커스텀 엘리먼트 특정 define 명령어와 HTML에서 is attr로도 구현 가능한 걸로 앎.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class MyPage extends LitElement {
    render() {
    return html`
    ${this.headerTemplate}
    ${this.articleTemplate}
    ${this.footerTemplate}
    `
    }

    get headerTemplate() {
    return html`<header>Header</header>`
    }

    get articleTemplate() {
    return html`<article>Article</article>`
    }

    get footerTemplate() {
    return html `<footer>Footer</footer>`
    }
    }

    import 이용시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { LitElement, html } from 'lit-element';

    class MyArticle extends LitElement {
    render() {
    return html`
    <article>article</article>
    `;
    }
    }
    customElements.define('my-article', MyArticle);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import './my-header.js';
    import './my-article.js';
    import './my-footer.js';

    class MyPage extends LitElement {
    render() {
    return html`
    <my-header></my-header>
    <my-article></my-article>
    <my-footer></my-footer>
    `;
    }
    }

    CSS 사용

    • ★추천★: css``를 이용한 선언 (static styles property)
    • render 안에 <style> 태그를 이용한 방법
    • 외부 스타일 시트를 이용한 방법 <link rel="stylesheet" href="..." />

    css``

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class myElement extends LitElement {
    static get styles() {
    const mainColor = css`red`
    const unSafeStr = `red`

    return css`
    :host {
    display: block;
    color: ${mainColor};
    background-color: ${unsafeCSS(unSafeStr)};
    }
    `
    }
    }

    // or 여러개의 CSS 선언시

    class MyElement extends LitElement {
    static get styles() {
    return [ css`:host { display: block; }`, ...]
    }
    }

    <style>태그 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import {LitElement, property} from 'lit-element';

    class MyElement extends LitElement {
    const mainColor = css`blue`;

    render() {
    return html`
    <style>
    :host {
    color: ${this.mainColor};
    }
    </style>
    `;
    }
    }

    외부 스타일시트 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import {LitElement} from 'lit-element';

    class MyElement extends LitElement {
    render() {
    return html`
    <link rel="stylesheet" href="./styles.css">
    `;
    }
    }

    shadow Dom에서의 CSS 작성법

    :host(...)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    :host {
    display: block;
    color: blue;
    }

    :host(.important) {
    color: red;
    font-weight: bold;
    }

    ::slot

    1
    2
    ::slotted(*) { font-family: Roboto; }
    ::slotted(span) { color: blue; }

    커스텀 엘리먼트명으로 CSS 작성

    1
    2
    3
    4
    5
    my-element { 
    font-family: Roboto;
    font-size: 20;
    color: blue;
    }

    :host보다 우선순위가 높다.

    CSS Value 적용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <!DOCTYPE html>
    <html>
    <head>
    <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
    <title>lit-element code sample</title>
    <style>
    body {
    --theme-primary: green;
    --theme-secondary: aliceblue;
    --theme-warning: red;
    --theme-font-family: Roboto;
    }

    my-element {
    --my-element-text-color: var(--theme-primary);
    --my-element-background-color: var(--theme-secondary);
    --my-element-font-family: var(--theme-font-family);
    }

    my-element.warning {
    --my-element-text-color: var(--theme-warning);
    }
    </style>
    </head>
    <body>
    <my-element></my-element>
    <my-element class="warning"></my-element>
    </body>
    </html>

    Event

    @이벤트명을 이용

    1
    2
    3
    render() {
    return html`<button @click="${this.handleClick}">`;
    }

    Dom으로 추가되기 전에 받는 이벤트는 constructor()에서 선언

    1
    2
    3
    4
    constructor() {
    super();
    this.addEventListener('DOMContentLoaded', this.handleLoaded);
    }

    firstUpdated()

    1
    2
    3
    firstUpdated(changedProperties) {
    this.addEventListener('click', this.handleClick);
    }

    connectedCallback()

    discconectedCallback()

    1
    2
    3
    4
    5
    6
    7
    8
    connectedCallback() {
    super.connectedCallback();
    document.addEventListener('readystatechange', this.handleChange);
    }
    disconnectedCallback() {
    document.removeEventListener('readystatechange', this.handleChange);
    super.disconnectedCallback();
    }

    custom event

    1
    <my-element @my-event="${(e) => { console.log(e.detail.message) }}"></my-element>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class MyElement extends LitElement {
    render() {
    return html`<div>Hello World</div>`;
    }
    firstUpdated(changedProperties) {
    let event = new CustomEvent('my-event', {
    detail: {
    message: 'Something important happened'
    }
    });
    this.dispatchEvent(event);
    }
    }

    standard event

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyElement extends LitElement {
    render() {
    return html`<div>Hello World</div>`;
    }
    updated(changedProperties) {
    let click = new Event('click');
    this.dispatchEvent(click);
    }
    }

    이벤트 버블링

    버블링 확인

    1
    2
    3
    handleEvent(e){
    console.log(e.bubbles);
    }

    e.target

    1
    <my-element onClick="(e) => console.log(e.target)"></my-element>
    1
    2
    3
    4
    5
    6
    render() {
    return html`
    <button id="mybutton" @click="${(e) => console.log(e.target)}">
    click me
    </button>`;
    }

    composedPath

    1
    2
    3
    handleMyEvent(event) {
    console.log('Origin: ', event.composedPath()[0]);
    }

    shadow root 통과

    1
    2
    3
    4
    5
    6
    7
    8
    firstUpdated(changedProperties) {
    let myEvent = new CustomEvent('my-event', {
    detail: { message: 'my-event happened.' },
    bubbles: true,
    composed: true // 이부분!
    });
    this.dispatchEvent(myEvent);
    }

    LifeCycle

    requestUpdate()

    1
    2
    3
    4
    5
    // Manually start an update
    this.requestUpdate();

    // Call from within a custom property setter
    this.requestUpdate(propertyName, oldValue);

    performUpdate()

    1
    2
    3
    4
    async performUpdate() {
    await new Promise((resolve) => requestAnimationFrame(() => resolve()));
    super.performUpdate();
    }

    shouldUpdate(changedProp)

    1
    2
    3
    4
    5
    6
    7
    shouldUpdate(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
    console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
    // prop1이 바뀔 때만, update
    return changedProperties.has('prop1');
    }

    update(changedProp)

    render()

    firstUpdated(changedProp)

    1
    2
    3
    4
    5
    6
    7
    firstUpdated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
    console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
    const textArea = this.shadowRoot.getElementById(this.textAreaId);
    textArea.focus();
    }

    updated(changedProp)

    1
    2
    3
    4
    5
    6
    7
    updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
    console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
    let b = this.shadowRoot.getElementById('b');
    b.focus();
    }

    updateComplete()

    1
    2
    3
    4
    5
    await this.updateComplete;
    // do stuff

    // or
    this.updateComplete.then(() => { /* do stuff */ });

    npm 모듈로 만들기

    npm packages 가이드에 따라 작성

    한글 가이드 블로그 글

    npm 모듈 사용하기

    1. npm install 모듈명

      1
      npm install some-package-name
    2. Javascript에서 사용

      1
      import 'some-package-name';

      HTML에서 사용

      1
      2
      3
      <script type="module">
      import './path-to/some-package-name/some-component.js';
      </script>

      Or:

      1
      <script type="module" src="./path-to/some-package-name/some-component.js"></script>
    3. 이후, READE에 따른 컴포넌트 사용

      1
      <some-component></some-component>

    Polyfill

    1. npm install

      1
      npm install --save-dev @webcomponents/webcomponentsjs
    2. HTML Script 추가

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <head>  
      <!--script src="./path-to/custom-elements-es5-loader.js"></script-->
      <script src="path-to/webcomponents-loader.js"defer></script>
      <script type="module">
      window.WebComponents = window.WebComponents || {
      waitFor(cb){ addEventListener('WebComponentsReady', cb) }
      }

      WebComponents.waitFor(async () => {
      import('./path-to/some-element.js');
      });
      </script>
      </head>

    이유는 알 수 없는데 위 튜토리얼 사항대로 하면, ie11은 지원되지 않는다.
    아래와 같이하면 되는걸로 확인

    1
    2
    3
    4
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.4.4/polyfill.min.js"></script>
    <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.2.10/bundles/webcomponents-sd-ce-pf.js"></script>
    <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.2.10/custom-elements-es5-adapter.js"></script>
    <script src="./main-bundle.js"></script>