이번 포스트에서는 이전 포스트에서 만들었던 다크 모드 버튼을 컴포넌트로 따로 만들어 보겠다.
컴포넌트Component란, UI(화면)를 구성하는 레고 블록 같은 '독립적이고 재사용 가능한 부품'을 의미한다.
이전 포스트
라라벨 Livewire, 다크 모드 기능 구현
다크 모드 버튼을 컴포넌트로 추출하고 모바일 화면에도 다크 모드 버튼 추가 · 4th-avenue/laravel13-livewire@cfb476e · GitHub
전체적인 코드 수정은 이 깃허브 링크를 통해서 확인할 수 있다.
어떤 경우에 컴포넌트로 만들면 좋을까?
코드 작성을 하다가 아래 4가지 상황 중 하나라도 해당한다면, 따로 빼서 컴포넌트로 만드는 것이 효율적이다.
- 동일한 UI나 로직이 2회 이상 반복될 때 (재사용성)
- 한 파일의 코드 줄 수가 너무 길어져 가독성이 떨어질 때 (비대화 방지)
- 내부 스타일과 속성이 너무 복잡해서 숨기고 싶을 때 (캡슐화)
- 독립적인 기능과 목적이 명확할 때 (단일 책임)
다크 모드 버튼의 경우가 마지막의 '독립적인 기능과 목적이 명확한 것'에 해당한다.
좋아요(Like) 버튼 같은 경우에도 이에 해당한다.
위의 보기에 해당하는 경우에는 주저하지 말고 resources/views/components 디렉터리로 코드를 격리하여 컴포넌트화하는 것을 권장한다.
resources/views/components 폴더에 darkmode-button.blade.php 파일 만들기
<button x-data @click="$store.darkMode.toggle()" {{ $attributes->merge(['class' => 'p-1.5 mr-2 border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-300 dark:bg-gray-700 rounded-full']) }}>
<template x-if="$store.darkMode.dark">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>
</template>
<template x-if="!$store.darkMode.dark">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
</svg>
</template>
</button>
다크 모드 버튼의 코드를 그대로 복사하고 붙여 넣은 상태에서 약간의 수정을 거쳐 컴포넌트로 만들었다.
대부분 이전 포스트에서 설명했던 코드들이지만, 예고했던 대로 다시 한번 좀 더 자세히 설명해 보겠다.
<button> 태그에 x-data 속성이 들어가 있다.
x-data는 해당 HTML 엘리먼트와 그 하위 자식 노드들을 Alpine.js의 반응형 컴포넌트 영역으로 선언하고 초기화하는 속성이다.
Alpine.js가 DOM을 탐색하다가 x-data를 만나면, 해당 스코프 내에서 Alpine.js의 지시어(x-if, @click 등)를 사용할 수 있도록 자바스크립트 인스턴스를 바인딩한다.
본래 x-data="{ open: false }" 처럼 내부에 로컬 상태 객체를 정의해야 하지만, 이 컴포넌트에서는 내부 로컬 데이터가 필요 없고 오직 외부 전역 스토어($store)에만 접근하므로 빈 속성(x-data)으로 선언하여 Alpine.js 컴포넌트 활성화 역할만 수행하도록 했다.
@click="$store.darkMode.toggle()"은 버튼 클릭 시 Alpine.js 전역 스토어에 등록된 다크 모드 전환 함수를 실행한다.
@click은 Alpine.js에서v-on:click의 축약형으로, 브라우저의 click 이벤트를 리스닝 한다.$store는 Alpine.js가 제공하는 전역 상태 관리 객체에 접근하는 내장 객체다.darkMode는 이전 포스트에서 app.js에Alpine.store('darkMode', { ... })형태로 정의해 둔 전역 스토어의 식별자다.- 버튼을 클릭할 때마다 해당 스토어 내부에 선언된
toggle()메서드가 동기적으로 호출되어, 로컬스토리지 값을 변경하고<html>태그의 dark 클래스를 토글하는 비즈니스 로직을 수행하게 된다.
{{ $attributes->merge(['class' => ' ... ']) }}는 라라벨 블레이드 컴포넌트가 기본으로 제공하는 클래스 병합 시스템이다.
이 컴포넌트를 다른 뷰 파일에서 사용할 때 외부에서 추가적인 테일윈드 클래스나 속성을 주입할 수 있게 해 준다.
버튼 내부에 배치된 두 개의 <template> 태그는 Alpine.js의 조건부 렌더링 지시어인 x-if를 사용하고 있다.
값이 true일 때(다크 모드 상태일 때)는 상단의 달 모양 SVG 아이콘을 DOM 트리에 동적으로 삽입하고, 값이 false일 때(라이트 모드 상태일 때)는 하단의 해 모양 SVG 아이콘을 렌더링 한다.
livewire/layout/navigation.blade.php 파일 수정
위의 깃허브 링크를 들어가 보자.
얼핏 보면 뭔가 복잡한 변화가 생긴 것처럼 보이지만 정리해보면 별거 없다.
<div class="flex">
<div class="flex items-center">
<x-darkmode-button />
</div>
<!-- 기존의 코드들 -->
</div>
그리고 기존의 코드들을 <div class="flex">로 감싸고, 기존의 코드들 위에 <x-darkmode-button />을 넣어서 darkmode-button 컴포넌트를 호출했다.
버튼의 디자인이 망가지지 않도록 또 버튼을 <div class="flex items-center">로 감싼다.
이전 포스트에서는 데스크톱 화면에서만 다크 모드 버튼이 표시됐었는데, 이제는 모바일 화면에서도 다크 모드 버튼이 표시된다.
다음 포스트
0 Comments
댓글 쓰기
🔸 댓글은 블로그 운영자의 승인 후에 블로그에 표시됩니다.
🔸 비로그인 방문자 분께서는 '익명'보다 이름/URL로 댓글을 남겨주시면 감사하겠습니다. (URL은 생략 가능합니다.)
🔸 구글 로그인 방문자는 '알림 사용'에 체크를 하시면, 남겨주신 댓글에 대한 답글 알림을 메일로 받아볼 수 있습니다. 📩