2023년 7월 21일 금요일

[2023-07-21] vue에서 변수에 component 객체를 넣었더니, Vue.warn 이 떴다. markRaw를 사용하세요

안녕하세요. 클스 입니다.

vue에서 변수에 component 객체를 넣었더니, Vue.warn 이 떴다. 

component 객체는 많은 정보를 담고 있어서, watch를 하게되면 엄청난 부하가 발생한다.

그래서 친절하게 markRaw를 사용하라고 알려준다.


import { markRaw } from 'vue';

export default {
data() {
return {
layout: null,
};
},
watch: {
$route: {
immediate: true,
handler() {
this.layout = markRaw(this.$route.meta.layout || DefaultLayout);
},
},
},
};


Vue.js에서 markRaw 함수는 객체를 "원시"로 표시하는 데 사용되며, 이는 Vue 반응성 시스템에서 관찰되지 않음을 의미합니다. 특정 성능 문제가 있거나 특정 객체에 대해 Vue의 반응성 시스템을 선택 해제해야 하는 경우에만 사용하십시오.markRaw 함수는 다음과 같이 사용됩니다.
const obj = {
  a: 1,
  b: 2,
};

const rawObj = markRaw(obj);
이제 rawObj는 반응성이 없습니다. 즉, rawObj의 속성이 변경되면 Vue는 렌더링을 다시 트리거하지 않습니다.markRaw 함수는 특정 성능 문제를 해결하는 데 유용합니다. 예를 들어, 매우 큰 객체를 반응성 객체로 선언하면 Vue는 객체의 모든 속성을 추적해야 합니다. 이는 성능 저하의 원인이 될 수 있습니다. 이 경우 markRaw 함수를 사용하여 객체를 반응성으로 선언하지 않고 렌더링을 다시 트리거하지 않도록 할 수 있습니다.markRaw 함수는 또한 특정 객체에 대해 Vue의 반응성 시스템을 선택 해제해야 하는 경우에 유용합니다. 예를 들어, 객체가 외부 라이브러리에서 제공되고 Vue의 반응성 시스템과 호환되지 않는 경우 markRaw 함수를 사용하여 객체를 반응성으로 선언하지 않고 렌더링을 다시 트리거하지 않도록 할 수 있습니다.

라벨: , ,

2023년 6월 8일 목요일

[2023-06-07] Quasar dotenv 로 multi profile 구축하기

 안녕하세요. 클스 입니다.


프로젝트를 하다보면 local, dev, test, staging, production 등 다양한 환경으로 API 주소등 환경을 다르게 가져가야 할 때가 있습니다.

이때 필요한 것이 env 파일을 이용해야 합니다.


Quasar 는 process.env 를 기본으로 제공합니다. quasar.config.js에 환경 변수를 넣어주면

process.env.변수 이렇게 사용하면 됩니다.

https://quasar.dev/quasar-cli-webpack/handling-process-env/

그러나 별도 파일로 구성하면 유지관리가 직관적이고 편리하기 때문에 dotenv 패키지를 많이 사용합니다.

1) dotenv 설치하기

$ yarn add dotenv

$ .env 폴더 생성 ==> project root에 생성합니다.



2) .env.local 파일 생성 후 아래 추가

APP_TITLE=':Admin'
APP_MODE='local-로컬'
APP_VERSION='0.6.5'
API_URL=http://localhost:8001

3) quasar.config.js 에 아래 추가

/* eslint-env node */

/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/

// Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js

const { configure } = require('quasar/wrappers');
const path = require('path');
const DotEnv = require('dotenv'); ==> 여기 추가

그리고 build 섹션에 아래와 같이 추가합니다.

build: {
env: DotEnv.config({ path: `./.env/.env.${process.env.ENVIRONMENT}` }).parsed,
}

4) App.vue 등 vue 파일에서 사용하기

<template>
<router-view />
</template>

<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';

export default defineComponent({
name: 'App',
components: {
// TODO:
},
setup() {
return {
//TODO:do something
};
},
mounted() {
// console.log('App Info:', process.env);
console.log('App Info:', process.env.APP_TITLE, '-', process.env.APP_MODE);
},

watch: {
$route: {
immediate: true,
handler(to) {
document.title = to.meta.title ? to.meta.title + ' ::: KELU' : 'KELU' + '::' + process.env.APP_MODE;
},
},
},
});
</script>


여기서는 route 정보를 이용해서 app title을 변경하였습니다.


5) 실행하기

파일명이 .env.local 이므로 local를 환경변수로 받습니다.
실행할때 ENVIRONMENT 를 환경변수로 넣어주어 실행합니다.

$ ENVIRONMENT=local quasar dev .d88888b. d88P" "Y88b 888 888 888 888 888 888 8888b. .d8888b 8888b. 888d888 888 888 888 888 "88b 88K "88b 888P" 888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888 Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888 "Y888888" "Y88888 "Y888888 88888P' "Y888888 888 Y8b


ENVIRONMENT 를 적절하게 변경하면 본인에게 맞는 환경을 쉽게 구축할 수 있습니다.

이상 클스 였습니다.



라벨: , , ,

2023년 5월 31일 수요일

[2023-05-31] Quasar + vite + vue 에서 raw-loader를 대체하기 vite-raw-plugin

안녕하세요. 클스 입니다.

오늘은 5월의 마지막 날이네요

Quasar는 vue3 + vite를 기반으로 합니다. 그런데 초반에 text 파일을 읽어서 처리하는데

raw-loader 를 사용했습니다. 이것은 webpack 기반이라 vite와는 맞지 않습니다.

그래서 yarn install 을 하면 아래와 같이 경고가 발생합니다.

yarn install v1.22.19
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
[3/5] 🚚  Fetching packages...
[4/5] 🔗  Linking dependencies...
warning " > raw-loader@4.0.2" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
[5/5] 🔨  Building fresh packages...
✨  Done in 4.53s.

오늘은 똑 같은 기능을 하면서 vite 기반의 라이브러리로 대체해보고자 합니다.

먼저 raw-loader 삭제 합니다.
$ yarn remove raw-loader

그리고 vite-raw-plugin 을 설치합니다.
$ yarn add vite-raw-plugin

소스는 하나도 수정할 필요가 없습니다.

package.json 에 정상적으로 삭제되고 설치된 것을 확인 할 수 있습니다.
"vite-raw-plugin": "^1.0.1",

<script lang="ts">
import { useQuasar } from 'quasar';
import { defineComponent, defineAsyncComponent, ref } from 'vue';
import getVueTemplate from 'src/ztemplate/vue_gen_template.txt?raw';

... 생략 ...

export default defineComponent({
name: 'LoginPageEx',
components: {
    //TODO: Your components
},

setup() {
const $q = useQuasar();
return {
    //TODO:
};
},
data() {
return {
    //TODO:
};
},
methods: {
showFile() {
console.log('getVueTemplate > ', getVueTemplate);
this.$q
.dialog({
title: 'Source Code Here <클립보드로 들어가 있어요>!!!',
message: getVueTemplate,
style: 'min-width:50vw;min-height=50vh',
html: false,
})
.onOk(() => {
// console.log('OK')
})
.onCancel(() => {
// console.log('Cancel')
})
.onDismiss(() => {
// console.log('I am triggered on both OK and Cancel')
});
},
... 생략 ...

기존소스 변경없이 적용되니 좋네요~

이상입니다.

라벨: , , , ,

2023년 4월 20일 목요일

[2023-04-19] vscode로 typescript 개발 환경(Menu 테이블을 Tree형태로 구성)

안녕하세요. 클스 입니다.

typescript 가 이젠 많이 중요해 졌습니다. vuejs, react 등에서 빠질 수 가 없네요~


개요

- DB의 메뉴 테이블에서 데이터를 가져와서 웹에 tree 구조로 출력해보려고 하니 웹을 올리고 소스 수정하면 빌드하고 확인하는 시간들이 많이 들어요~

- 그래서 typescript만 개발하면 좋을 듯하여 셋팅 해봤습니다.


메뉴 테이블 쿼리

WITH RECURSIVE tree_view(menu_id, menu_nm, up_menu_id, depth, path) AS (
    SELECT 
        menu_id, 
        menu_nm, 
        up_menu_id, 
        1 as depth, 
        CAST(menu_id AS VARCHAR(100)) as path
    FROM tb_menu
    WHERE up_menu_id is NULL -- 최상위 노드를 선택합니다
UNION ALL    
    SELECT 
        child.menu_id, 
        child.menu_nm, 
        child.up_menu_id, 
        parent.depth + 1 as depth, 
        CONCAT(parent.path, '_', child.menu_id) as path
    FROM tb_menu child
    JOIN tree_view parent 
      ON child.up_menu_id = parent.menu_id
)
SELECT 
    menu_id, menu_nm, up_menu_id, depth, path
    , CONCAT( RIGHT('----------',depth*3), menu_nm, ' ') AS parent_child_tree
FROM tree_view
ORDER BY path;


tree_view 조회 결과

menu_idmenu_nmup_menu_iddepthpathparent_child_tree
M00192메뉴/권한 관리M000013ROOT_M00001_M00192---------메뉴/권한 관리
M00190권한 관리M001924ROOT_M00001_M00192_M00190----------권한 관리
M00191메뉴 관리M001924ROOT_M00001_M00192_M00191----------메뉴 관리
M00193회사/조직 관리M000013ROOT_M00001_M00193---------회사/조직 관리
M00187회사 관리M001934ROOT_M00001_M00193_M00187----------회사 관리
M00188조직 관리M001934ROOT_M00001_M00193_M00188----------조직 관리
M00189사용자 관리M001934ROOT_M00001_M00193_M00189----------사용자 관리
M00194시스템관리M000013ROOT_M00001_M00194---------시스템관리


데이터를 tree 형태로 만드는 tree_test.ts 

- 조금 범용적으로 만들기 위해 menu_id -> id로 menu_nm -> name으로 , up_menu_id -> up_id 로 설계 했습니다.


필수 패키지 설치 및 vscode extension 설치

* 패키지 설치
$ sudo npm install -g typescript
$ sudo npm install -g ts-node    <누락되서 나중에 설치했어요>
$ mkdir tsTest
$ cd tsTest
$ npm init -y   #초기 프로젝트 설정
$ code .   # vscode 실행

* vscode extension 설치
$ Code Runner 설치하고 sample.ts 파이를 만들고, CTRL + ALT + N 하면 실행된다.
  한가지 아쉬운점은 jupyter notebook 처럼 변수 등등 디버깅을 모르겠다. ㅠ.ㅠ
   

      해당 디렉토리에 package.json 이 생긴다. $ npm install 을하면 package.json에 정의된 모듈들이 설치됩니다. 지금은 아무것도 없어서 package-lock.json 만 생기네요~


       * Code Runner 확장 프그램 설치

       



vscode에서 tree_test.ts 파일을 만든다

소스에는 원본 데이터 -> Tree 자료 구조 -> 출력 하는 함수가 포함되어 있습니다.

interface item {
  id: string;
  name: string;
  up_id: string | null;
}

interface TreeNode {
  id: string;
  up_id: string | null;
  label: string;
  avatar?: string;
  icon?: string;
  disabled?: boolean;
  children?: TreeNode[];
}

function buildTree(items: item[]): TreeNode[] {
  const menuMap = new Map<string, item>();
  const treeMap = new Map<string, TreeNode>();

  // 메뉴 항목 맵 만들기
  for (const item of items) {
    menuMap.set(item.id, item);
  }

  // 트리 노드 맵 만들기
  for (const [id, item] of menuMap) {
    const treeNode: TreeNode = {
      id: item.id,
      up_id: item.up_id,
      label: item.name,
    };

    treeMap.set(id, treeNode);

    // 부모 노드가 있는 경우, 부모 노드에 자식 노드 추가하기
    const parentId = item.up_id;

    if (parentId !== null) {
      const parent = treeMap.get(parentId);

      if (parent !== undefined) {
        if (parent.children === undefined) {
          parent.children = [];
        }

        parent.children.push(treeNode);
        console.log(treeNode);
      }
    }
  }

  // 최상위 노드들 찾기
  const rootNodes: TreeNode[] = [];

  for (const [id, item] of menuMap) {
    if (item.up_id === null) {
      const rootNode = treeMap.get(id);

      if (rootNode !== undefined) {
        rootNodes.push(rootNode);
        console.log(rootNode);
      }
    }
  }

  return rootNodes;
}

function printTree(treeNodes: TreeNode[], depth = 0) {
  for (const treeNode of treeNodes) {
    const indent = " ".repeat(depth * 2);
    console.log(`${indent}${treeNode.label} (${treeNode.id})`);

    if (treeNode.children !== undefined) {
      printTree(treeNode.children, depth + 1);
    }
  }
}

// 임의의 데이터 배열
const menuData = [
  { id: "ROOT", up_id: null, name: "ROOT" },
  { id: "M1", up_id: "ROOT", name: "메뉴 1" },
  { id: "M2", up_id: "M1", name: "서브메뉴 1-1" },
  { id: "M3", up_id: "M1", name: "서브메뉴 1-2" },
  { id: "M4", up_id: "ROOT", name: "메뉴 2" },
  { id: "M5", up_id: "M4", name: "서브메뉴 2-1" },
  { id: "M6", up_id: "M5", name: "서브메뉴 2-1-1" },
  { id: "M7", up_id: "M5", name: "서브메뉴 2-1-2" },
  { id: "M8", up_id: "M4", name: "서브메뉴 2-2" },
];

const tree = buildTree(menuData);
console.log(tree);

console.log("---BEGIN----------------------------------------------");
printTree(tree);
console.log("----END---------------------------------------------");

const menuData2 = [
  { id: "ROOT", name: "ROOT", up_id: null },
  { id: "M00001", name: "관리자", up_id: "ROOT" },
  { id: "M00192", name: "메뉴/권한 관리", up_id: "M00001" },
  { id: "M00190", name: "권한 관리", up_id: "M00192" },
  { id: "M00191", name: "메뉴 관리", up_id: "M00192" },
  { id: "M00193", name: "회사/조직 관리", up_id: "M00001" },
  { id: "M00187", name: "회사 관리", up_id: "M00193" },
  { id: "M00188", name: "조직 관리", up_id: "M00193" },
  { id: "M00189", name: "사용자 관리", up_id: "M00193" },
  { id: "M00194", name: "시스템관리", up_id: "M00001" },
  { id: "M00182", name: "공통코드 관리", up_id: "M00194" },
  { id: "M00183", name: "용어사전 관리", up_id: "M00194" },
  { id: "M00184", name: "로그 이력", up_id: "M00194" },
  { id: "M00235", name: "휴일 관리", up_id: "M00194" },
  { id: "M00236", name: "게시판 관리", up_id: "M00194" },
  { id: "M00237", name: "알림 관리", up_id: "M00194" },
  { id: "M00243", name: "사용자별 사용 현황", up_id: "M00194" },
  { id: "M00195", name: "화면 마법사", up_id: "M00001" },
  { id: "M00178", name: "조회 조건 관리", up_id: "M00195" },
  { id: "M00179", name: "메타정보 정의", up_id: "M00195" },
  { id: "M00180", name: "포틀릿 관리", up_id: "M00195" },
  { id: "M00181", name: "화면 마법사", up_id: "M00195" },
];

const tree2 = buildTree(menuData2);
// console.log(tree2);

console.log("---BEGIN----------------------------------------------");
printTree(tree2);
console.log("----END---------------------------------------------");


실행하기



     마우스 우 클릭을 하면 Run Code 가 나온다. 핫키는 Ctrol+Option +N 입니다.

실행 결과


Done] exited with code=127 in 0.01 seconds

[Running] ts-node "/Users/keunsookim/quasar/typescript/tree_test.ts"
{ id: 'M1', up_id: 'ROOT', label: '메뉴 1' }
{ id: 'M2', up_id: 'M1', label: '서브메뉴 1-1' }
{ id: 'M3', up_id: 'M1', label: '서브메뉴 1-2' }
{ id: 'M4', up_id: 'ROOT', label: '메뉴 2' }
{ id: 'M5', up_id: 'M4', label: '서브메뉴 2-1' }
{ id: 'M6', up_id: 'M5', label: '서브메뉴 2-1-1' }
{ id: 'M7', up_id: 'M5', label: '서브메뉴 2-1-2' }
{ id: 'M8', up_id: 'M4', label: '서브메뉴 2-2' }
{
id: 'ROOT',
up_id: null,
label: 'ROOT',
children: [
{ id: 'M1', up_id: 'ROOT', label: '메뉴 1', children: [Array] },
{ id: 'M4', up_id: 'ROOT', label: '메뉴 2', children: [Array] }
]
}
[
{
id: 'ROOT',
up_id: null,
label: 'ROOT',
children: [ [Object], [Object] ]
}
]
---BEGIN----------------------------------------------
ROOT (ROOT)
메뉴 1 (M1)
서브메뉴 1-1 (M2)
서브메뉴 1-2 (M3)
메뉴 2 (M4)
서브메뉴 2-1 (M5)
서브메뉴 2-1-1 (M6)
서브메뉴 2-1-2 (M7)
서브메뉴 2-2 (M8)
----END---------------------------------------------
{ id: 'M00001', up_id: 'ROOT', label: '관리자' }
{ id: 'M00192', up_id: 'M00001', label: '메뉴/권한 관리' }
{ id: 'M00190', up_id: 'M00192', label: '권한 관리' }
{ id: 'M00191', up_id: 'M00192', label: '메뉴 관리' }
{ id: 'M00193', up_id: 'M00001', label: '회사/조직 관리' }
{ id: 'M00187', up_id: 'M00193', label: '회사 관리' }
{ id: 'M00188', up_id: 'M00193', label: '조직 관리' }
{ id: 'M00189', up_id: 'M00193', label: '사용자 관리' }
{ id: 'M00194', up_id: 'M00001', label: '시스템관리' }
{ id: 'M00182', up_id: 'M00194', label: '공통코드 관리' }
{ id: 'M00183', up_id: 'M00194', label: '용어사전 관리' }
{ id: 'M00184', up_id: 'M00194', label: '로그 이력' }
{ id: 'M00235', up_id: 'M00194', label: '휴일 관리' }
{ id: 'M00236', up_id: 'M00194', label: '게시판 관리' }
{ id: 'M00237', up_id: 'M00194', label: '알림 관리' }
{ id: 'M00243', up_id: 'M00194', label: '사용자별 사용 현황' }
{ id: 'M00195', up_id: 'M00001', label: '화면 마법사' }
{ id: 'M00178', up_id: 'M00195', label: '조회 조건 관리' }
{ id: 'M00179', up_id: 'M00195', label: '메타정보 정의' }
{ id: 'M00180', up_id: 'M00195', label: '포틀릿 관리' }
{ id: 'M00181', up_id: 'M00195', label: '화면 마법사' }
{
id: 'ROOT',
up_id: null,
label: 'ROOT',
children: [ { id: 'M00001', up_id: 'ROOT', label: '관리자', children: [Array] } ]
}
---BEGIN----------------------------------------------
ROOT (ROOT)
관리자 (M00001)
메뉴/권한 관리 (M00192)
권한 관리 (M00190)
메뉴 관리 (M00191)
회사/조직 관리 (M00193)
회사 관리 (M00187)
조직 관리 (M00188)
사용자 관리 (M00189)
시스템관리 (M00194)
공통코드 관리 (M00182)
용어사전 관리 (M00183)
로그 이력 (M00184)
휴일 관리 (M00235)
게시판 관리 (M00236)
알림 관리 (M00237)
사용자별 사용 현황 (M00243)
화면 마법사 (M00195)
조회 조건 관리 (M00178)
메타정보 정의 (M00179)
포틀릿 관리 (M00180)
화면 마법사 (M00181)
----END---------------------------------------------

[Done] exited with code=0 in 0.932 seconds

마무리


이 ts 소스를 vuejs 에 넣어서 웹에 tree 형태로 출력하시면 됩니다.

typescript는 처음이라 함수를 재귀호출로 못만들었습니다. 람다등 좀더 공부를 많이해서
해봐야 겠습니다.

이만 클스 였습니다.


추가로 하는김에 vue에 붙혀서 웹으로 표시 해봤습니다. 추가적으로 클릭시 이벤트 받기, 아이콘 출력하기 tree customize 하기, drag & drop 하기... 





라벨: , , , , , ,