TIL - 22주차 코드

 

1. JS : 230424

1) CSS 애니메이션 이벤트 객체

  • 애니메이션가 동작하다가 멈추면, 각 엘리먼트들이 자리 바뀜.

  • dom.js

// 4-2. 이벤트 객체
{
    let s1 = document.querySelector(".s2");
    let ul = s1.querySelector("ul");
    let current = null; // ul.querySelectorAll("li.current");
    let lis = ul.querySelectorAll("li");

    for(let li of lis)
        li.onclick = function(e){

            if(current){
                
                // *** 실습 3 : 애니메이션 

                let selected = e.target.parentElement;
                while(selected.tagName !== 'LI')
                    selected = e.parentElement;

                selected.classList.add("selected");

                // onanimationend 애니메이션 끝나고 동작하는 것이 end이다.
                selected.onanimationend = function(){
                    selected.classList.remove("selected");
                    let selectedBefore = selected.previousElementSibling;

                    current.replaceWith(selected);
                    if(selectedBefore)
                        selectedBefore.after(current);
                    else
                        ul.prepend(current);
                    
                    current.classList.remove("current");

                    current = null;
                    
                }


                return;
            }




  • dom.html
    <section class="s2">
        <h1>이미지목록</h1>
        <style>
            @keyframes zoomIn {
                from {
                    transform: scale(1,1);
                }
                /* 중간에 중간단계로서 50%의 동작도 구현할 수 있다. */
                30%{
                    transform: rotate3d(1,1,1,90deg);
                }
                60%{
                    transform: rotate3d(2,-1,-1,-0.5turn);
                }
                to {
                    transform: scale(1.2,1.2);
                }
            }
            
            @keyframes selectedani {
                from {
                    transform: scale(1,1);
                }
                /* 중간에 중간단계로서 50%의 동작도 구현할 수 있다. */
                30%{
                    transform: rotate3d(1,1,1,90deg);
                }
                60%{
                    transform: rotate3d(2,-1,-1,-0.5turn);
                }
                to {
                    transform: scale(1.2,1.2);
                }
            }
            .s2 ul{
                padding: 0;
                list-style-type: none;
                display: flex;                
            }            

            .s2 span{
                display: inline-flex;                
                width:100px;
                height: 100px;
                align-items: center;
                justify-content: center;
                color: white;
                font-weight: bold;
                background-color: blue;

                /* 시간 초 딜레이 넣기 */
                /* transition: 0.5s; */
            }

            /* 여기서 li.current가 되는구나 JS를 굳이 안써도 된다. */
            .s2 li.current .box,
            .s2 li.current span
            {
                box-sizing: border-box;
                background-color: red;
                border:3px solid yellow;
                /* 박스를 사이즈를 키울 때, 전체 사이즈를 안 밀리고 싶으면 tranform을 이용한다! */
                /* 다른 것에 영향을 주지 않고 사이즈를 늘릴 수 있다. 그래서 tranform을 사용함. */
                /* transform: scale(1.2,1.2); */

                /* 애니메이션 이용하기! */
                animation-name: zoomIn;
                animation-duration: .5s;     /* 반복 시간 */
                animation-iteration-count: infinite;    /* 반복 횟수 */
                animation-direction: alternate;
            }      

            .s2 li.selected .box,
            .s2 li.selected span
            {
                /* 애니메이션 이용하기! */
                animation-name: selectedani;
                animation-duration: 2s;     /* 반복 시간 */
                animation-direction: alternate;
            }        
        </style>
        <ul>
            <!-- 기존보다 구조가 달라지면 문제가 생김. -->
            <li><span>1</span></li>
            <li>
                <div>
                    <span>2</span>
                </div>
            </li>
            <li><span>3</span></li>
            <li><span>4</span></li>
        </ul>
    </section>
    <hr>




2) 목록을 키보드로 관리 (DOM)

  • 키보드를 이용해서 엘리먼트들을 관리하고 삭제할 수 있다.

  • dom.js


// .s3 목록을 키보드로 관리!!
{
    let s = document.querySelector(".s3");
    let ul = s.querySelector("ul");
    let current = null;
    let lis = ul.querySelectorAll("li");
    let delButton = s.querySelector(".btn-del");
    s.focus();

    s.onkeydown = function(e){
        console.log("key down");
        console.log(`code:${e.code}, key:${e.key}, keyCode:${e.keyCode}`);
        
        // if(e.code === 'Delete'){

        //     let selectedButtons = [...ul.querySelectorAll("input:checked")]
        //                         .map(a=>a.parentElement);

        //     for(let li of selectedButtons)
        //         li.remove();
        // }
        if(e.code === 'Delete'){
            // Trigger 함수 : 이벤트 !!
            // click 누르는 이벤트에서 가능
            const event = new Event("click");
            delButton.dispatchEvent(event); // 이게 Trigger이다. 이벤트에 방아쇄를 당김.
        }
    }

    delButton.onclick = function(){
        // let selectedButtons = e.target.value;
        // console.log(selectedButtons);
        // let selectedButtons = ul.querySelectorAll(".selected");
        // let selectedButtons = ul.querySelectorAll("input:checked");

        // *** JS Spread Operator(연산자) 문법 ***
        // 개별 요소로 분배 가능!!
        let selectedButtons = [...ul.querySelectorAll("input:checked")]
                                .map(a=>a.parentElement);
        

        // let arr = [1,2,3];

        // ** For Each : for each는 하나씩 꺼내서 사용한다.
        // ** Map : 하나씩 꺼내서 다른 형태로 바꿔줄 때!! map을 이용한다.
        // arr.forEach(a => console.log(a+1));

        // let arr1 = arr.map(a=>`<a id=${a}>`);
        // console.log(arr1);

        for(let li of selectedButtons)
            li.remove();

        
    }

    ul.onclick = function(e){
        // console.log(`clicked: ${e.target}`);
        // console.log(`clicked: ${e.target}`);
        if(e.target.tagName !== 'SPAN')
            return;

        let selected = e.target.parentElement;

        while(selected.tagName !== 'LI'){
            selected = selected.parentElement;
            // 'e.parentElement'가 아니라 'selected.parentElement'이다.
        }

        selected.classList.add("selected");
    }
}




  • dom.html
    <section tabindex="0" class="s3">
        <h1>이미지목록</h1>
        <style>
            .s3 ul{
                padding: 0;
                list-style-type: none;
                display: flex;                
            }            

            .s3 span{
                display: inline-flex;                
                width:100px;
                height: 100px;
                align-items: center;
                justify-content: center;
                color: white;
                font-weight: bold;
                background-color: blue;

                /* 시간 초 딜레이 넣기 */
                /* transition: 0.5s; */
            }

            /* 여기서 li.current가 되는구나 JS를 굳이 안써도 된다. */
            .s3 li.current .box,
            .s3 li.current span
            {
                box-sizing: border-box;
                background-color: red;
                border:3px solid yellow;
                /* 박스를 사이즈를 키울 때, 전체 사이즈를 안 밀리고 싶으면 tranform을 이용한다! */
                /* 다른 것에 영향을 주지 않고 사이즈를 늘릴 수 있다. 그래서 tranform을 사용함. */
                /* transform: scale(1.2,1.2); */

                /* 애니메이션 이용하기! */
                animation-name: zoomIn;
                animation-duration: .5s;     /* 반복 시간 */
                animation-iteration-count: infinite;    /* 반복 횟수 */
                animation-direction: alternate;
            }      

            .s3 li.selected .box,
            .s3 li.selected span
            {
                /* 애니메이션 이용하기! */
                animation-name: selectedani;
                animation-duration: 2s;     /* 반복 시간 */
                animation-direction: alternate;
            }        
        </style>
        <div>
            <button class="btn-del">삭제</button>
        </div>
        <ul>
            <!-- 기존보다 구조가 달라지면 문제가 생김. -->
            <li><input type="checkbox"></input><span>1</span></li>
            <li><input type="checkbox"></input>
                <div>
                    <span>2</span>
                </div>
            </li>
            <li><input type="checkbox"></input><span>3</span></li>
            <li><input type="checkbox"></input><span>4</span></li>
        </ul>
    </section>
    <hr>



3) 파일 드래그앤드랍 DOM

  • dom.js
window.addEventListener("load",function(){

// .s4 and drop ai
{
    let s = this.document.querySelector(".s4");
    let dropZone = s.querySelector(".drop-zone");

    dropZone.ondragenter = function(e){
        console.log("enter");


    }
    dropZone.ondragover = function(e){
        e.preventDefault();

        // 드래그한게 '파일'이면 true 아니면 false
        if(e.dataTransfer.types[0]=="Files"){
            dropZone.classList.add("valid");
            dropZone.classList.remove("invalid");
        }
        else{
            dropZone.classList.add("invalid");
            dropZone.classList.remove("valid");
        }   

        // ** Over에서는 초당 이벤트가 발생해서 파일 정보를 가져올 수 없다!!
        // 그래서, Drop으로 이벤트 옮기기!!
        // let file = e.dataTransfer.files  ;
        // if(file.length > 1){
        //     console.log("하나 이상의 파일은 업로드할 수 없습니다.");
        // }

        console.log("over");

        if(e.dataTransfer.files.length > 1){
            console.log("하나 이상의 파일은 업로드할 수 없습니다.");
        }
    }
    dropZone.ondragleave = function(e){
        console.log("leave");
    }
    dropZone.ondrop = function(e){
        // 브라우저가 기본적으로 파일을 읽기 때문에 기본 속성 날리기!
        // over에도 drop처럼 preventDefault로 같이 날려주기!!

        e.preventDefault(); 
        console.log(e.dataTransfer.type);
        // console.log("drop");

        if(e.dataTransfer.files.length > 1){
            console.log("하나 이상의 파일은 업로드할 수 없습니다.");
        }
    }
    
}



  • dom.html
    <!-- 드래그 드랍은 파일 자체인 컨텐트를 끌어다가 파일을 옮겨야 한다. -->
    <section class="s4">
        <style>
            .s4 .drop-zone{
                box-sizing: border-box;
                width: 500px;
                height: 400px;
                border: 1px solid gray;
                background-color: beige;
            }
            .s4 .drop-zone.valid{
                background-color: greenyellow;
            }

            .s4 .drop-zone.invalid{
                background-color: red;
            }

            .s4 .box-zone{
                display: flex;
            }
            .s4 .box{
                box-sizing: border-box;
                width: 100px;
                height: 100px;
                background-color: green;
            }
        </style>
        <h1>Drag and Drop API</h1>
        <div class="drop-zone">
        </div>
        <div class="box-zone">
            <div draggable="true" class="box">1</div>
            <div class="box">2</div>
            <div class="box">3</div>
            <div class="box">4</div>
        </div>
    </section>



2. 230425

1) 파일 업로드

  • dom.js
window.addEventListener("load",function(){

// .s4 and drop ai
{
    let s = this.document.querySelector(".s4");
    let dropZone = s.querySelector(".drop-zone");

    dropZone.ondragenter = function(e){
        console.log("enter");


    }
    dropZone.ondragover = function(e){
        e.preventDefault();

        // 드래그한게 '파일'이면 true 아니면 false
        if(e.dataTransfer.types[0]=="Files"){
            dropZone.classList.add("valid");
            dropZone.classList.remove("invalid");
        }
        else{
            dropZone.classList.add("invalid");
            dropZone.classList.remove("valid");
        }   

        // ** Over에서는 초당 이벤트가 발생해서 파일 정보를 가져올 수 없다!!
        // 그래서, Drop으로 이벤트 옮기기!!
        // let file = e.dataTransfer.files  ;
        // if(file.length > 1){
        //     console.log("하나 이상의 파일은 업로드할 수 없습니다.");
        // }

        console.log("over");

        if(e.dataTransfer.files.length > 1){
            console.log("하나 이상의 파일은 업로드할 수 없습니다.");
        }
    }
    dropZone.ondragleave = function(e){
        console.log("leave");
    }
    dropZone.ondrop = function(e){
        // 브라우저가 기본적으로 파일을 읽기 때문에 기본 속성 날리기!
        // over에도 drop처럼 preventDefault로 같이 날려주기!!

        e.preventDefault(); 
        // console.log(e.dataTransfer.type);
        // console.log("drop");

        
        // if(e.dataTransfer.files.length > 1){
            //     console.log("하나 이상의 파일은 업로드할 수 없습니다.");
            // }
            
        let file = e.dataTransfer.files[0];
        console.log(file);
        let formData = new FormData();

        formData.append("file",file);
        formData.append("test","hehe");

        let request = new XMLHttpRequest();
        request.onload = function(){
            console.log("done");   
        }

        request.onprogress = function(){
            console.log("progress");   
        }

        request.open("POST","http://localhost:8080/upload");
        // request.setRequestHeader("Content-Type", "multipart/form-data");

        request.send(formData);
    }
    
}


  • HomeController.java
package com.rland.web.controller;

import java.io.File;
import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.HttpServletRequest;

// @CrossOrigin(origins = "http://127.0.0.1:5500")
@RestController
@RequestMapping("/")
public class HomeController {
    
    //@ResponseBody
    @GetMapping
    public String index(){
        return "hello ideas";
    }
    //1
    @PostMapping("upload")
    public ResponseEntity<String> upload(MultipartFile file, HttpServletRequest request) throws IllegalStateException, IOException{

			
        // 업로드 할 파일이 없다면 continue
        // 만약 break를 한다면 첫번째 파일이 없고 두 번째만 있을경우 문제가 생긴다
        if(file.isEmpty())
            return new ResponseEntity<String>("파일을 전송하지 않습니다.", HttpStatus.BAD_REQUEST);
        
        // urlPath: 홈디렉토리의 경로
        String urlPath = "/upload/"; // 서비스할때 프로그램이 어느 위치에 있을지 모르기때문에..

        // realPath: 시스템의 실제 경로
        String realPath = request.getServletContext().getRealPath(urlPath); // 서비스할때 프로그램이 어느 위치에 있을지 모르기때문에..

        System.out.println(realPath);

        File filePath = new File(realPath);

        if(!filePath.exists())
            filePath.mkdirs();

        String fileName = file.getOriginalFilename();
        File saveFile = new File(filePath+File.separator+fileName); // File.separator는 OS별 구분자( ex) "/" ) 표시!

        System.out.println(filePath+File.separator+fileName);

        file.transferTo(saveFile);  
        
        System.out.println(realPath);

        // Post 방식에서 "OK" 대신에 ResponseEntity<String>로 OK 상태값으로 리턴값을 보내줄 수 있다.
        return new ResponseEntity<String>("몰라데이터",HttpStatus.OK);
    }
}


  • CorsConfig.java
package com.rland.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig {
   @Bean
   public WebMvcConfigurer corsConfigurer() {
      return new WebMvcConfigurer() {
         @Override
         public void addCorsMappings(CorsRegistry registry) {
            registry
            .addMapping("/**").allowedOrigins("http://127.0.0.1:5500");
         }
      };
   }
}



3. VueJS Composition API : 230426

  • 외부 라이브러리 추가 중요!
import { onMounted, reactive, ref } from 'vue';
import { computed } from '@vue/reactivity';

// Composition API는 템플릿 분리 시, import도 편하다. 모델에 components에 안 담아도 된다.
import Header from "./components/Header.vue";


0) 과거 Options API의 변수 바인딩

// Reactivity : 2-Way 방식이다. 2-Way은 값이 바뀌면 값이 같이 바뀐다.
// 옵션s API에서는 기본 옵션이다.
data(){
    return{
        a:10
    }
},



1) ref() : 변수 양방향 바인딩

  • ref() 함수가 양방향 바인딩을 가능하게 해준다.
  • ref() 함수는 값이 1개 사용될 때 사용되며, 그냥 ref() 함수 1개만 쓰면 된다.
// 1. ref()
let b = ref(30);



2) reactive : 객체 양방향 바인딩

  • ref({})는 Object에서 이렇게 하면 되지만 이상하다.
  • 객체를 반응형으로 하려면, Reactive를 이용해서 객체의 반응형 프락시(proxy)를 반환합니다.

// 2. reactive
// 1) 변수 양방향 바인딩
let menu = reactive({
    id:1,
    name:"아메리카노",
    price:3000
});


// let list = reactive(new Map([['list']]));

// 2) 객체 양방향 바인딩
// ** reactive 그 자체를 바꾸면 안되고 그 속성을 바꿀 수 있어야 한다.
let model = reactive({
    newList:[],
    list:[]
});




3) Life Cycle(Composition)

  • mounted가 아니라 onMounted로 구성

  • 내부 함수를 더이상 this를 사용하지 않음.


// ------------- 3. Life Cycle -----------------------
onMounted(() => {
    console.log("mounted11");
    console.log(`b:${b.value}`);
    console.log(`menu.price:${menu.price}`);
    load();
    console.log(model.list);
});




4) Event Handlers(Composition)

  • 이제, VueJS의 Composition API에서 Fetch API를 async와 await로 구성한다.

  • 데이터를 담는 그릇인 model은 reactive로 감싸서 사용하자!


// ** Fetch API의 새로운 방법!!
async function load(){
    
    // 1) 원래 Fetch API를 이렇게 사용했었다.
    // fetch("")
    // .then()
    // .then()
    // .then()
    // .then()

    // 2) 이제는 비동기 처리로 진행할 수 있다. 
    // 또한, 변수 선언은 위에서 사용할 수 있다.
    let response = await fetch("http://192.168.0.33:8080/menus");
    let json = await response.json();
    model.list = json.list;
    console.log(json);
    console.log(model.list);
}

// function을 애로우 펑션으로 사용? : 이유에 맞게 사용해라!!
// 짧은 것에도 이유가 있어야 한다.   

// 1) 함수 모양
function clickHandler(){
    console.log("cliCk");
    load();
}

// 2) 람다 표현식 : 코드를 다른 쪽으로 '수식'을 쉽게 전달하기 위해서 사용!!
// 그래서, 보통 수식을 많이 다루는 빅데이터에서 람다 표현식이 사용된다.
// 그래서 'const clickHandler = ;' 에서 나머지 코드 부분이 수식이여야 한다.
const clickHandler = () => {
    console.log("cliCk");
}




5) computed 함수 사용법

a. 과거 방식의 computed 방식

// Options API에서 computed를 이용하여 계산하는 방법
computed:{
    total(){
        return this.a+2;
    }
}


b. Composition API 방식의 computed 방식

// *** 이것도 라이브러리 import 시켜야 한다.
// 첫 번째 방법 :
let total1 = computed(()=>b.value+3);

// 두 번째 방법 : JS map 이용
let total2 = computed(() => model.list.map((m) => m.price).reduce((p,c)=>p+c,0));

// 세 번째 방법 :

let total = computed(()=>{
    let result = 0;
    for(let m of model.list)
        result += m.price;

    return result;
});




6) 실습 코드의 html

  • 계산을 위해서 modifier을 이용하자 : a.number(문자열을 숫자로 변경!!)
<template>
    <Header/>
    <div>
        <ul>
            <li v-for="m in model.list">
                <span></span>
            </li>
        </ul>
    </div>
    <div>
        <!-- total price : <span>\{\{ total \}\}</span> -->
        total price : <span>\{\{ total2 \}\}</span>
    </div>
    <!-- <div>
        hello  
    </div> -->
    <div>
        <!-- 계산을 위해서 modifier을 이용하자 : a.number(문자열을 숫자로 변경!!) -->
        a:<span v-text="a"></span><input v-model.number="a"/><br>
        b:<span v-text="b"></span><input v-model.number="b"/><br>
        price:<span v-text="menu.price"></span><input v-model="menu.price"/><br>
        <button @click="clickHandler">클릭</button>
    </div>
</template>



7) 실습 전체 코드 정리

<script>

// 옵션s API는 객체들의 중첩으로 이루어져 있다.
// export default들에 따라 객체를 반환해준다.
// 무조건 객체들의 중첩이 발생한다.
export default{
    
    // Reactivity : 2-Way 방식이다. 2-Way은 값이 바뀌면 값이 같이 바뀐다.
    // 옵션s API에서는 기본 옵션이다.
    data(){
        return{
            a:10
        }
    },

    // Options API에서 computed를 이용하여 계산하는 방법
    // computed:{
    //     total(){
    //         return this.a+2;
    //     }
    // }
}

</script>

<!-- 옵션s API와 Composition API들이 있기 때문에 
 가지 블럭을 한번에 같이 사용할  있다. -->
<script setup>

    // import도 자동으로 추가해주자!
    import { onMounted, reactive, ref } from 'vue';
    import { computed } from '@vue/reactivity';

    // Composition API는 템플릿 분리 시, import도 편하다. 모델에 components에 안 담아도 된다.
    import Header from "./components/Header.vue";

    // 1. ref()
    // ref() 함수가 양방향 바인딩을 가능하게 해준다.
    // ref() 함수는 값이 1개 사용될 때 사용되며, 그냥 ref() 함수 1개만 쓰면 된다.
    let b = ref(30);

    // 2. reactive
    // ref({})는 Object에서 이렇게 하면 되지만 이상하다. 
    // 객체를 반응형으로 하려면, Reactive를 이용해서 객체의 반응형 프락시(proxy)를 반환합니다.
    let menu = reactive({
        id:1,
        name:"아메리카노",
        price:3000
    });


    // let list = reactive(new Map([['list']]));

    // ** reactive 그자체를 바꾸면 안되고 그 속성을 바꿀 수 있어야 한다.
    let model = reactive({
        newList:[],
        list:[]
    });

    // 5. computed
    // *** 이것도 import 시켜야 한다.
    // 첫 번째 방법 :
    // let total1 = computed(()=>b.value+3);

    // 두 번째 방법 : JS map 이용
    let total2 = computed(() => model.list.map((m) => m.price).reduce((p,c)=>p+c,0));

    // // 세 번째 방법 :
    
    // let total = computed(()=>{
    //     let result = 0;
    //     for(let m of model.list)
    //         result += m.price;

    //     return result;
    // });

    // ------------- 3. Life Cycle -----------------------
    onMounted(() => {
        console.log("mounted11");
        console.log(`b:${b.value}`);
        console.log(`menu.price:${menu.price}`);
        load();
        console.log(model.list);
    });

    // ------------- 4. Event Handlers  ----------------------
    
    // ** Fetch API의 새로운 방법!!
    async function load(){
        
        // 1) 원래 Fetch API를 이렇게 사용했었다.
        // fetch("")
        // .then()
        // .then()
        // .then()
        // .then()

        // 2) 이제는 비동기 처리로 진행할 수 있다. 
        // 또한, 변수 선언은 위에서 사용할 수 있다.
        let response = await fetch("http://192.168.0.33:8080/menus");
        let json = await response.json();
        model.list = json.list;
        console.log(json);
        console.log(model.list);
    }

    // function을 애로우 펑션으로 사용? : 이유에 맞게 사용해라!!
    // 짧은 것에도 이유가 있어야 한다.   

    // 1) 함수 모양
    function clickHandler(){
        console.log("cliCk");
        load();
    }

    // 2) 람다 표현식 : 코드를 다른 쪽으로 '수식'을 쉽게 전달하기 위해서 사용!!
    // 그래서, 보통 수식을 많이 다루는 빅데이터에서 람다 표현식이 사용된다.
    // 그래서 'const clickHandler = ;' 에서 나머지 코드 부분이 수식이여야 한다.
    // const clickHandler = () => {
    //     console.log("cliCk");
    // }
</script>


<template>
    <Header/>
    <div>
        <ul>
            <li v-for="m in model.list">
                <span>\{\{ m.name \}\}</span>
            </li>
        </ul>
    </div>
    <div>
        <!-- total price : <span>\{\{ total \}\}</span> -->
        total price : <span>\{\{ total2 \}\}</span>
    </div>
    <!-- <div>
        hello \{\{ a \}\} \{\{ b \}\}\{\{ c \}\}
    </div> -->
    <div>
        <!-- 계산을 위해서 modifier을 이용하자 : a.number(문자열을 숫자로 변경!!) -->
        a:<span v-text="a"></span><input v-model.number="a"/><br>
        b:<span v-text="b"></span><input v-model.number="b"/><br>
        price:<span v-text="menu.price"></span><input v-model="menu.price"/><br>
        <button @click="clickHandler">클릭</button>
    </div>
</template>

<style>

</style>



4. VueJs Composition : 230427

1) Watch

  • watch는 해야할 것들을 알고 있다. 즉, 트리거와 같은 역할이다.

  • watch : 입력 값이 바뀔 때, 행위를 취해야할 때, watch를 사용한다!

<!-- 옵션s API와 Composition API들이 있기 때문에 
 가지 블럭을 한번에 같이 사용할  있다. -->
<script setup>

    // import도 자동으로 추가해주자!
    import { onMounted, reactive, ref, watch } from 'vue';
    import { computed } from '@vue/reactivity';

    // Composition API는 템플릿 분리 시, import도 편하다. 모델에 components에 안 담아도 된다.
    import Header from "./components/Header.vue";
    
    // ** reactive 그자체를 바꾸면 안되고 그 속성을 바꿀 수 있어야 한다.
    let model = reactive({
        newList:[],
        list:[]
    });


    // [2] watch 함수 사용
    // 입력값이 바뀔 때, 행위를 취해야할 때, watch를 사용한다! 
    watch(model,()=>{
    console.log("호호호");
    });

    let query = ref("");
    
    watch(query,()=>{
        console.log(query.value);

        if(query.value !== '')
            model.list = model.list.filter(m=>m.name.includes(query.value));
        else
            model.list = model.list.filter(m=>m.name.includes(''));
    });




2) shallowRef, triggerRef

a. shallowRef

  • 값이 바뀌면, 바뀐대로 동작한다.

  • shallowRef는 일단, 값을 초기화 해준다.

  • 그 이후에 값을 바꾸고 출력했느데 이것은 Reactive 방식이 아니라 active 방식이다.

  • 초기화되고 값이 덮여졌고

  • 입력 값을 바꾸고 나서 화면에서도 바로 다시 바뀌어야 한다.

  • 모델을 바꾸고 다시 바뀐 모델에서 화면에서도 바뀐다.


    let aa = shallowRef({name:'okay'});
    // aa.value.name = "good";
    aa.name = "good";
    
    function inputHandler(){

    // trigger만 호출하면, 값이 리액티브적으로 값이 바뀐다.
    // 매번 적용되지는 않지만, 어느 순간 한번 리액티브로 동작시키고 싶으면, 
    // triggerRef를 이용!!! 피드백 받으면서 만든다?
    triggerRef(aa);
    }




b. triggerRef

  • trigger만 호출하면, 값이 리액티브적으로 값이 바뀐다.

  • 매번 적용되지는 않지만, 어느 순간 한번 리액티브로 동작시키고 싶으면, triggerRef를 이용!!! 피드백 받으면서 만든다?

function inputHandler(){

	// trigger만 호출하면, 값이 리액티브적으로 값이 바뀐다.
	// 매번 적용되지는 않지만, 어느 순간 한번 리액티브로 동작시키고 싶으면, 
	// triggerRef를 이용!!! 피드백 받으면서 만든다?
	triggerRef(aa);
}



c. 전체 코드


let aa = shallowRef({name:'okay'});
// aa.value.name = "good";
aa.name = "good";

function inputHandler(){

	// trigger만 호출하면, 값이 리액티브적으로 값이 바뀐다.
	// 매번 적용되지는 않지만, 어느 순간 한번 리액티브로 동작시키고 싶으면, 
	// triggerRef를 이용!!! 피드백 받으면서 만든다?
	triggerRef(aa);
}


<template>
    <div>
        name : <span></span><input type="text" v-model="aa.name" @input="inputHandler">
    </div>
</template>




3) Props : 컴포넌트와 상호작용하는 방법

  • 부모로부터 자식에게 데이터 넘겨주는 방법

  • 부모에서는 데이터 바인딩해서 넘겨준다!

  • 스스로서 데이터를 가지고 올 수 없는 상황에서 사용하게 된다.**

<script setup>
	import NewList from "./components/NewMenus.vue";
    
    let model = reactive({
        newList:[],
        list:[]
    });
</script>

<template>
    <NewList :data="model.newList"/>
</template>




  • 자식에서는 모델로서 바인딩된 데이터를 받는다.
<!-- 과거 방식의 부모로부터 자식에게 데이터 넘겨주는 방법!! 
    <script>
    export default(){
        props:['data']
    }
</script> -->

<!-- 현재 방식의 부모로부터 자식에게 데이터 넘겨주는 방법!!  -->
<!-- 컴포넌트와 상호작용하는 방법!! -->
<script setup>
import { reactive } from 'vue';
// import { reactive } from 'vue';

    let props = defineProps({
        list:[]
    });
</script>
<template>
    <section>
        <h1>new list</h1>
        <ul>
            <li v-for="m in props.list"></li>
        </ul>
    </section>
</template>

<style scoped>
    section{
        border: 1px solid gray;
        padding: 20px;
    }
</style>



4) Props 응용

  • 현재 방식의 부모로부터 자식에게 데이터 넘겨주는 방법!!
  • 컴포넌트와 상호작용하는 방법!!
<template>
    <div>
        <!-- total price : <span></span> -->
        total price : <span></span><br>
        name : <span></span><input type="text" v-model="aa.name" @input="inputHandler">
    </div>
    <NewList :list="model.newList" title="추천메뉴" :name="aa.name" />
</template>


<!-- 현재 방식의 부모로부터 자식에게 데이터 넘겨주는 방법!!  -->
<!-- 컴포넌트와 상호작용하는 방법!! -->
<script setup>
import { reactive } from 'vue';
// import { reactive } from 'vue';

    let props = defineProps({
        list:[],
        title:"",
        name:{}
    });
</script>

<template>
    <section>
        <h1> </h1>
        <ul>
            <li v-for="m in props.list"></li><br>
        </ul>
    </section>
</template>




5. Vue.js : 230428

0) CSS 설계 방법 :

a. CSS 설계 방법

  • 보통 css 코드는 .vue 파일의 style scoped에 넣기
    • CSS 코드도 Vue 엔진의 변환기를 거쳐서 파일 용량이 줄어든다.


b. SVG 파일 위치

  • 비트맵 코드로 되어있는 파일: public 폴더에 넣기

  • .svg 파일 : .vue 파일의 style scoped에 넣기



c. CSS 구조화

a) icon :

b) button :

c) decorate :

d) utils.: 예를 들면, 버튼 크기대로 코드 블럭이 생긴다. 가장 마지막에 컨트롤 되며 !important 이용

e) 목록 등등

  • 위의 순서대로 CSS 코드를 구조화한다.**



1) Vue로 모달 띄우기 기능

  • 데이터 값을 바인딩하지 않고 속성으로 넣지도 않고 바로 꽂을 때, v-slot 이용한다.

  • 모달의 범위를 넓힐 수도 있다. DB의 목록도 받아서 범위로 포함시킬 수 있다.



a. App.vue

  • script
<script>

    let showModal = ref(false);

    function showHandler(){
        // showModal.value=!showModal.value;
        showModal.value=true;
    }

</script>


  • template
<template>
    <div>
        <button @click="showHandler">show</button>
    </div>
    
    <!-- 데이터 값을 바인딩하지 않고 속성으로 넣지도 않고 바로 꽂을 때, v-slot 이용한다. -->
    <!-- 모달의 범위를 넓힐 수도 있다. DB의 목록도 받아서 범위로 포함시킬 수 있다. -->
    <Modal title="공지사항" :show="showModal" @ok="dlgOkHandler">
        <div>
            삭제되었습니다. 
        </div>
    
    </Modal>

</template>



b. Modal.vue

  • script
<script setup>

    let props = defineProps({
        title:"",
        // show:"",
        show:false
    });

</script>


  • template
<template>
    <!-- 첫번째 방법 d-none 이벤트 -->
    <!-- <div class="screen" :class="{'d-none':props.show===false}"> -->
    
    <!-- 두번째 방법 d-none 이벤트 -->
    <div class="screen" :class="{'d-none':!props.show}">

     <section>
            <h1 ></h1>
            
            <div class="content">
                <slot></slot>
            </div>
            
            <div class="commands">
                <button>OK</button>
                <button>Cancel</button>
            </div>
        </section>  
    </div>
</template>


  • style
<style scoped>
    /* 유틸로 만든 것이다. 유틸은 모든 스타일을 모두 엎을 수 있어야 해서 !important 이용 */
    .d-none{
        display: none !important;
    }

    /* screen이 뒷 배경을 만들어준다. */
    .screen{
      
        /* opacity는 자식에게도 영향을 줘버려서 
        rgba를 사용해야한다. */
        /* opacity:0.8; */
        /* background-color: black; */
        background-color: rgba(0, 0, 0, 0.8);

        position: fixed;

        left:0;
        top:0;
        
        /* width:100vw는 화면 비율로 따라간다. 
        100%는 부모 따라간다. */
        width:100vw;
        height:100vh;

        /* // 모달은 화면 따라가야한다. 그래서 fixed를 이용한다.
        // absolute는 문서 따라가기 때문이다. */
        
        display:flex;
        justify-content: center;
        align-items: center;

    }

    /* section이 모달 부분이다. 그래서 여기서 애니메이션을 준다. */
    section{   
        background-color: white;
        display:inline-block;
        border-radius: .7em;
    }
    section>h1{
        font-size: 14px;
        padding: 0px 10px;
    }
    section>.content{
        border-top:1px solid red;
        border-bottom: 1px solid brown;
        padding:10px 20px;
    }
    section>.commands{
        padding: 10px 10px;

        display: flex;
        justify-content: center;
    }
</style>



2) 모달에서 $emit 사용법

a. App.vue

  • @ok라는 속성을 제공해준다.


  • script
<script>
    let showModal = ref(false);

    function showHandler(){
        // showModal.value=!showModal.value;
        showModal.value=true;
    }
    function dlgOkHandler(e){
        console.log("오케이한거지?");
        console.log(e);
        showModal.value=false;
    }
</script>


  • template
<template>
    <div>
        <button @click="showHandler">show</button>
    </div>
    
    <!-- 데이터 값을 바인딩하지 않고 속성으로 넣지도 않고 바로 꽂을 , v-slot 이용한다. -->
    <!-- 모달의 범위를 넓힐 수도 있다. DB의 목록도 포함시킬  있다. -->
    <Modal title="공지사항" :show="showModal" @ok="dlgOkHandler">
        <div>
            삭제되었습니다. 
        </div>
    
    </Modal>
</template>



b. Modal.vue

  • defineProps는 readOnly 변수라서 값을 변경할 수 없다.**

  • 따라서, $emit을 이용하여 부모에서 자식의 값을 연동하여 사용한다.

  • $emit에 의해서 부모와 자식 관계에서 자식이 자기를 포함한 영역에서 ‘ok’를 포함한 함수를 호출하게 해준다!
    • <button @click="$emit('ok',$event)">OK</button>
  • $event는 부모에서 발생한 이벤트 함수에서 받아온 이벤트 값을 받아와서 출력한다.


  • script

<script setup>

    let props = defineProps({
        title:"",
        // show:"",
        show:false
    });

    // ******** $emit ***********
    // ** defineProps는 readOnly 변수라서 값을 변경할 수 없다.
    // 따라서, $emit을 이용하여 부모에서 자식의 값을 연동하여 사용한다.
    // function okHandler(){

    // }
</script>




  • template
<template>
    <!-- 첫번째 방법 d-none 이벤트 -->
    <!-- <div class="screen" :class="{'d-none':props.show===false}"> -->
    
    <!-- 두번째 방법 d-none 이벤트 -->
    <div class="screen" :class="{'d-none':!props.show}">

     <section>
            <h1 ></h1>
            <div class="content">
                <slot></slot>
            </div>
            <div class="commands">

                <!-- 부모와 자식 관계에서 자식이 자기를 포함한 영역에서 
                    ok를 포함한 함수를 호출하게 해준다! -->
                <!-- $event는 부모에서 받아온 ok 함수의 이벤트 값을 받아온다.  -->
                <button @click="$emit('ok',$event)">OK</button>
                <button>Cancel</button>
            </div>
        </section>  
    </div>
</template>



3) CSS 애니메이션 정리

  • Vue에서 모달이 출력되는 모습을 css의 transform을 이용하려고 했지만 버그가 발생하여 대신 @keyframes을 이용하여 애니메이션 구현하기!

  • script

<script setup>
    let props = defineProps({
        title:"",
        // show:"",
        show:false
    });
</script>


  • template
<template>
    <div class="screen" :class="{'d-none':!props.show}">

    <!-- /* section이 모달 부분이다. 그래서 여기서 애니메이션을 준다. */ -->
     <section :class="{'show-effect':show}">
            <h1 ></h1>
            <div class="content">
                <slot></slot>
            </div>
            <div class="commands">
                <button @click="$emit('ok',$event)">OK</button>
                <button>Cancel</button>
            </div>
        </section>  
    </div>
</template>


  • style

<style scoped>
    @keyframes show-effect {
        from{
            transform: translateY(-200px);
        }
        to{
            transform: translateY(0px);
        }
    }

    @keyframes fadein {
        from {
            opacity: 0;
        }
        to {
            opacity: 1;
        }
    }
    /* 유틸로 만든 것이다. 유틸은 모든 스타일을 모두 엎을 수 있어야 해서 !important 이용 */
    .d-none{
        display: none !important;
    }

    /* screen이 뒷 배경을 만들어준다. */
    .screen{
       
        
        /* opacity는 자식에게도 영향을 줘버려서 
        rgba를 사용해야한다. */
        /* opacity:0.8; */
        /* background-color: black; */
        background-color: rgba(0, 0, 0, 0.8);

        position: fixed;

        left:0;
        top:0;
        
        /* width:100vw는 화면 비율로 따라간다. 
        100%는 부모 따라간다. */
        width:100vw;
        height:100vh;

        /* // 모달은 화면 따라가야한다. 그래서 fixed를 이용한다.
        // absolute는 문서 따라가기 때문이다. */
        
        display:flex;
        justify-content: center;
        align-items: center;

    }

    /* section이 모달 부분이다. 그래서 여기서 애니메이션을 준다. */
    section{   
        background-color: white;
        display:inline-block;
        border-radius: .7em;

        /* 모달이 위에서 내려오는 CSS 이동 처리 */
        /* 하지만, 버그가 생겨서 @keyframes 애니메이션 이용하기! */
        /* transform: translateY(-200px);
        translate: 2s; */
    }

    section.show-effect{
        /* transform: translateY(0px); */
        /* animation: show-effect 0.5s ease-in-out; */
        animation-fill-mode: forwards;  /* forwards는 keyframes 가다가 멈추길 원하는 곳에서 멈춘다. */
        /* animation-fill-mode: backwards;  backwards는 다시 되돌아 간다. */   
        animation: fadein 0.5s;
    }   

    section>h1{
        font-size: 14px;
        padding: 0px 10px;
    }
    section>.content{
        border-top:1px solid red;
        border-bottom: 1px solid brown;
        padding:10px 20px;
    }
    section>.commands{
        padding: 10px 10px;

        display: flex;
        justify-content: center;
    }
</style>



4) css 정리

  • css utils는 모든 스타일을 모두 엎을 수 있어야 해서 !important를 이용한다. 가장 마지막에 처리한다.


  • css opacity 속성은 자식에게도 영향을 줘버려서 rgba를 사용해야한다. */


  • width:100vw는 화면에 보이는 전체 비율로 따라간다. width:100%는 부모 컴포넌트 크기에 따라 출력된다. */
width:100vw;
height:100vh;


  • 모달은 화면 따라가야한다. 그래서 css의 position울 fixed를 이용한다. 왜냐하면 position의 absolute는 문서 따라가기 때문이다.



5) Vue.js 정리

a. watch vs computed

b. v-slot 개념