2022년 4월 15일

Tips - Vue 기반 앱 프로그램 개발 과정에서 경험한 유용한 것들

처음으로 Vue 기술을 적용하여 간단한 웹 프로그램을 개발하는 과정에서 경험한 유용한 것들을 순서로 기술하였다. ( WebPack 를 사용하여 간단한 웹 페이지를 만들어본 경험이 많은 도움이 되었다. )

UI Kit

화면 UI 구현은 Vue-Material-Kit 을 사용하였다. 백문이 불여일견(百聞不如一見) 이라고 했던가 Vue 를 잘 모르는 상황에서 개발을 시작하는데 많은 도움이 되었다.
 

인증

JWT 기반의 인증을 사용하는 것이 보편적인 것 같으며 관련 코드는  JWT authentication from scratch with Vue.js and Node.js  자료를 참고하였다. 참고로 REST 통신 부분은 원 소스 방식이 아닌 axios 를 사용하였다.

Service 객체는 통신만 담당하고 결과 데이터는 Store 에 저장된다.  UI는 이벤트를 통하여 결과를 전달 받는  소스 구현 방식이 생소하게 보인다.  (아주 오래전 경험한 어도비 FLEX 의 MVC 프레임워크인 Cairngorm 기반 응용 프로그램 개발이 연상되기도 한다.)

Dynamic Route Matching  

동적 라우팅 매칭을 이용하여 Spring 의 @PathVariable 와 같이 구현하였다. 정규식을 지원하기 때문에  파라메터 값을 String 이 아닌 Number  값으로 전달할 수 도 있다. 아쉬운 것은 route 가 아닌 history back 경우는 Number 타입이 아닌 String 타입으로 값이 전달되어 경고가 발생하는 이슈가 있었다.

{
path: "/albums",
name: "albums",
components: { default: Albums, header: MainNavbar, footer: MainFooter },
props: {
header: { colorOnScroll: 400 },
footer: { backgroundColor: "black" }
},
beforeEnter: ifAuthenticated
},
{
path: "/albums/:albumId",
name: "album",
components: { default: Album, header: MainNavbar, footer: MainFooter },
props: {
header: { colorOnScroll: 320 },
default: true,
footer: { backgroundColor: "black" }
},
beforeEnter: ifAuthenticated
},
view raw router.js hosted with ❤ by GitHub

Editor 

간단하게 CKEditor5 를 이용하여 구현이 가능했지만 첨부 이미지 처리를 위하여 사용자 정의 이미지 어뎁터를 구현해주어야 하는 이슈가 있었으나 홈페이지에서 관련 예제가 제공되어 기술적 어려움은 없었다. 

import Vue from "vue";
import { authHeader } from "@/utils";
export default class ImageUploadAdapter {
constructor( loader , url ) {
// The file loader instance to use during the upload.
this.loader = loader;
this.url = url || `${Vue.Constants.API_ROOT_URL}/data/images/0/upload`;
}
// Starts the upload process.
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
// Aborts the upload process.
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
xhr.open( 'POST', this.url , true );
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}
// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve( {
default: getDownloadUrl(response[0]),
} );
} );
// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
// Prepares the data and sends the request.
_sendRequest( file ) {
// Prepare the form data.
const data = new FormData();
data.append( 'upload', file );
// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.
this.xhr.setRequestHeader("Authorization", authHeader().Authorization);
// Send the request.
this.xhr.send( data );
}
}
function getDownloadUrl(item) {
return encodeURI(`${Vue.Constants.API_ROOT_URL}/download/images/${item.linkId}`);
}
export function ImageUploadAdapterPlugin( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
// Configure the URL to the upload script in your back-end here!
return new ImageUploadAdapter( loader );
};
}

참고자료

댓글 없음:

댓글 쓰기