-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Organize Component Writing Styles #44
Comments
코드를 리뷰하다보니 웹 컴포넌트 클래스에 여러 개의 메서드가 있을 때, 어떤 메서드가 |
Web Component 작성 스타일 간략 정리1. HTML 구조 정의 방식1) 정적으로 생성(1) template 사용특징:
예시 코드: // template 생성
const template = document.createElement("template");
template.innerHTML = `
<link rel="stylesheet" href="./components/footer-link/footer-link.css">
<ul class="footer-link">
<li><a id="link" href="#"><slot></slot></a></li>
</ul>
`; (2) render 함수로 분리하고 그 안에서 정적으로 생성특징:
예시 코드: // render 내에서 정적 생성
render() {
this.shadowRoot.innerHTML = `
<article class="review-item">
<section class="review-content">
<figure><img src="${this.img}" alt="Reviewer"></figure>
<blockquote>${this.text}</blockquote>
</section>
<footer class="review-footer"><figcaption>${this.name}</figcaption></footer>
</article>
`;
} 2) 동적으로 생성특징:
예시 코드: // HTML 동적 생성
this.shadowRoot.innerHTML = `<link rel="stylesheet" href="components/link-button/link-button.css">`;
const element = document.createElement("a");
element.setAttribute("href", this.getAttribute("href"));
element.classList.add("button", this.getAttribute("size"), this.getAttribute("variant"));
if (this.hasAttribute("outline")) element.classList.add("outline");
element.innerHTML = `<slot></slot>`;
this.shadowRoot.appendChild(element); 2. 속성 핸들링 방식1) 속성 변경 감지 및 처리특징:
예시 코드: // 관찰할 속성 정의
static get observedAttributes() {
return ["href"];
}
// 속성 변경 처리
attributeChangedCallback(name, oldValue, newValue) {
if (name === "href") {
this.updateLink(newValue);
}
}
updateLink(href) {
this.linkElement.setAttribute("href", href);
} 2) 속성 검증 및 기본 속성 설정특징:
예시 코드: constructor() {
super();
this.attachShadow({ mode: "open" });
this.validateAttributes(); // 속성 검증
this.setDefaultAttributes(); // 기본 속성 설정
this.render();
}
// 필수 속성 검증
validateAttributes() {
if (!this.hasAttribute("href")) throw new Error('The "href" attribute is required.');
}
// 기본 속성 설정
setDefaultAttributes() {
if (!this.hasAttribute("size")) this.setAttribute("size", "small");
if (!this.hasAttribute("variant")) this.setAttribute("variant", "ghost");
} 3) 속성 접근을 위한 getter 사용특징:
예시 코드: // getter 메서드 사용
get text() { return this.getAttribute("text") || ""; }
get name() { return this.getAttribute("name") || ""; }
get img() { return this.getAttribute("img") || ""; } 3. 초기 렌더링 방식1) 생성자 내에서 초기 렌더링특징:
예시 코드: constructor() {
super();
this.attachShadow({ mode: "open" });
this.validateAttributes();
this.setDefaultAttributes();
this.render(); // 초기 렌더링
} 2) connectedCallback 내에서 초기 렌더링특징:
예시 코드: connectedCallback() {
this.attachShadow({ mode: "open" });
this.render(); // 초기 렌더링
} 3) template을 통한 초기 렌더링특징:
예시 코드: constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true)); // 초기 렌더링
this.linkElement = this.shadowRoot.getElementById("link");
this.updateLink(this.getAttribute("href"));
} |
Example code of the best practiceclass DsExample extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.validateAttributes();
this.setDefaultAttributes();
this.shadowRoot.innerHTML = `
<style>
/* Add your CSS styling here */
</style>
<!-- Add your HTML here -->
<div>example</div>
`;
}
validateAttributes() {
/* Add validate Attributes */
}
setDefaultAttributes() {
/* Add default attributes settings */
}
}
customElements.define('ds-example', DsExample); |
const html = `
<style>
/* Add your CSS styling here */
</style>
<!-- Add your HTML here -->
<div>example</div>
`;
class DsExample extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = html;
this.validateAttributes();
this.setDefaultAttributes();
}
validateAttributes() {
/* Add validate Attributes */
}
setDefaultAttributes() {
/* Add default attributes settings */
}
}
customElements.define('ds-example', DsExample); @DaleSeo 클래스 이름 앞에도 prefix 붙이는 거 맞을까요?? |
한 단계 더 나아가, const css = `
<style>
/* Add your CSS styling here */
</style>
`
const html = `
<!-- Add your HTML here -->
<div>example</div>
`
class Example extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = css + html;
this.validateAttributes();
this.setDefaultAttributes();
}
validateAttributes() {
/* Add validate Attributes */
}
setDefaultAttributes() {
/* Add default attributes settings */
}
}
customElements.define('ds-example', Example); |
오 이 방법도 가독성이 높아지고 수정할 때 편할 것 같습니다! |
좋습니다ㅎㅎ 더 관리하기 편해질 것 같네요! |
그럼 이 스타일로 작업하겠습니다! |
html, css 분리했을 때 코드 하이라이팅이 요상하게 되는 문제가 있긴 하군요... |
저는 따로 인지는 못했는데 혹시 어떤 부분 말씀하시는 걸까요? |
|
네! /html/만 사용했습니다! /css/ 테그도 사용해야하는 지 몰랐습니다..! |
@DaleSeo @yolophg @SamTheKorean @bhyun-kim 아래와 같은 패턴은 어떠실까요? 반복되는 불필요한 코드(style tag, global style)를 함수로 빼냈습니다. import {
createCSS as css,
createHTMLWithGlobalCSS as html,
} from "../html-css-utils";
const style = css`
/* Default: Extra-small devices such as small phones (less than 640px) */
a {
display: flex;
...
`;
const htmlContent = html`
<a>
<slot></slot>
</a>
`;
class ButtonLink extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = htmlContent + style; 여기서 사용된 유틸 함수는 다음과 같습니다./**
* Processes a template literal to combine strings and interpolated values into a single HTML string.
*
* @param {TemplateStringsArray} strings - An array of string literals.
* @param {...string} values - Interpolated values within the template literal.
* @returns {string} The combined HTML string.
*/
export function createHTMLWithGlobalCSS(strings, ...values) {
const GlobalCSS = `<link rel="stylesheet" href="./global-styles.css" />`;
let htmlString = strings[0];
for (let i = 0; i < values.length; i++) {
htmlString += values[i] + strings[i + 1];
}
return GlobalCSS + htmlString;
}
/**
* Processes a template literal to combine strings and interpolated values into a single CSS string.
*
* @param {TemplateStringsArray} strings - An array of string literals.
* @param {...string} values - Interpolated values within the template literal.
* @returns {string} The combined CSS string.
*/
export function createCSS(strings, ...values) {
let content = strings[0];
for (let i = 0; i < values.length; i++) {
content += values[i] + strings[i + 1];
}
return `
<style>
${content}
<style>
`;
} |
저는 이 방향이 깔끔하고 좋은 것 같습니다! |
저도 좋습니다! |
@DaleStudy/website 지난 번에 논의할 때 우리가 충분히 고려하지 않은 부분이 있습니다. 바로 declarative하게 프로그래밍을 하려면, 결국 어떻게 해서든 HTML 마크업 또는 CSS 스타일을 하기 전에 속성값이 접근할 수 있어야 한다는 것입니다. 이런 측면에서는 @yolophg 님께서 제안해주셨던 아래 논의도 상당히 관련이 있으니 참고 바랍니다. |
@DaleSeo 이거 될 수 있으면 빨리 논의해서 정하면 좋을 것 같은데요. 어떡할까요? 다음 정기 미팅까지 기다리기에는 코드 진행이 더딜 것 같습니다. |
@DaleStudy/website 현재 확정된 웹 컴포넌트 작성 패턴 예시입니다. 한 번 확인해주세요~ import { css, html } from "../html-css-utils.js";
class ButtonLink extends HTMLElement {
constructor() {
super();
this.validateAttributes();
this.render();
}
validateAttributes() {
if (!this.hasAttribute("href")) {
throw new Error('The "href" attribute is required.');
}
if (this.hasAttribute("size")) {
const size = this.getAttribute("size");
const validSizes = ["big", "small"];
if (!validSizes.includes(size)) {
throw new Error(
`The "size" attribute must be one of ${validSizes.join(", ")}.`
);
}
}
if (this.hasAttribute("variant")) {
const variant = this.getAttribute("variant");
const validVariants = ["ghost", "primary"];
if (!validVariants.includes(variant)) {
throw new Error(
`The "variant" attribute must be one of ${validVariants.join(", ")}.`
);
}
}
}
render() {
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = this.createCss() + this.createHtml();
}
createCss() {
return css`
a {
display: flex;
justify-content: center;
align-items: center;
width: max-content;
text-align: center;
}
`;
}
createHtml() {
const href = this.getAttribute("href");
const variant = this.getAttribute("variant") ?? "ghost";
const size = this.getAttribute("size") ?? "small";
return html`
<a
href="${href}"
class="${size} ${variant}"
target=${href.startsWith("#") ? "_self" : "_blank"}
>
<slot></slot>
</a>
`;
}
}
customElements.define("ds-button-link", ButtonLink); |
@sounmind 님, 예시 코드 정리해주셔서 감사합니다. 전반적으로 논의한 패턴과 부합합니다. 몇 가지 nitpicks을 드리자면,
위 의견 모두 어느정도 제 개인적인 선호가 반영되어 있으며, 대세에 전혀 영향을 주지 않는 사소한 디테일입니다. 따라서 최종 의사 결정은 테크리드에게 맡기도록 하겠습니다. 저는 현재 확정된 패턴으로도 충분히 만족스럽습니다. 😁 더 중요한 것은 프로젝트가 더 이상 패턴 때문에 지연되지 않아야 한다는 것입니다. |
@DaleSeo 님 피드백 감사합니다. 네이밍 관련은 모두 동의해서 그대로 반영하면 좋겠습니다. render 함수는 아래와 같은 이유로 유지하면 좋겠네요!
|
예시 작성 감사합니다! 그럼 현재까지 작성된 방식으로 변경하도록 하겠습니다! |
다시 명확하게: 웹 컴포넌트 클래스에서 attribute를 가져오기 위해 getter, setter는 사용하지 않는다. |
Background
The text was updated successfully, but these errors were encountered: