🔎nuxt 폴더 구조
.
├── assets/
├── components/
├── layouts/
├── middleware/
├── pages/
├── plugins/
├── static/
├── store/
├── nuxt.config.js
└── package.json
- assets
이미지, 스타일시트 및 기타 자산을 저장하는 디렉토리. 이 자산은 빌드 후에 /_nuxt/ 디렉토리에 복사됨. - components
Vue.js 컴포넌트를 저장하는 디렉토리. - layouts
애플리케이션의 레이아웃을 저장하는 디렉토리. 레이아웃은 페이지 컴포넌트를 렌더링할 때 사용됨. - middleware
미들웨어 함수를 저장하는 디렉토리. 미들웨어는 페이지 렌더링 전에 실행됨. - pages
애플리케이션의 페이지 컴포넌트를 저장하는 디렉토리. 파일 시스템 기반 라우팅을 사용하여 URL 경로와 매핑됨. - plugins
Vue.js 플러그인을 저장하는 디렉토리. 애플리케이션이 시작될 때 자동으로 로드됨. - static
정적 파일을 저장하는 디렉토리. 이 파일은 빌드 후에 /_nuxt/ 디렉토리에 복사됨. - store
Vuex 스토어 모듈을 저장하는 디렉토리. 상태 관리를 위해 사용됨. - nuxt.config.js
Nuxt.js 구성 파일. 빌드, 렌더링 및 애플리케이션 구성을 위한 다양한 옵션을 제공함. - package.json
NPM 패키지 구성 파일. 애플리케이션의 종속성 및 스크립트를 정의함.
🎉필요한 경우 이 구조를 조정하고 사용자 지정 폴더를 추가하여 애플리케이션을 더욱 유연하게 만들 수 있음
🔎nuxt 프로젝트의 app.vue
app.vue 파일은 Nuxt.js에서 기본적으로 제공하는 파일 중 하나로, 애플리케이션의 최상위 컴포넌트.
이 파일은 pages 디렉토리에 있는 모든 페이지 컴포넌트의 공통 레이아웃을 정의하고, 라우터 및 미들웨어 등의 설정을 포함함.
(아래 예제코드에는 미들웨어 설정이 없음)
<!-- 디렉토리가 없는 경우 -->
<template>
<h1>Hello World!</h1>
</template>
<!-- 디렉토리가 있는 경우 -->
<!-- NuxtPage는 pages 폴더의 index.vue 파일을 가리킨다. -->
<template>
<div>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</div>
</template>
응용
<script lang="ts" setup>
import { AppConfigInput } from '@nuxt/schema'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { AppSetup } from './utils/app'
import { ITheme } from './utils/theme'
AppSetup()
const theme = useState<ITheme>('theme.current')
const locale = useState<string>('locale.setting')
const app = useAppConfig() as AppConfigInput
// pinia 라이브러리 사용
const pinia = createPinia()
// piniaPluginPersistedstate 플러그인 적용
// pinia의 상태를 브라우저의 로컬 스토리지에 저장할 수 있음
pinia.use(piniaPluginPersistedstate)
// useHead 함수를 사용하여 HTML 헤더 내부의 정보를 설정하고 있음.
useHead({
title: app.name,
titleTemplate: 'example',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'example',
},
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
})
</script>
<template>
<Html :class="`${theme === 'dark' ? 'dark' : ''}`" :lang="locale">
<!-- :class 속성을 통해 현재 테마 모드에 따라 body 태그의 클래스 이름이 결정되고, :lang 속성을 통해 현재 언어 설정에 따라 HTML 태그의 lang 속성이 결정됨 -->
<Body
class="antialiased duration-300 transition-colors text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-800"
>
<NuxtLayout>
<NuxtLoadingIndicator :height="5" :duration="3000" :throttle="400" />
<!--NuxtPage는 기본적으로 pages 폴더 내부의 index.vue 파일을 참조.-->
<NuxtPage />
</NuxtLayout>
</Body>
</Html>
</template>
https://kyounghwan01.github.io/blog/Vue/vue3/composition-api/#setup
🔎 script setup 구문이란?
<script setup> 구문은 Vue 3에서 도입된 새로운 구문 중 하나
🎉 <script setup> 구문을 사용 시 이점
코드의 간결성
이전에는 Vue 컴포넌트를 작성할 때 data, computed, methods 등의 속성을 일일이 정의해야 했음
그러나 <script setup> 구문에서는 이런 작업을 대부분 생략할 수 있음 => SFC 방식에 아주 적합!
타입스크립트와 같은 타입 체킹 도구를 사용할 때 유용
컴포넌트의 타입을 정의하는 작업이 단순해짐
최신 자바스크립트 기능을 사용할 수 있음
예를 들어, <script setup> 구문에서는 import 문을 사용해 모듈을 불러올 수 있음
더보기
composition api 방식
<script setup>
import { ref } from 'vue'
const count = ref(0)
onMounted(() => {console.log(count)})
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
options api 방식(~vue 2버전)
<template>
<button @click="increment">{{ count }}</button>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
mounted() {
console.log(this.count); // 0
},
methods: {
increment() {
this.count++;
},
},
};
</script>
📌app.vue 파일의 역할
- pages 디렉토리에 있는 모든 페이지 컴포넌트의 공통 레이아웃을 정의
- 전역적으로 사용되는 CSS 파일이나 플러그인 등을 설정
- 라우터 설정 및 미들웨어 등의 설정을 포함
- 애플리케이션 전역에서 사용되는 상태 관리 라이브러리를 설정
- app.vue 파일은 다음과 같은 속성과 라이프사이클 훅을 가짐.
🔎components
📌 컴포넌트 사용 이유
기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화하는 데 도움이 됨
📌 컴포넌트의 등록
SpanBtn.vue
<!--기본 용법-->
<template>
<span class="text-xs bg-blue-400 px-3 py-1 rounded-full text-white">컴포넌트 예시</span>
</template>
📌 컴포넌트의 사용
example.vue
<template>
<div>
<SpanBtn />
</div>
</template>
상단에서 해당 컴포넌트를 import 하지 않아도 알아서 불러온다.
📌 부모 컴포넌트에서 자식 컴포넌트로 값 넘기기(defindProps)
SpanBtn.vue(자식 컴포넌트)
<script lang="ts" setup>
// const props는 생략해도 된다.
// const props = defineProps({
// text: {
// type: String,
// default: '',
// },
// })
defineProps({
text: {
type: String,
default: '',
},
})
</script>
<template>
<span class="text-xs bg-blue-400 px-3 py-1 rounded-full text-white">{{
text
}}</span>
</template>
type: props의 타입 정의
default: 기본값 → 설정하게 되면 required가 true가 됨
required: 필수 유무(boolean)
example.vue(부모 컴포넌트)
<template>
<div>
<SpanBtn :text="'props 예제'" />
<SpanBtn :text="'속성 변경도 가능'" class="bg-red-500" />
</div>
</template>
props로 function도 넘길 수 있다!
📌 자식 컴포넌트에서 부모컴포넌트로 값 넘기기(emit)
더보기
자식 컴포넌트
<script setup lang="ts">
import { defineEmits } from 'vue'
const props = defineProps({
label: {
type: String,
default: '',
},
inputType: {
type: String,
default: 'text',
},
placeholder: {
type: String,
default: '',
},
required: {
type: Boolean,
required: false,
default: true,
},
maxLength: {
type: Number,
default: 30,
},
initValue: {
type: String,
required: false,
default: '',
},
constraint: {
type: Function,
required: false,
},
constraintText: {
type: String,
required: false,
default: '',
},
className: {
type: String,
required: false,
default: '',
},
})
// emit 정의
const emits = defineEmits(['update-value'])
// emit 함수
const updateInputValue = (value: string | null) => {
value = value?.trim() ?? ''
if (props.constraint) {
const element = document.querySelector('.' + props.className) as HTMLElement
if (value && !props.constraint(value)) {
element.style.visibility = 'visible'
element.style.opacity = '0.8'
} else {
element.style.visibility = 'hidden'
element.style.opacity = '0'
}
}
emits('update-value', value)
}
</script>
<template>
<div class="flex flex-row w-full justify-between items-center">
<div>
<span class="text-sm font-bold">{{ label }}</span>
<span v-if="!required" class="text-xs">(선택)</span>
</div>
<!-- input text-->
<div
v-if="inputType === 'text' && initValue === ''"
class="w-2/3 relative inline-block"
>
<input
type="text"
:placeholder="placeholder"
class="w-full text-sm rounded-lg border border-gray-200 bg-white dark:bg-gray-700 py-1.5 px-3"
:maxlength="maxLength"
@input="(event) => updateInputValue((event.target as HTMLInputElement).value)"
/>
<span
v-if="constraintText !== ''"
class="constraint-text text-xs bg-rose-400 px-3 py-1.5"
:class="className"
>{{ constraintText }}</span
>
</div>
</div>
</template>
<script lang="ts" setup>
definePageMeta({
layout: 'dashboard',
})
useHead(() => ({
title: '토큰 발행',
meta: [
{
name: '토큰 발행',
content: '토큰 발행',
},
],
}))
const coinName = ref('')
// 영문 대소문자, 숫자만 허용
function regexEngNum(value: string) {
const regex = /^[a-zA-Z0-9\s]+$/
return regex.test(value)
}
</script>
<template>
<PageWrapper>
<PageHeader :text="['토큰 발행']">
<PageTitle :text="'토큰 발행'" />
<p class="mt-2 text-sm font-normal">신규 토큰을 발행할 수 있습니다.</p>
</PageHeader>
<PageBody>
<PageSection>
<PageLabelInput
:label="'토큰 이름'"
:placeholder="'토큰 이름을 설정하세요(영문 대소문자, 숫자, 공백만 허용)'"
:constraint="regexEngNum"
:constraint-text="'영문 대소문자와 숫자만 입력해주세요'"
:class-name="'coinName'"
:max-length="20"
@update-value="(value: string) => (coinName = value)"
/>
</PageSection>
</PageBody>
</PageWrapper>
</template>
'Front-End > vue' 카테고리의 다른 글
[nuxt3] plugin 기능과 definePageMeta를 활용한 웹 내 페이지 검색 기능 구현 (2) | 2023.08.07 |
---|---|
서버 사이드 렌더링과 Nuxt.js (0) | 2023.03.14 |
[vue] 코드 저장할 때마다 웹팩 자동 저장(웹팩 감시 옵션) (0) | 2023.01.09 |
[vue] 웹팩 사용하기 (0) | 2022.12.28 |
[vue] 컴포넌트의 필요성과 특성 (0) | 2022.12.12 |
댓글