Lit-Element + 아토믹 패턴 아토믹 패턴이란? 컴포넌트의 재사용성
을 고려하여 원자(Atomic)
단위로 분리하여 설계하는 방식을 의미합니다. 요소의 조합을 통해 작은 기능을 가진 원자
부터 하나의 페이지
까지를 구현합니다.
요소와 역할 원자 Atoms 기능의 최소 단위이자, 최소 단위의 컴포넌트 어떠한 환경이 주어지든 컴포넌트가 생성되어야 하기 때문에 범용적으로 설계되어야 합니다. 대표적으로 Input, Button 등이 있습니다.
분자 Molecules 원자의 조합으로, 하나의 단위로 동작하는 컴포넌트 대표적으로 Card 와 Input Form 등이 있습니다.
유기체 Organisms 분자의 조합으로, 비교적 복잡한 구조 대표적으로 Card List 등이 있습니다.
템플릿 Templates 유기체의 조합으로, 페이지의 기본적 구조 대표적으로 컨텐츠를 묶는 헤더, 메인, 푸터 등이 있습니다.
페이지 Pages 템플릿의 조합으로, 완전한 하나의 페이지 페이지 단위에서 어플리케이션 상태 관리가 되어야합니다.
장점
컴포넌트의 재사용성
이 극대화됩니다.
컴포넌트
단위의 테스트가 가능합니다.
단점
페이지
에서 원자
컴포넌트로 이동하며 코드를 분석해야하기 때문에 러닝커브가 발생합니다.
컴포넌트
가 파편화되어 사용되기 떄문에 컴포넌트
를 수정하였을 때 예상치 못한 곳에서 사이드 이펙트가 발생할 수 있습니다.
원칙 단일 책임 컴포넌트는 관심사
에 따라 하나의 역할
만 수행해야합니다. 역할에 따라 컴포넌트
를 생성해야합니다.
제어 위임 컴포넌트에 대한 제어를 외부로 위임하여 유연성
과 재사용성
을 높일 수 있습니다.비즈니스 로직
은 외부로부터 받아오도록 합니다.
아토믹 패턴 도입 고려 이유 Accordion
서비스는 신규 기능 도입을 중점으로 개발되어 왔습니다. 그러나 서비스가 점점 확대되고, 팀원도 점차 늘어나며 기존 서비스에 대한 유지/보수
의 중요성이 대두되었습니다. 현재는 템플릿
단위의 컴포넌트로 개발되어 의존성
이 강하여 재사용
이 아닌 동일한 코드를 다시 작성하는 형태로 개발되어왔습니다. 기존의 서비스를 어떻게 리팩토링하면 좋을지 고민하다 차후 확장성을 고려하여 아토믹 패턴
형태의 개발을 고려하게 되었습니다.
Lit-Element 를 어떻게 아토믹 패턴으로 적용할 것인가? 컴포넌트 단일 책임
과 제어 위임
을 고려하여 컴포넌트
를 생성합니다.
컴포넌트
가 하나의 기능만 동작하도록 구현해야 합니다.
dispatchEvent
를 통해 상위 컴포넌트에 제어 위임
을 해야합니다.
예제 1 2 3 4 5 6 7 8 9 10 11 12 13 @property ({ type : Number }) count = 0 @eventOptions ({}) onClickEvent(): void { this .dispatchEvent(new CustomEvent(`click-event` )); }render ( ) { <button @click =${this.clickEvent} > <span > ${this.count}</span > </button > `; }
디자인 Lit ELement
는 Shadow DOM
기반의 컴포넌트를 생성하여, 외부에서 선언한 CSS
가 동작하지 않습니다. 하위 컴포넌트에 직접 CSS
를 구현하기 위해서는 Style Element
를 이용해야합니다.
예제 1 2 3 4 5 6 7 8 9 10 11 @property () fontSize = `16px` render ( ) { <style > div { font-size : ${this.fontSize} } </style > <div > 예제 </div > }
예제 코드 원자 Atomic 컴포넌트 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import { html, TemplateResult, customElement, eventOptions, property, LitElement, } from 'lit-element' ;@customElement ('atomic-element' )export default class AtomicComponent extends LitElement { @property ({ type : String }) size = `30px` ; @property ({ type : String }) color = 'black' ; @property ({ type : String }) backgroundColor = `` ; @property ({ type : String }) text = `` ; @eventOptions ({}) clickEvent(): void { this .dispatchEvent(new CustomEvent(`click-event` )); } protected render(): TemplateResult { return html` <style > button { color : ${this .color} ; font-size : ${this .size} ; background-color : ${this .backgroundColor} ; text-align : center; } </style > <button @click = ${this .clickEvent} > <span > ${this .text} </span > </button > ` ; } }
분자 Molecules 컴포넌트 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import { html, TemplateResult, customElement, property, LitElement, eventOptions, } from 'lit-element' ;import './atomic-element' ;@customElement ('molecules-element' )export default class MoleculesComponent extends LitElement { @property ({ type : Number }) count = 0 ; @property ({ type : String }) size = `30px` ; @property ({ type : String }) fontColor = `black` ; @property ({ type : String }) backgroundColor = `` ; @eventOptions ({}) clickAddEvent(): void { this .dispatchEvent(new CustomEvent(`click-add-event` )); } @eventOptions ({}) clickMinusEvent(): void { this .dispatchEvent(new CustomEvent(`click-minus-event` )); } protected render(): TemplateResult { return html` <style > span { font-size : ${this .size} ; color : ${this .fontColor} ; } </style > <atomic-element @click-event = ${this .clickAddEvent} text ="+" size = ${this .size} color = ${this .fontColor} backgroundColor = ${this .backgroundColor} > </atomic-element > <span > ${this .count} </span > <atomic-element @click-event = ${this .clickMinusEvent} text ="-" size = ${this .size} color = ${this .fontColor} backgroundColor = ${this .backgroundColor} > </atomic-element > ` ; } }
유기체 Organisms 컴포넌트 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 import { html, TemplateResult, customElement, property, LitElement, eventOptions, } from 'lit-element' ;import './molecules-element' ;@customElement ('organisms-element' )export default class OrganismsComponent extends LitElement { @property ({ type : Number }) count1 = 0 ; @property ({ type : Number }) count2 = 0 ; @property ({ type : String }) size = '30px' ; @property ({ type : String }) backgroundColor = '' ; @property ({ type : String }) fontColor = 'black' ; @eventOptions ({}) onClickAddEvent1(): void { this .dispatchEvent(new CustomEvent(`click-add-event1` )); } @eventOptions ({}) onClickMinusEvent1(): void { this .dispatchEvent(new CustomEvent(`click-minus-event1` )); } @eventOptions ({}) onClickAddEvent2(): void { this .dispatchEvent(new CustomEvent(`click-add-event2` )); } @eventOptions ({}) onClickMinusEvent2(): void { this .dispatchEvent(new CustomEvent(`click-minus-event2` )); } protected render(): TemplateResult { return html` Example 1: <molecules-element .count = ${this .count1} size = ${this .size} backgroundColor = ${this .backgroundColor} fontColor = ${this .fontColor} @click-add-event = ${this .onClickAddEvent1} @click-minus-event = ${this .onClickMinusEvent1} > </molecules-element > Example2 : <molecules-element .count = ${this .count2} size = ${this .size} backgroundColor = ${this .backgroundColor} fontColor = ${this .fontColor} @click-add-event = ${this .onClickAddEvent2} @click-minus-event = ${this .onClickMinusEvent2} > </molecules-element > ` ; } }
템플릿 Template 컴포넌트 Template
컴포넌트 이후로도 조합하여 Page
컴포넌트 단위까지 생성합니다. 여기서는 Template
컴포넌트를 최종 레이아웃으로 상정하여 작성하겠습니다.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import { html, TemplateResult, customElement, property, LitElement } from 'lit-element' ;import './organisms-element' ;@customElement('templates-element' ) export default class TemplateComponent extends LitElement { @property({ type: Number }) count1 = 0 ; @property({ type: Number }) count2 = 0 ; addCount1(): void { this .count1 = ++this .count1; } minusCount1(): void { this .count1 = --this .count1; } addCount2(): void { this .count2 = ++this .count2; } minusCount2(): void { this .count2 = --this .count2; } protected render(): TemplateResult { return html` <organisms-element .count1=${this .count1} .count2=${this .count2} @click -add-event1=${this .addCount1} @click -minus-event1=${this .minusCount1} @click -add-event2=${this .addCount2} @click -minus-event2=${this .minusCount2} fontColor="red" backgroundColor="blue" ></organisms-element> `; } }
Lit-Element + Store Store 란? 프론트엔드 개발이 고도화되면서 어플리케이션의 효율적인 유지/보수
를 위해 다양한 아키텍처
가 등장하였습니다.Store
는 웹 컴포넌트
와 비즈니스 로직
을 구분지어 관리하는 모델 컨트롤러
입니다.
아토믹 패턴 적용 아토믹 패턴에서는 최상단 컴포넌트인 Page
컴포넌트에서 비즈니스 로직을 관리합니다.
Store 생성 맨텍 OSS 팀에서는 상태 관리를 Mobx
라이브러리를 사용하여 관리합니다.Mobx
로 관리되는 Store
를 생성합니다.
Store
에는 화면에서 보여줄 State 프로퍼티
와 State를 변경할 Action 함수
를 선언합니다.
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 import { action, observable } from 'mobx' ;export default class ExampleStore { @observable count1 = 0 ; @observable count2 = 0 ; @action addCount1 = (): void => { this .count1 = ++this .count1; }; @action minusCount1 = (): void => { this .count1 = --this .count1; }; @action addCount2 = (): void => { this .count2 = ++this .count2; }; @action minusCount2 = (): void => { this .count2 = --this .count2; }; }
Template Component + Store 예제로 만든 Template
컴포넌트와 조합하여 Store
를 적용합니다.Store
호출 후 생성하여 내부의 State
와 Action
을 Property
로 전달합니다.
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 import { html, TemplateResult, customElement } from 'lit-element' ;import { MobxLitElement } from '@adobe/lit-mobx' ;import './organisms-element' ;import ExampleStore from './example-store' ;@customElement ('templates-element' )export default class TemplateComponent extends MobxLitElement { store = new ExampleStore(); protected render(): TemplateResult { return html` <organisms-element .count1 = ${this .store.count1} .count2 = ${this .store.count2} @click-add-event1 = ${this .store.addCount1} @click-minus-event1 = ${this .store.minusCount1} @click-add-event2 = ${this .store.addCount2} @click-minus-event2 = ${this .store.minusCount2} fontColor ="red" backgroundColor ="blue" > </organisms-element > ` ; } }
결과
정리 Lit-Element
를 활용해서 Atomic 패턴
을 구현해보고 실제 비즈니스 로직을 담당하는 Store
를 사용해서 얘제를 구현해보았습니다.
Atom
컴포넌트는 상위의 기능을 수행할 수 있도록 범용성과 재사용성을 고려하도록 개발해야하고, 비즈니스 로직은 최상단 Page
컴포넌트에서 구성되어야 합니다.
하지만 무분별하게 컴포넌트를 파편화 할 경우 예상치 못한 사이드 이펙트가 발생할 수 있으므로 개발시 이점 고려하여 개발을 진행해야 할 것입니다.