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는 문서 따라가기 때문이다.