21.02.24 lamplight서비스 프로젝트(게시물 추가 테스트, 백엔드 연결, 게시물 번호별 리스팅 등)
2021. 2. 24. 20:48ㆍVue.js/Spring & Vue APP 프로젝트(프론트엔드)
#NOTE
typescript에서는 title:string, body:string 이런식으로 type을 적어주어야 한다
setup()
- setup() 함수는 프로그램 실행시 단 한번 호출된다.
- setup() 함수는 프로그램당 한 개씩만 존재할 수 있으며, 최초 한 번 실행된 이후에는 재호출되지 않아야 한다.
- 참고: setup() 함수 안에 선언된 변수는, draw() 함수를 비롯한 여타 함수들이 접근할 수 없다.
submint.prevent
- 말그대로 submit을 막는 것
reactive
- reactive는 기존 뷰 문법의 data 속성 느낌이고,
- ref는 좀 더 리액티브 속성을 개별적으로 선언하는 느낌....???
state??
기존 코드에서는 props, data, methods가 같은 계층에 존재했습니다.
하지만 Vue3에서는 props와 setup이 같은 계층에 존재하고,
data는 state로, method 들은 각각의 기명함수로 작성되어 한번에 반환되도록 변화하였습니다.
state의 경우에도 그냥 선언하는 것이 아니라, vue reactive를 사용하게 되었습니다.
Reactive(반응형)는 Vue가 반응형 시스템을 유지하기 위해서 사용하는 간단한 JavaScript 객체입니다.
Reactive는 아래와 같이 동작합니다.
(그림: https://media.vlpt.us/images/bluestragglr/post/50e4c7c7-c697-4c5a-8329-820828389777/Untitled.png)
Vue2 에서는 data나 method, computed 등을 선언하게 되면 알아서 각각에 대해 위와 같이 동작하는 reactive 객체를 생성하였습니다.
하지만 그 과정이 묵시적이었고, 유저들은 위 과정을 알 필요가 없었습니다.
하지만 Vue3에서의 타입스크립트의 지원이나 state로의 명칭 변경, 명시적 reactive 사용 등을 보았을 때,
전반적으로 명료한 선언을 지향하는 방향으로 변화한 것으로 보입니다.
보일러플레이트 코드?
-뭔가를 하기 위해 필요하지만 몰라도 되는 것
-하긴 해야되는 것
axios
- ajax 통신을 하게 해주는 것
dependencies
- 개발할 때와 서비스 할 때 모두 필요한 것
devDependencies
- 개발 할 때만 필요한 것
any
- 어떤 타입이든 들어갈 수 있는 타입
- object와 비슷한 개념??
#소스코드
<MainApi>
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import {IArticle} from '../types'
// API 원형
abstract class HttpClient {
protected readonly instance: AxiosInstance;
public constructor(instance: AxiosInstance) {
this.instance = instance;
this._initializeRequestInterceptor();
this._initializeResponseInterceptor();
}
private _initializeRequestInterceptor() {
this.instance.interceptors.request.use(
this._handleRequest,
this._handleError,
);
};
private _initializeResponseInterceptor() {
this.instance.interceptors.response.use(
this._handleResponse,
this._handleError,
);
};
protected _handleRequest(config:AxiosRequestConfig) : AxiosRequestConfig {
return config;
}
protected _handleResponse(axiosResponse:AxiosResponse) : AxiosResponse {
return axiosResponse;
}
protected _handleError(error: AxiosError) {
if (error.response) {
// 요청이 이루어졌으며 서버가 2xx의 범위를 벗어나는 상태 코드로 응답했습니다.
alert('요청을 처리하는 중에 오류가 발생하였습니다.');
}
else if (error.request) {
// 요청이 이루어 졌으나 응답을 받지 못했습니다.
// `error.request`는 브라우저의 XMLHttpRequest 인스턴스 또는
// Node.js의 http.ClientRequest 인스턴스입니다.
alert('서버 또는 네트워크의 상태가 좋지 않습니다.');
}
else {
// 오류를 발생시킨 요청을 설정하는 중에 문제가 발생했습니다.
console.log('Error', error.message);
}
return Promise.reject(error);
};
}
// 응답타입1
interface Base__IResponseBodyType1 {
resultCode:string;
msg:string;
}
// /usr/article/list 의 응답 타입
export interface MainApi__article_list__IResponseBody extends Base__IResponseBodyType1 {
body:{
articles: IArticle[]
};
}
// /usr/article/detail 의 응답 타입
export interface MainApi__article_detail__IResponseBody extends Base__IResponseBodyType1 {
body:{
article: IArticle
};
}
// http://localhost:8021/usr/ 와의 통신장치
export class MainApi extends HttpClient {
public constructor() {
super(
axios.create({
baseURL:'http://localhost:8021/usr/',
})
);
}
protected _handleRequest(config:AxiosRequestConfig) {
config.params = {};
config.params.authKey = localStorage.getItem("authKey");
return config;
};
protected _handleResponse(axiosResponse:AxiosResponse) : AxiosResponse {
if ( axiosResponse?.data?.requestCode == "F-B" ) {
alert('로그인 후 이용해주세요.');
location.replace('/member/login');
}
return axiosResponse;
}
// http://localhost:8021/usr/article/list?boardId=? 를 요청하고 응답을 받아오는 함수
public article_list(boardId: number) {
return this.instance.get<MainApi__article_list__IResponseBody>(`/article/list?boardId=${boardId}`);
}
// http://localhost:8021/usr/detail/id?id=? 를 요청하고 응답을 받아오는 함수
public article_detail(id: number) {
return this.instance.get<MainApi__article_detail__IResponseBody>(`/article/detail?id=${id}`);
}
}
<IArticle>
export interface IArticle {
id:number;
regDate:string;
updateDate:string;
boardId:number;
memberId:number;
title:string;
body:string;
extra__writer:string;
}
<ArticleListPage.vue>
<template>
<TitleBar class="bg-purple-500">Article List</TitleBar>
<section class="section section-article-write-form-box px-2">
<div class="container mx-auto">
<!--submint.prevent: 말그대로 submit을 막는 것-->
<form v-on:submit.prevent="checkAndWriteArticle">
<FormRow title="제목">
<input ref="newArticleTitleElRef" type="text" class="form-row-input" placeholder="제목을 입력하세요.">
</FormRow>
<FormRow title="내용">
<textarea ref="newArticleBodyElRef" class="form-row-input" placeholder="내용을 입력하세요."></textarea>
</FormRow>
<FormRow>
<!-- App.vue로 부터 .btn-primary의 style 가져오기 -->
<div class="btns">
<input class="btn-primary" type="submit" value="등록">
</div>
</FormRow>
</form>
</div>
</section>
<section class="section section-article-list px-2">
<div class="container mx-auto">
<div class="mt-6" v-bind:key="article.id" v-for="article in state.articles">
<div class="px-10 py-6 bg-white rounded-lg shadow-md">
<div class="flex justify-between items-center">
<span class="font-light text-gray-600">
2021-02-24 10:20:30
</span>
<a href="#" class="px-2 py-1 bg-gray-600 text-gray-100 font-bold rounded hover:bg-gray-500">
자유
</a>
</div>
<div class="mt-2">
<a href="#" class="text-2xl text-gray-700 font-bold hover:underline">
{{ article.title }}
</a>
<p class="mt-2 text-gray-600">
{{ article.body }}
</p>
</div>
<div class="flex justify-between items-center mt-4">
<a href="#" class="text-blue-500 hover:underline">자세히 보기</a>
<div>
<a href="#" class="flex items-center">
<img src="https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=731&q=80" alt="avatar" class="mx-4 w-10 h-10 object-cover rounded-full hidden sm:block">
<h1 class="text-gray-700 font-bold hover:underline">홍길동</h1>
</a>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
/* 전역 컴포넌트 적용으로 안해도 됨
import TitleBar from '../components/TitleBar.vue';
*/
//import { Article } from '../dtos/' /* '../dtos/index'이지만 생략가능 */
import { IArticle } from '../types/'
import { MainApi } from '../apis/'
export default defineComponent({
name: 'ArticleListPage',
/* setup() 함수는 프로그램 실행시 단 한번 호출된다.
setup() 함수는 프로그램당 한 개씩만 존재할 수 있으며,
최초 한 번 실행된 이후에는 재호출되지 않아야 한다.
참고: setup() 함수 안에 선언된 변수는, draw() 함수를 비롯한 여타 함수들이 접근할 수 없다. */
setup(){
const route = useRoute(); //useRoute 객체 생성
const mainApi:MainApi = getCurrentInstance()?.appContext.config.globalProperties.$mainApi;
/* ref: HTMLInputElement의 위치를 가져오는 것 */
const newArticleTitleElRef = ref<HTMLInputElement>();
const newArticleBodyElRef = ref<HTMLInputElement>();
/* reactive: reactive는 기존 뷰 문법의 data 속성 느낌이고,
ref는 좀 더 리액티브 속성을 개별적으로 선언하는 느낌....??? */
/* articles 배열을 state로 선언 */
/* state는 무슨 의미????*/
/* 기존 코드에서는 props, data, methods가 같은 계층에 존재했습니다.
하지만 Vue3에서는 props와 setup이 같은 계층에 존재하고,
data는 state로, method 들은 각각의 기명함수로 작성되어 한번에 반환되도록 변화하였습니다.
state의 경우에도 그냥 선언하는 것이 아니라, vue reactive를 사용하게 되었습니다.
Reactive(반응형)는 Vue가 반응형 시스템을 유지하기 위해서 사용하는 간단한 JavaScript 객체입니다.
Reactive는 아래와 같이 동작합니다.(그림: https://media.vlpt.us/images/bluestragglr/post/50e4c7c7-c697-4c5a-8329-820828389777/Untitled.png)
Vue2 에서는 data나 method, computed 등을 선언하게 되면 알아서 각각에 대해 위와 같이 동작하는 reactive 객체를 생성하였습니다.
하지만 그 과정이 묵시적이었고, 유저들은 위 과정을 알 필요가 없었습니다.
하지만 Vue3에서의 타입스크립트의 지원이나 state로의 명칭 변경, 명시적 reactive 사용 등을 보았을 때,
전반적으로 명료한 선언을 지향하는 방향으로 변화한 것으로 보입니다.
*/
const state = reactive({
articles: [] as IArticle[]
});
function loadArticles(boardId:number){
mainApi.article_list(boardId)
.then(axiosResponse => {
state.articles = axiosResponse.data.body.articles;
});
}
// 바로 실행하는 것이 아닌 모든 것이 준비되었을때 실행됨
onMounted(() => {
// route.query.boardId as any ?? "1"
// route.query.boardId가 null이거나 undifind이면 1을 적용하라는 의미
const boardId = parseInt(route.query.boardId as string ?? "1");
loadArticles(boardId);
});
/* 공백 체크 */
function checkAndWriteArticle(){
//일반적으로 안해도 되지만 typescript에서는 해야됨
if(newArticleTitleElRef.value == null){
return;
}
const newArticleTitleEl = newArticleTitleElRef.value;
newArticleTitleEl.value = newArticleTitleEl.value.trim();
if(newArticleTitleEl.value.length == 0){
alert('제목을 입력해 주세요.')
newArticleTitleEl.focus();
return;
}
if(newArticleBodyElRef.value == null){
return;
}
const newArticleBodyEl = newArticleBodyElRef.value;
newArticleBodyEl.value = newArticleBodyEl.value.trim();
if(newArticleBodyEl.value.length == 0){
alert('내용을 입력해 주세요.')
newArticleBodyEl.focus();
return;
}
// 글작성 함수로 보내기
writeArticle(newArticleTitleEl.value, newArticleBodyEl.value);
// 글작성 후 내용 초기화
newArticleTitleEl.value = '';
newArticleBodyEl.value = '';
}
//typescript에서는 title:string, body:string 이런식으로 type을 적어주어야 한다
function writeArticle(title:string, body:string){
//const newArticle = new Article(title, body);
//state.articles.push(newArticle);
}
return{
state,
newArticleTitleElRef,
newArticleBodyElRef,
checkAndWriteArticle
}
}
/* 전역 컴포넌트 적용으로 안해도 됨
components: {
TitleBar
}
*/
})
</script>
<style scoped>
</style>
'Vue.js > Spring & Vue APP 프로젝트(프론트엔드)' 카테고리의 다른 글
21.03.11 lamplight서비스 프로젝트(초기셋팅, 리스트페이지 디자인, 백엔드 게시물불러오기,입력값보내기 테스트까지 완료) (0) | 2021.03.11 |
---|---|
21.03.03 lamplight서비스 프로젝트(글쓰기,상세페이지,로그인 ~ 회원가입까지) (0) | 2021.03.03 |
21.02.22 lamplight서비스 프로젝트(FormRow 전역 컴포넌트 적용, 상황별 버튼 셋팅, 폰트 적용 등) (0) | 2021.02.22 |
21.02.21 clean서비스 프로젝트(TitleBar 전역 컴포넌트 개념 도입, slot 도입) (0) | 2021.02.21 |
21.02.20 clean서비스 프로젝트(vue 개발환경 셋팅 및 기초 연습) (0) | 2021.02.20 |