HTML 사용자 정의 요소에서 템플릿의 다양한 방법

나는 React와 Angular에서 몇 년 동안 공부했고 HTML 사용자 정의 요소를 배우기로 결정했다. 이것은 브라우저의 원본 구성 요소 프레임워크이다."이건 쉬울 것 같아."라고 나는 생각했다. "{{substitutions}}를 어디에 두었는지 알려주면 거기서부터 시작할 거야."
하.
#1점: 이 교체는 템플릿 엔진의 일부이며, 이 프레임에는 템플릿 엔진이 없습니다.본고에서 나는 구성 요소의 HTML 부분을 처리할 때 많은 선택을 상세하게 소개할 것이다. 예를 들어 구성 요소의 형식을 저장하고, 어떻게 매개 변수화하는지, 언제 사용하는지 <template> 표시를 하고, 어떻게 보여주고 다시 보여주는지, 그리고 그것을 언제 다시 보여주는지 어떻게 알 수 있는지.
이 예시에서, 나는 제로 의존적인 단일 파일 구성 요소를 사용하기 때문에, HTML을 fetch ed 코드와 분리하지 않을 것이다.나는 문서 작성에 약간의 Typescript를 사용할 것이다.나는 또한 항상 음영DOM을 사용하고 HTML/CSS를 코드 파일의 밑에 보존하기를 원한다. 이것은 그것이 간단하기 때문이 아니라 어렵기 때문이다.
우리의 예는 import 구성 요소입니다. flexbox의 줄 정렬에만 하위 항목의 스타일을 설정합니다.이것은 하위 노드를 입력으로 받아들이기 때문에, 우리는 <flex-row> 표시가 본 컴퓨터 프레임워크가 이 하위 노드를 어디에 두었는지 표시할 것이다.사용 예:
<flex-row>
  <div>Left sidebar.</div>
  <div>Main content, containing a very long text that wraps around the viewport multiple times. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
</flex-row>

가장 간단한 예
customElements.define('flex-row', class extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }).innerHTML = /*html*/`<slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start;"></slot>`;
  }
})
<slot> 정의를 최종 class 호출에 직접 연결함으로써, 우리는 실제적으로 클래스를 명명할 필요가 없다..define 그림자 DOM을 만들고 인용을 되돌려줍니다. 우리는 즉시 .attachShadow 문자열을 사용하여 그것을 만들 수 있습니다..innerHTML 주석은 IDE를 알려 줍니다. 백틱 문자열은 색 문법으로 HTML로 강조해야 합니다.CSS는 내연이지만 /*html*/ 이후<style></style>도 일할 수 있다.그러나 만약 우리의 템플릿이 매우 크다면, 우리는 그것을 코드에서 파일의 밑부분으로 옮길 수 있다.

틀을 밑에 놓다
customElements.define('flex-row', class extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }).innerHTML = template();
  }
})

function template() {
  return /*html*/`<slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start;"></slot>`;
}
템플릿을 파일 끝으로 이동하면 더욱 깨끗해 보입니다.그러나 우리가 </slot>를 사용한다면 너무 일찍 .define에 전화를 걸면 template() is undefined와 오류가 발생할 수 있습니다.그래서 여기서 const template = () => ...를 사용해야 승급할 수 있습니다.또는 나는 이 종류를 명명하고 function 호출을 맨 밑으로 옮길 수 있다.잊기 쉬운 곳.
어쨌든, 템플릿에 매개 변수 .define 를 추가해서 논쟁을 진행합시다.
<flex-box wrap='nowrap'>에 매개변수 추가하기
customElements.define('flex-row', class extends HTMLElement {
  connectedCallback() {
    const wrap = this.getAttribute('wrap');
    this.attachShadow({ mode: 'open' }).innerHTML = template(wrap);
  }
);

function template(flexWrap: string) {
  return /*html*/`<slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start; ${flexWrap ? `flex-wrap: ${flexWrap};` : ''}"></slot>`;
}
flex-wrap에 음영을 추가하는 것은 connectedCallback에 음영을 추가하는 것이 아니라 원소의 속성이 준비되어 있다는 차이점이다.우리의 템플릿 함수는 어떤 함수처럼 매개 변수화되어 있다.
지금 이 기능을 사용하는 것은 매우 이상하다.왜 constructor 아니면<flex-box wrap='nowrap'> 또는 <flex-box nowrap>입니까?그것 또한 영원히 다시 과장되지 않을 것이다.이 두 가지 기능을 추가합시다.

렌더링 방법 및 속성 모니터링
customElements.define('flex-row', class FlexRow extends HTMLElement {
  connectedCallback() {
    this.wrap = typeof this.getAttribute('wrap') === 'string' ? 'wrap' : typeof this.getAttribute('nowrap') === 'string' ? 'nowrap' : '';
    this.attachShadow({ mode: 'open' }).innerHTML = template(this.wrap);
  }

  #wrap = '';
  get wrap() { return this.#wrap; }
  set wrap(v) { this.#wrap = v; FlexRow.render(this); };

  static render({ shadowRoot, wrap }: FlexRow) {
    if (shadowRoot) shadowRoot.innerHTML = template(wrap);
  }
});

function template(flexWrap: string) {
  return /*html*/`<slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start; ${flexWrap ? `flex-wrap: ${flexWrap};` : ''}"></slot>`;
}
여기 열어야 할 게 많아요.
1) 우리는 init에서 두 개의 속성<flex-box wrap>wrap을 검사한다.
2) 우리는 적당한 문자열을 자바스크립트의 개인 변수nowrap에 저장할 것이다.(Typescript는 브라우저 지원이 아직 완료되지 않았으나, 내 다음 예제에서는 사용하지 않을 것입니다.)
3) 외부 코드가 속성을 변경할 때 반응할 수 있도록 Getter/setter 쌍을 만듭니다.
4) 정적this.#wrap 함수를 다시 렌더링하기 위해 설명했습니다.
현재 소비자들은 render<flex-row wrap> 등 짧은 속성을 사용하여 우리의 구성 요소를 초기화할 수 있지만 언제든지 속성<flex-row nowrap>을 사용하여 변경할 수 있다.
만약 우리가 지원 필드ourElement.wrap = 'nowrap';를 개인 구성원으로 설정하지 않았다면 소비자들은 직접 #wrap로 그것을 클릭한 후에 왜 아무것도 작동하지 않는지, 왜 브라우저 devtools에 ourElement._wrap = ... 속성이 있고 wrap 속성이 있는지 알 수 없을 것이다.
이와 유사하게, 우리는 렌더링 함수를 정적으로 해서 그것을 숨긴다.이것은 확실히 명명류가 필요하지만, 보답으로 우리는 대량의 _wrap 접두사를 사용하지 않도록 파라미터를 분해할 수 있다.
두 번째: 속성과 속성은 HTML 사용자 정의 요소에서 자동으로 서로 대칭복사되지 않습니다.생각해 봐this..value: 속성은 초기 값을 가지고 실제로는 상수이며 속성만 사용자에 따라 업데이트된다.HTML 사용자 정의 요소에서는 모든 것이 이렇습니다.
그러나 만약에 우리가 일부 렌즈를 원한다면, 특히 CSS는 flex행이 현재 포장 중인지 여부에 따라 자신을 바꿀 수 있다면 어떻게 해야 합니까?CSS는 속성이 아니라 속성만 봅니다...

렌더링 방법 및 속성 모니터링 2
class FlexRow extends HTMLElement {
  connectedCallback() {
    this.wrap = typeof this.getAttribute('wrap') === 'string' ? 'wrap' : typeof this.getAttribute('nowrap') === 'string' ? 'nowrap' : '';
    this.attachShadow({ mode: 'open' }).innerHTML = template(this.wrap);
  }

  get wrap() { return this.getAttribute('wrap'); }
  set wrap(v) { this.setAttribute('wrap', v || ''); render(this); };
}

const render = ({ shadowRoot, wrap }: FlexRow) => shadowRoot ? shadowRoot.innerHTML = template(wrap) : null

const template = (flexWrap: string | null) =>
  /*html*/`<slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start; ${flexWrap ? `flex-wrap: ${flexWrap};` : ''}"></slot>`;

customElements.define('flex-row', FlexRow);
여기 몇 가지 일이 있습니다.
속성이 변경될 때 속성을 업데이트해야 하기 때문에 이 속성도 지원 필드일 수 있습니다.
예를 들기 위해서, 나는 <input type=text value='initialVal' />를 맨 밑으로 옮겼고, 화살표 함수를 사용하여 .define를 표시할 수 있도록 허락했다.이것은 아름다운 단행 프로그램이기 때문에 우리는 정적 프로그램과 같은 방법을 사용할 것이다template().
새로운 기능이 몇 가지 문제를 가져왔다.render() 속성은 일치하는 속성이 부족하고 nowrap 속성은 문자열'nowrap'을 받아들여 사용자를 곤혹스럽게 합니다.나는 이 설계 문제를 토론하는 것을 연기할 것이다. 왜냐하면 그것은 틀과 무관하기 때문이다. 그러나 나는 확실히 그것을 말하고 싶다.
더 중요한 것은 앞으로 wrap 또는 ourElement.attributes['wrap'] = 'wrap'를 통해 속성을 업데이트하는 것은 아무런 역할을 하지 않을 것이다. 설령 이런 속성이 처음에 작용하더라도 우리 사용자를 실망시킬 수 있다.

속성 및 속성 모니터링
class FlexRow extends HTMLElement {
  connectedCallback() {
    this.wrap = typeof this.getAttribute('wrap') === 'string' ? 'wrap' : typeof this.getAttribute('nowrap') === 'string' ? 'nowrap' : '';
    this.attachShadow({ mode: 'open' }).innerHTML = template(this.wrap);
  }

  get wrap() { return this.getAttribute('wrap'); }
  set wrap(v) { this.setAttribute('wrap', v || ''); render(this); };

  static observedAttributes = ['wrap', 'nowrap'];

  attributeChangedCallback(name: string, oldValue: string, newValue: string) {
    if (oldValue !== newValue) render(this);
  }
}

const render = ({ shadowRoot, wrap }: FlexRow) => shadowRoot ? shadowRoot.innerHTML = template(wrap) : null

const template = (flexWrap: string | null) =>
  /*html*/`<slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start; ${flexWrap ? `flex-wrap: ${flexWrap};` : ''}"></slot>`;

customElements.define('flex-row', FlexRow);
추가 ourElement.setAttribute('wrap','wrap'), static (Required) attributeChangedCallback 에 첫 번째 인자의 가능한 값을 표시하여 속성 변경에 반응할 수 있도록 합니다.일반적으로, 우리는 이 함수에서 값을 속성에서 속성으로 복사하기 위해 observedAttributes를 사용해야 하지만, 우리의 속성은 이미 이 속성을 가리키는 Getter/setter를 사용하기 때문에 필요없다.물론 우리는 무한 순환을 검사해야 한다.
나는 본문 첫머리에 React와 Angular와 같은 간단한 교체가 없다고 말했다.템플릿 문자열 텍스트와 this[name] = newValue; 를 사용하여 문자열을 해석해 왔습니다.구성 요소가 영원히 다시 렌더링되지 않는다면, 이것은 매우 좋은 것입니다. 왜냐하면 이것은 영구적이며, 주 메뉴 표시줄과 같은 일회용이기 때문입니다.그러나 리셋을 도입할 때 문자열의 재해석은 성능에 큰 영향을 미칠 수 있다.
초기 렌더링에 대한 단일 문자열 해석과 다시 렌더링에 대한 정확한 업데이트를 통해 성능 문제를 해결할 수 있습니다.우리는 .innerHTML HTML 표기와 <template> 방법으로 이 점을 실현해야 한다.HTML과 해당 준비 단계는 여전히 파일의 맨 아래에 있습니다.

미리 컴파일된 템플릿, 최소 다시 렌더링
class FlexRow extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }).appendChild(createdTemplateNode!.content.cloneNode(true));
    updateTemplateNode(this);
  }

  get wrap() { return this.getAttribute('wrap'); }
  set wrap(v) { this.setAttribute('wrap', v || ''); this.removeAttribute('nowrap'); updateTemplateNode(this); };

  static observedAttributes = ['wrap', 'nowrap'];

  attributeChangedCallback(name: string, oldValue: string, newValue: string) {
    if (oldValue !== newValue) updateTemplateNode(this);
  }
}

const txt = /*html*/`
  <template>
    <slot style="display: flex; flex-direction: row; flex: 1 1 auto; align-items: flex-start;"></slot>
  </template>
`;

const createdTemplateNode = new DOMParser().parseFromString(txt, 'text/html').head.querySelector('template');

const updateTemplateNode = ({ shadowRoot, wrap }: FlexRow) => {
  const node = shadowRoot?.firstElementChild as HTMLElement;
  if (!node) return;
  if (wrap) node.style.setProperty('flex-wrap', wrap);
  else node.style.removeProperty('flex-wrap');
}

customElements.define('flex-row', FlexRow);
우리는 .cloneNode명명renderupdateTemplateNode으로 바꾸고 createdTemplateNode명명과 평행하며 암시render가 전체 이야기라는 것을 피할 것이다.
우리의 template() 함수는 현재 txt 라는 변수일 뿐이며, 매개 변수는 한 번만 사용되기 때문에 더 이상 받아들일 수 없습니다.이 내용은 <template> 표시에 포장되어 있습니다. 현재 DOMParser가 텍스트를 분석하고 연결을 끊은 노드 트리를 되돌려주기 때문입니다.그리고 우리는 즉시 <template> 표시를 위해 검색을 선택한 다음, 지금부터 이 나무는 우리 clone 의 모든 실례에 있는 나무가 될 것이다.
일단 템플릿의 주요 부분이 자리를 잡으면, 우리는 <flex-row> 변경이 필요한 곳에서만 그것을 만질 수 있다.이 예는 updateTemplateNode 매개 변수만 있기 때문에, 우리의 업데이트는 단지 하나의 코드일 뿐입니다. 정확한 노드의 스타일 속성을 설정하거나 삭제하는 데 사용되며, 이 노드는 항상 루트 노드 이외의 첫 번째 하위 노드입니다.
그것의 유형과 복잡성은 약간 높지만, 다시 렌더링할 때soleflex-wrap는 문자열을 만들고 해석하는 속도보다 훨씬 빠르며, 특히 상기 문자열이 다른 사용자 정의 구성 요소를 호출할 때보다 빠르다.

어느 것이 가장 좋아요?
사실 나는 마지막 예를 계속 사용하는 것을 건의하지 않는다.기본 UI 프레임워크가 아닌 HTML 사용자 정의 요소의 장점 중 하나는 제로 의존적이고 제로 라이브러리의 구성 요소를 만들 수 있다는 것이다.이것은 마이크로 전단을 만드는 데 매우 유용하다.그러나 우리가 속도를 최적화하려고 시도할 때 추가 코드 자체는 메모리와 안내 시간을 소모하고 모든 구성 요소 간에 같은 코드를 공유할 수 있는 라이브러리가 없으면 원가가 배로 증가한다.
여기까지 읽어줘서 고마워요.나는 네가 나의 작은 여행이 매우 재미있다고 느끼기를 바란다.

좋은 웹페이지 즐겨찾기