728x90
반응형 데이터를 만들기 위한 함수

ref

<script setup>
import { ref } from 'vue'

let id = 0

const newTodo = ref('')
const hideCompleted = ref(false)
const todos = ref([
  { id: id++, text: 'HTML 배우기', done: true },
  { id: id++, text: 'JavaScript 배우기', done: true },
  { id: id++, text: 'Vue 배우기', done: false }
])
  • 사용 대상: 모든 종류의 값 (기본 데이터 타입 또는 객체/배열).
  • 접근 방법: .value 속성을 통해 값에 접근하고 변경합니다.
  • 주요 특징: 기본 데이터 타입(예: 문자열, 숫자, 불리언)에 반응성을 부여할 수 있으며, 객체나 배열에도 사용 가능하지만, 단일 값에 대한 반응성을 관리하는 데 주로 사용됩니다.

reactive

<script setup>
import { reactive } from 'vue'

let id = 0

const state = reactive({
  newTodo: '',
  hideCompleted: false,
  todos: [
    { id: id++, text: 'HTML 배우기', done: true },
    { id: id++, text: 'JavaScript 배우기', done: true },
    { id: id++, text: 'Vue 배우기', done: false }
  ]
})
</script>
  • 사용 대상: 객체나 배열.
  • 접근 방법: .value 속성 없이 객체나 배열 내부의 속성에 직접 접근합니다.
  • 주요 특징: 객체나 배열 전체에 대한 반응성을 부여합니다. 중첩된 객체나 배열을 포함하여 구조 내의 모든 데이터에 반응성이 적용됩니다.

 

reactive 사용의 이점

  1. 직관적인 접근과 수정: reactive 객체의 속성에 접근하거나 수정할 때, .value를 사용할 필요가 없습니다. 이는 코드를 좀 더 직관적으로 만들고, 일반 JavaScript 객체와 동일한 방식으로 작업할 수 있게 합니다.
  2. 중첩된 반응성: reactive를 사용하면, 객체 내의 중첩된 속성도 자동으로 반응성이 적용됩니다. 따라서 객체 내부 구조가 복잡해지거나 중첩된 경우에도, 별도의 처리 없이 모든 레벨에서 반응성을 유지할 수 있습니다.
  3. 코드 조직: 여러 반응형 데이터를 하나의 reactive 객체에 그룹화하여 관리할 수 있습니다. 이는 관련 데이터와 로직을 함께 묶어서 관리하기 편리하게 해줍니다, 코드의 가독성과 유지보수성을 향상시킬 수 있습니다.

선택 기준

  • 기본 타입(문자열, 숫자, 불리언 등)에 대한 반응성이 필요한 경우, 또는 Vue 템플릿에서 직접 참조되는 단일 변수에 대해서는 ref를 사용하는 것이 좋습니다.
  • 객체나 배열과 같은 복합적인 데이터 구조에 대해 반응성을 제공하고, 중첩된 데이터 구조를 다루거나 여러 관련 데이터를 함께 관리해야 하는 경우 reactive를 사용하는 것이 더 적합할 수 있습니다.
728x90

전통적인 Options API

 

Vue에서의 스크립트 구현은 크게 두가지 방식이 있다. 전통적인 Options API와 Composition API이다.

전통적인 Options API

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo">
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      newTodo: '',
      todos: [],
      nextId: 0
    }
  },
  methods: {
    addTodo() {
      this.todos.push({ id: this.nextId++, text: this.newTodo });
      this.newTodo = ''; // 입력 필드 초기화
    }
  }
}
</script>

Composition API

Vue 3의 Composition API는 컴포넌트 로직을 구성하는 새로운 방법을 제공합니다. 이 API의 도입은 Vue 애플리케이션을 설계하고 개발하는 방식에 근본적인 변화를 가져왔습니다. Composition API의 특징과 장점은 다음과 같습니다:

특징

  1. 반응형 상태 정의: refreactive 함수를 사용하여 반응형 상태를 선언적으로 정의할 수 있습니다. 이 상태들은 컴포넌트의 템플릿에서 직접 사용될 수 있으며, 상태 변경 시 자동으로 화면을 업데이트합니다.
  2. setup 함수: 컴포넌트의 반응형 상태, 계산된 속성, 메서드 등 모든 로직을 setup 함수 내에 정의합니다. setup 함수는 컴포넌트의 옵션/라이프사이클 훅보다 먼저 실행되며, 여기에서 정의된 모든 값과 함수는 템플릿에서 직접 사용할 수 있습니다.
  3. 함수 기반의 구성: 로직을 재사용 가능한 함수로 캡슐화하고 조합하여 컴포넌트를 구성할 수 있습니다. 이러한 접근 방식은 컴포넌트 간에 코드를 재사용하고 조합하기 쉽게 만듭니다.

장점

  1. 로직의 재사용 및 조직화: Composition API를 사용하면 로직을 기능별로 구성하고 필요한 곳에서 재사용할 수 있습니다. 이는 코드의 중복을 줄이고, 프로젝트의 유지보수성을 향상시킵니다.
  2. 유연성: 컴포넌트의 로직을 더 유연하게 조직할 수 있습니다. 예를 들어, 관련된 모든 반응형 상태와 함수를 함께 그룹화하여 관리할 수 있으며, 이는 컴포넌트의 가독성을 크게 향상시킵니다.
  3. 타입스크립트와의 호환성: Composition API는 타입스크립트와 더욱 잘 호환됩니다. setup 함수 내에서 정의된 상태와 함수에 타입을 적용하기 쉬워, 타입스크립트를 사용하는 프로젝트에서 타입 안정성과 개발 경험을 향상시킬 수 있습니다.
  4. 응집력 있는 코드 베이스: 기능별로 로직을 그룹화하고 재사용할 수 있기 때문에, 관련 로직이 물리적으로 가까운 위치에 있게 됩니다. 이는 응집력 있는 코드 베이스를 유지하는 데 도움이 됩니다.
  5. 더 나은 성능: Composition API를 사용하면 필요한 로직만을 정확히 구성하여 사용할 수 있기 때문에, 불필요한 로직의 실행을 최소화할 수 있습니다. 이는 애플리케이션의 성능을 최적화하는 데 기여할 수 있습니다.

Composition API는 Vue 애플리케이션을 개발할 때 더 큰 유연성과 향상된 코드 재사용성을 제공합니다. 이는 특히 대규모 애플리케이션 또는 복잡한 컴포넌트 구조를 가진 프로젝

트에서 그 장점이 두드러집니다.

Vue 3 초기 버전에서의 Composition API 사용 예시

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" />
    <ul>
      <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const newTodo = ref('');
    const todos = ref([]);

    const addTodo = () => {
      todos.value.push({ id: todos.value.length + 1, text: newTodo.value });
      newTodo.value = '';
    };

    return { newTodo, todos, addTodo };
  }
};
</script>

Vue 3 초기 버전에서 Composition API를 사용할 때는 setup 함수 내에서 컴포넌트의 반응형 상태, 계산된 속성, 메서드 등을 정의하고, 필요한 값들을 객체로 반환하여 템플릿에서 사용할 수 있도록 했습니다.

 

Vue 3.2 이후에서의 <script setup> 구문 사용 예시

Vue 3.2부터 도입된 <script setup> 구문을 사용하면, setup 함수 내에서 반환 과정 없이 직접 변수와 함수를 정의할 수 있으며, 이들은 자동으로 템플릿에서 사용 가능해집니다. 이로 인해 코드가 더욱 간결하고 선언적으로 변화했습니다.

 

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" />
    <ul>
      <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const newTodo = ref('');
const todos = ref([]);

const addTodo = () => {
  todos.value.push({ id: todos.value.length + 1, text: newTodo.value });
  newTodo.value = '';
};
</script>

주요 차이점

  • 코드 간결성: <script setup> 구문을 사용하면, 컴포넌트의 로직을 더욱 간결하게 작성할 수 있습니다. setup 함수 내에서 반환 과정 없이 모든 로직을 정의할 수 있으며, 이는 코드의 가독성과 유지보수성을 크게 향상시킵니다.
  • 자동 import 추론: <script setup>은 IDE와 도구에서 더 나은 타입 추론과 자동 완성을 제공합니다. 또한, Vue 컴파일러는 <script setup> 내에서 사용된 Vue API를 자동으로 감지하여 필요한 코드를 자동으로 import 합니다.
  • 컴포넌트 정의의 선언적 표현: <script setup> 구문은 컴포넌트의 정의를 더 선언적으로 만들어 줍니다. 이는 Vue 컴포넌트의 구조와 로직을 더 명확하게 표현할 수 있게 해줍니다.

<script setup> 구문은 Vue 3.2 이상에서 Composition API를 사용하는 새로운 표준이 되었으며, 많은 개발자들이 이 새로운 패턴을 선호하고 있습니다.

728x90

백엔드에서 이미지 파일을 static 폴더에 올렸다면 일반적으로는 프론트에서 참조할 수가 없다.

하지만 express는 이미지 파일과 같은 정적 파일들을 쉽게 참조하여 반환할 수 있는 메소드를 제공한다.

 

| Back-end

app.use('/static', express.static(path.join(__dirname, 'static')));

express.static 메소드는 node프로세스가 실행되는 path에 따라 상대적이므로 위와 같이 환경변수인 __dirname을 써서 디렉토리명을 지정해줄 수 있다. node 프로세스가 실행되는 같은 경로의 static 폴더를 참조한다.

 

그러면 아래와 같이 참조했을 때 백엔드의 static 디렉토리내의 모든 파일을 참조할 수 있게 된다.

http://localhost:3000/api/static/images/mt/resize-1697954077172.jpg

 

| Front-end

다음은 nuxt.config.js를 수정하여

api라는 경로로 들어왔을 때 경로를 다시 쓰기해주도록 한다.

 axios: {
        proxy: true,
        prefix: '/api'
    },

    proxy: {
        '/api': { target: process.env.AXIOS_BASE_URL, pathRewrite: { '^/api/': '/' } },
    },

[프론트 .env 폴더에 정의된 api 주소]

AXIOS_BASE_URL=http://127.0.0.1:10072

 

만약 DB에 저장된 path과 다음과 같다면 프론트의 메소드나 computed 속성으로 경로를 반환하면 될 것이다.

static/images/mt/resize-1697954077178.jpg

 

  getImageUrl(img) {
            if (img.path && img.path.includes('static/images')) {
                return '/api/' + img.path;
            }
            return URL.createObjectURL(img);
        },

그러면 백엔드에서 굳이 파일객체를 리턴한다는지 리소스가 많이들고 비효율적인 방법을 쓰지않고도 쉽게 리소스를 참조할 수 있게 된다.

728x90

Nuxt.js / Node.js로 구성된 프로젝트에서 다음과 같은 에러가 뜨고 백엔드 API 요청이 되지 않는 현상이 있었다.

Error occurred while trying to proxy request

 

결론적으로 아래 환경 변수 (프론트의 .env)에서 localhost를 127.0.0.1 로 바꾸니 해결되었다.

AXIOS_BASE_URL=http://localhost:8071

m1 mac과 node18 버전에서 생기는 호환성 문제라고 하는데 이것도 환경마다 차이가 있어서 정확한 원인을 알 수가 없다.

아시는 분은 댓글 달아주세요~!

 

| 참고

https://github.com/chimurai/http-proxy-middleware/issues/171

728x90

Template

 <tr
    v-for="(diary, idx) in f_rec_diary_list"
    :key="idx"
    draggable="true"
    @dragstart="drag_area(idx, 'M')"
    @dragend="drag_end_area"
    @dragover.prevent
    @dragenter="drag_enter_area(idx)"
    @drop="drop_area(idx, f_rec_diary_list)"
>

 

Data

data() {
        return {            
            dragged_area_index: null,
            hover_area_index: null,
            dragged_item_index: null,
            hover_item_index: null,
            dragged_type: null,
        };
},

 

Methods

drag_area(index, type) {
    console.log("drag_area -> index", index, "type", type);
    this.dragged_area_index = index;
    this.dragged_type = type;
},
drag_enter_area(index) {
    this.hover_area_index = index;
},
drag_end_area() {
    this.hover_area_index = null;
},
async drop_area(drop_index, parent) {
    if (this.dragged_type !== "M") {
        return;
    }

    const dragged_item = parent.splice(this.dragged_area_index, 1)[0];
    parent.splice(drop_index, 0, dragged_item);

    console.log('parent', parent);

    // Update seq_no values after reordering
    for (let i = 0; i < parent.length; i++) {
        parent[i].seq_no = i + 1;
    }

    console.log("dragged_item", dragged_item);

    this.dragged_area_index = null;
    this.hover_area_index = null;
    this.dragged_type = null;
},
drag_item(index, type) {
    this.dragged_item_index = index;
    this.dragged_type = type;
},
drag_enter_item(index) {
    this.hover_item_index = index;
},
drag_end_item() {
    this.hover_item_index = null;
},
728x90

MathJax는 웹 브라우저에 수학 표기법을 표시하기 위한 JS Library이며 아래와 같은 형식을 따른다.

\(-{1 \over {500}}x + 212\)

 

프론트엔드에서 위 MathJax 수식을 올바르게 보여주기 위해 아래 과정이 필요하다.

 

1. nuxt.config.js의 head에 스크립트 코드 추가

 // Global page headers: https://go.nuxtjs.dev/config-head
    head: {
         ( ... ) 
        script: [
            {
              src: 'https://polyfill.io/v3/polyfill.min.js?features=es6',
              type: 'text/javascript',
              body: true
            },
            {
              src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js',
              type: 'text/javascript',
              body: true
            }
        ]
    },

 

2. 프론트 페이지의 mounted에 아래 코드 추가

async mounted() {
    // mathjax 수식 렌더링 적용
    if (window.MathJax) {
        window.MathJax.typeset();
    }
}

 

그러면 아래와 같은 html 요소가 있을 때 수식이 제대로 보여지게 된다.

<p v-html="math_exp"></p>
 
data() {
	return {
		math_exp : "<div class="math">&nbsp;<p>The graph of \(y = 3x - 5\) in the \(xy\)-plane is a line. What is the slope of the line?</p></div>"
}

728x90

개요

node에서 oracle client를 연동하기 위해서는 node-oracledb 라는 라이브러리가 필요하다. 이 글에서는 라이브러리 설치 및 환경변수 설정, 노드 백엔드에서의 간단한 연동법을 소개한다.

설치 및 환경변수 설정

💡 실행 환경 : linux arm64, m1 mac

 

  1. 다운로드
  • 아래 링크에서 Basic Light Package (ZIP)을 내려받는다

https://www.oracle.com/database/technologies/instant-client/linux-arm-aarch64-downloads.html

instantclient-basiclite-linux.arm64-19.10.0.0.0dbru (1).zip

  1. opt/oracle 폴더로 이동하여 압축해제
  • 경로이동

cd ~/../../opt/

/opt/oracle/instantclient_19_8 과 같이 라이브러리가 위치하도록한다.

  1. bash_profile 수정
  • vi ~/.bash_profile

DYLD_LIBRARY_PATH=/opt/oracle

(리눅스는 LD_LIBRARY_PATH)

  • source ~/.bash_profile
  1. 백엔드 실행 시 아래와 같은 오류가 없다면 정상적으로 연동된 것이다. 오류가 난다면 환경변수의 경로가 옳게 설정되었는지 다시 확인해본다.

Error: DPI-1047: Cannot locate a 64-bit Oracle Client library

사용법

  • 초기화 (라이브러리 경로 : libDir 을 확인)
const oracledb = require('oracledb');
const os = require('os');

switch (os.type().toLowerCase()) {
    case 'windows_nt':
        oracledb.initOracleClient({libDir: 'C:/EXTERNAL_LIBRARY/instantclient_21_8'});
        break;
    case 'darwin':
        oracledb.initOracleClient({libDir: '/opt/oracle/instantclient_19_8'});
        break;
    case 'linux':
        oracledb.initOracleClient({libDir: '/opt/oracle/instantclient_21_9'});
        break;
}

// 쿼리 아웃풋을 객체로 받을지 배열로 받을지 옵션 설정 (배열의 경우엔 .OUT_FORMAT_ARRAY)
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
  • 오라클 class 및 메소드 정의
class Oracle {
    constructor() {
    }
		// 접속 초기화
    async init() {
        const _this = this;
        return new Promise((resolve, reject) => {
            oracledb.getConnection({
                user: process.env.ORACLE_USER,
                password: process.env.ORACLE_PASS,
                connectString: process.env.ORACLE_HOST
            }, function (error, connection) {
                if (error) {
                    return reject(error);
                }
                _this.connection = connection;
                return resolve();
            });
        });
    }
		// 쿼리 실행
    execute(sql, params = []) {
        const _this = this;
        return new Promise((resolve, reject) => {
            _this.connection.execute(sql, params, function (error, result) {
                if (error) {
                    return reject(error);
                }
                return resolve(result.rows);
            });
        });
    }
		// 쿼리 커밋
    commit() {
        const _this = this;
        return new Promise((resolve, reject) => {
            if (_this.connection) {
                _this.connection.commit(error => {
                    _this.release();
                    if (error) {
                        return reject(error);
                    }
                    return resolve();
                });
            } else {
                return resolve();
            }
        });
    }
		// 롤백
    rollback() {
        const _this = this;
        return new Promise((resolve, reject) => {
            if (_this.connection) {
                _this.connection.rollback(error => {
                    _this.release();
                    if (error) {
                        return reject(error);
                    }
                    return resolve();
                });
            } else {
                return resolve();
            }
        });
    }
		// 접속 해제 (commit, rollback시 반드시 수행)
    release() {
        this.connection.release();
    }
}
  • 노드 백엔드의 객체 초기화 및 쿼리 실행 예제
const oracledb = new Oracle();
await oracledb.init();

( ... )
try {
	const [max_no] = await oracledb.execute(`SELECT MAX("NO") AS value FROM INTF_JJ.INTF_ES_ORDER WHERE REQ_DT = TO_CHAR(SYSDATE, 'YYYYMMDD')`);
	await oracledb.commit();
} catch (e) {
  await oracledb.rollback();
  return next(e);
}

참고

http://oracle.github.io/node-oracledb/

728x90

라디오박스라면 하나만 체크되니 css 를 수정해서 체크박스처럼 보일 수도 있겠지만

체크박스로 이미 퍼블이 된 상태에서 수정할 필요가 있어 직접 코드를 짜보았다.

 

템플릿내 코드 

 <input type="checkbox" v-model="pay_method.card" name="chkbox" id="card"
 @change="check_one( {type:'card' })" >
                            
<input type="checkbox" v-model="pay_method.bank" name="chkbox"  @change="check_one( {type:'bank' })"
 id="noBankBook">

 

data 정의

data(){
        return{            
            pay_method: { card: true, bank: false },
        }
    },

method 정의

methods: {
	check_one(element) {
      console.log('checkbox check : ', element.type)
      let obj = this.pay_method

      if (element.type) {
        for(const [key, value] of Object.entries(obj)) {          
          console.log(`${key}: ${value}`);

          if (key == element.type) {
            obj[key] = true
          } else {
            obj[key] = false
          }
        }

        const check = document.getElementById(`${element.type}`);
        
        if (check && check.checked != undefined) {
          check.checked = true;
        }
        console.log('checked', check.checked)
      } 

      console.log('type', JSON.stringify(this.search_type))

    },
}
728x90

https://github.com/microsoft/vscode-eslint/issues/696#issuecomment-512252381

 

ESLint fails to load plugins when using ESLint 6.x · Issue #696 · microsoft/vscode-eslint

I have the following packages installed as dev deps: { "babel-eslint": "^10.0.2", "eslint": "^6.0.1", "eslint-config-prettier": "^5.1.0",...

github.com

eslint 플러긴 호환성 문제인듯하다

 

1. cmd + shift + p 를 눌러

>user setting 를 검색

 

2. settings.json에서 아래 항목 추가

{
    "eslint.workingDirectories": [
        { "mode": "auto" }
    ],
 }
728x90
<script>
const TYPE = Object.freeze({
    ONE: 0, TWO: 1,THREE: 2
})
export default{
 ( ... )
}
</script>

위와 같이 const로 정의하고 Object.freeze하면 변경 불가능한 enum 상수가 된다.

 

TYPE.ONE과 같이 접근가능하고

백엔드에서는 value인 0값을 받아 처리가능하다.

+ Recent posts