티스토리 뷰
부트캠프 1주차, 미니 팀프로젝트로 팀 소개 웹페이지 만들기가 발제되었다. 우리 팀은 다이어리를 컨셉으로 소개 페이지를 만들기로 하였고, 나는 '좋아요'기능 구현을 맡게 되었다.
내가 아이디어를 내서 내가 맡는다고 하긴 했지만, 막막하다! 일단 좋아요 기능을 다른 사람들은 어떻게 만들었는지 쭉 보면서 흐름을 파악하고, 좋아요 기능에는 어떤 효과가 들어가야 할지 생각하는 것을 출발점으로 삼았다.
좋아요 기능에는 어떤 효과가 들어가야 할까?
- 좋아요 버튼을 누르면 빈 하트가 꽉 찬 하트로 바뀌어야 한다.
- 좋아요 버튼을 누르면 좋아요의 숫자가 하나 올라가야 한다.
- 좋아요 버튼을 두 번 클릭하면 꽉 찬 하트가 다시 빈 하트로 돌아가야 한다.
- 좋아요 버튼을 두 번 클릭하면 좋아요의 숫자가 하나 줄어들어야 한다.
>하지만 이 기능을 구현하려면 개별 사용자의 정보가 저장되어야 한다. 즉, 로그인 기능이 필요하다. 그러나 우리 팀은 아직 로그인 기능을 구현하겠다는 결정을 내리진 않아서 팀원들과 의논한 결과, 로그인 기능은 넣지 않게 되었다. 따라서 좋아요 기능은 버튼을 누르면 좋아요의 숫자가 하나 올라가는 기능까지만 구현하기로 했다. 좋아요 버튼도 한번 누르면 색이 바뀌고, 다시 한번 누르면 다시 원래대로 돌아가면 좋을 것 같다. 이래저래 많이 찾아보았으나 어려운 게 너무 많아 일단 쉬운 것 부터 하나씩 해내기로 했다.
버튼 역할을 할 아이콘을 https://fontawesome.com/icons 사이트에서 찾는다.
위 heart 모양이 버튼을 클릭하면 꽉 찬 다른 하트 svg 파일로 바뀌는 것을 원했으나, 계속 구글링해도 HTML에서 svg 파일을 다른 svg 파일로 바꾸는 방법을 찾지 못했다. 결국 하트 색깔만 빨간색으로 바꾸는 방법을 찾았고, 그걸로 결정!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.fa-heart{
width: 20px;
height: 20px;
cursor: pointer;
transition: fill 0.3s ease;
}
.fa-heart.filled{
fill: red;
}
button {
border: 0;
background-color: #ffffff;
width: 20px;
height: 20px;
}
</style>
</head>
<body>
<button onclick="toggleHeart()" id="likeButton">
<svg id="toggleImg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="fa-heart"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M225.8 468.2l-2.5-2.3L48.1 303.2C17.4 274.7 0 234.7 0 192.8v-3.3c0-70.4 50-130.8 119.2-144C158.6 37.9 198.9 47 231 69.6c9 6.4 17.4 13.8 25 22.3c4.2-4.8 8.7-9.2 13.5-13.3c3.7-3.2 7.5-6.2 11.5-9c0 0 0 0 0 0C313.1 47 353.4 37.9 392.8 45.4C462 58.6 512 119.1 512 189.5v3.3c0 41.9-17.4 81.9-48.1 110.4L288.7 465.9l-2.5 2.3c-8.2 7.6-19 11.9-30.2 11.9s-22-4.2-30.2-11.9zM239.1 145c-.4-.3-.7-.7-1-1.1l-17.8-20c0 0-.1-.1-.1-.1c0 0 0 0 0 0c-23.1-25.9-58-37.7-92-31.2C81.6 101.5 48 142.1 48 189.5v3.3c0 28.5 11.9 55.8 32.8 75.2L256 430.7 431.2 268c20.9-19.4 32.8-46.7 32.8-75.2v-3.3c0-47.3-33.6-88-80.1-96.9c-34-6.5-69 5.4-92 31.2c0 0 0 0-.1 .1s0 0-.1 .1l-17.8 20c-.3 .4-.7 .7-1 1.1c-4.5 4.5-10.6 7-16.9 7s-12.4-2.5-16.9-7z"/></svg>
</button>
<script>
function toggleHeart(){
const toggleImg = document.getElementById("toggleImg");
toggleImg.classList.toggle("filled");
}
</script>
</body>
</html>
이렇게 찾는 데에 정말 오래 걸렸다. 나는 최대한 내가 뭔가 만들어내고 싶었는데 내 머릿속에 있는 기본 지식이 너무 부족하다는 것을 절절히 깨달았다..결국 구글링해서 찾았지만 그대로 쓰지도 못하고 지피티의 도움을 받았다. 팀원들은 뚝딱뚝딱 무언가 자꾸 만들어 내는데 나는 계속 헤메고 있는 기분이라 속상했다. 그렇지만 시간은 없고 어쨌든 뭔가를 만들어내긴 해야 했기에, 내가 코드를 짜기보다는 그냥 검색에 몰두했다. 괜찮은 소스를 찾았고, 지피티의 도움을 받아 코드를 완성했다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Like Button Example</title>
<style>
.fa-heart {
width: 20px;
height: 20px;
cursor: pointer;
transition: fill 0.3s ease;
}
.fa-heart.filled {
fill: red;
}
button {
border: 0;
background-color: #ffffff;
width: 20px;
height: 20px;
}
</style>
</head>
<body>
<button onclick="toggleHeart()" id="likeButton">
<svg id="toggleImg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="fa-heart">
<path d="M225.8 468.2l-2.5-2.3L48.1 303.2C17.4 274.7 0 234.7 0 192.8v-3.3c0-70.4 50-130.8 119.2-144C158.6 37.9 198.9 47 231 69.6c9 6.4 17.4 13.8 25 22.3c4.2-4.8 8.7-9.2 13.5-13.3c3.7-3.2 7.5-6.2 11.5-9c0 0 0 0 0 0C313.1 47 353.4 37.9 392.8 45.4C462 58.6 512 119.1 512 189.5v3.3c0 41.9-17.4 81.9-48.1 110.4L288.7 465.9l-2.5 2.3c-8.2 7.6-19 11.9-30.2 11.9s-22-4.2-30.2-11.9zM239.1 145c-.4-.3-.7-.7-1-1.1l-17.8-20c0 0-.1-.1-.1-.1c0 0 0 0 0 0c-23.1-25.9-58-37.7-92-31.2C81.6 101.5 48 142.1 48 189.5v3.3c0 28.5 11.9 55.8 32.8 75.2L256 430.7 431.2 268c20.9-19.4 32.8-46.7 32.8-75.2v-3.3c0-47.3-33.6-88-80.1-96.9c-34-6.5-69 5.4-92 31.2c0 0 0 0-.1 .1s0 0-.1 .1l-17.8 20c-.3 .4-.7 .7-1 1.1c-4.5 4.5-10.6 7-16.9 7s-12.4-2.5-16.9-7z"/>
</svg>
</button>
<div>Likes: <span id="likeCount">0</span></div>
<script type="module">
// Firebase SDK 라이브러리 가져오기
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
import { getFirestore, doc, setDoc, updateDoc, increment, getDoc, collection, deleteDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
// Firebase 구성 정보 설정
const firebaseConfig = {
apiKey: "AIzaSyCABiHiGzxGELpkUPOgFO_sgwI2SqUblLw",
authDomain: "diary-be831.firebaseapp.com",
projectId: "diary-be831",
storageBucket: "diary-be831.appspot.com",
messagingSenderId: "1030858653688",
appId: "1:1030858653688:web:e25e3a5d7965126b84600d"
};
// Firebase 인스턴스 초기화
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
async function toggleHeart() {
const userId = "someUniqueUserId"; // 로그인 없이 유니크한 사용자 ID 생성
const postId = "examplePostId"; // 예제 포스트 ID
const postRef = doc(db, "posts", postId);
const userLikeRef = doc(collection(postRef, "likedUsers"), userId);
const docSnap = await getDoc(userLikeRef);
let likeCount = 0;
if (docSnap.exists()) {
await deleteDoc(userLikeRef);
await updateDoc(postRef, { likeCount: increment(-1) });
document.getElementById("toggleImg").classList.remove("filled");
} else {
await setDoc(userLikeRef, {});
await updateDoc(postRef, { likeCount: increment(1) });
document.getElementById("toggleImg").classList.add("filled");
}
const postSnap = await getDoc(postRef);
if (postSnap.exists()) {
likeCount = postSnap.data().likeCount;
}
document.getElementById("likeCount").innerText = likeCount;
}
document.addEventListener("DOMContentLoaded", async () => {
const postId = "examplePostId"; // 예제 포스트 ID
const postRef = doc(db, "posts", postId);
const postSnap = await getDoc(postRef);
if (postSnap.exists()) {
document.getElementById("likeCount").innerText = postSnap.data().likeCount || 0;
}
});
</script>
</body>
</html>
근데 안된다..! 내가 짠 코드도 아니니 뭐 봐야 모르겠고, 지피티만 닥달하니 뭐가 다른지도 모르겠는 코드를 내놓는다. 결국 코드를 바리바리 싸들고 튜터님한테 달려갔다. 도와주세욕!!!!!!
튜터님하고 코드를 한줄한줄 보고, 코드가 동작하는지 console.log()로 동작해보며 어떤 점이 오류가 났는지 찾아봤다. 어떤 것이 작동하는지 알아볼 때, console. log()를 사용하는구나. 기억해두기로 했다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Like Button Example</title>
<style>
.fa-heart {
width: 20px;
height: 20px;
cursor: pointer;
transition: fill 0.3s ease;
}
.fa-heart.filled {
fill: red;
}
button {
border: 0;
background-color: #ffffff;
width: 20px;
height: 20px;
}
.like {
display: flex;
flex-direction: column;
align-items: center;
}
.Count{
margin-left: 10px;
}
</style>
</head>
<body>
<div class="like">
<button id="likeButton">
<svg id="toggleImg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="fa-heart">
<path d="M225.8 468.2l-2.5-2.3L48.1 303.2C17.4 274.7 0 234.7 0 192.8v-3.3c0-70.4 50-130.8 119.2-144C158.6 37.9 198.9 47 231 69.6c9 6.4 17.4 13.8 25 22.3c4.2-4.8 8.7-9.2 13.5-13.3c3.7-3.2 7.5-6.2 11.5-9c0 0 0 0 0 0C313.1 47 353.4 37.9 392.8 45.4C462 58.6 512 119.1 512 189.5v3.3c0 41.9-17.4 81.9-48.1 110.4L288.7 465.9l-2.5 2.3c-8.2 7.6-19 11.9-30.2 11.9s-22-4.2-30.2-11.9zM239.1 145c-.4-.3-.7-.7-1-1.1l-17.8-20c0 0-.1-.1-.1-.1c0 0 0 0 0 0c-23.1-25.9-58-37.7-92-31.2C81.6 101.5 48 142.1 48 189.5v3.3c0 28.5 11.9 55.8 32.8 75.2L256 430.7 431.2 268c20.9-19.4 32.8-46.7 32.8-75.2v-3.3c0-47.3-33.6-88-80.1-96.9c-34-6.5-69 5.4-92 31.2c0 0 0 0-.1 .1s0 0-.1 .1l-17.8 20c-.3 .4-.7 .7-1 1.1c-4.5 4.5-10.6 7-16.9 7s-12.4-2.5-16.9-7z"/>
</svg>
</button>
<div class="Count"><span id="likeCount">0</span></div>
</div>
<script type="module">
// Firebase SDK 라이브러리 가져오기
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
import { getFirestore, doc, setDoc, updateDoc, increment, getDoc, collection, deleteDoc } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
// Firebase 구성 정보 설정
const firebaseConfig = {
apiKey: "AIzaSyCABiHiGzxGELpkUPOgFO_sgwI2SqUblLw",
authDomain: "diary-be831.firebaseapp.com",
projectId: "diary-be831",
storageBucket: "diary-be831.appspot.com",
messagingSenderId: "1030858653688",
appId: "1:1030858653688:web:e25e3a5d7965126b84600d"
};
// Firebase 인스턴스 초기화
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const likeButton = document.getElementById("likeButton");
async function toggleHeart() {
const userId = "someUniqueUserId"; // 로그인 없이 유니크한 사용자 ID 생성
const postId = "postId"; // 예제 포스트 ID
const postRef = doc(db, "testPosts", postId); // 경로 수정
const userLikeRef = doc(collection(postRef, "likedUsers"), userId);
const docSnap = await getDoc(userLikeRef);
let likeCount = 0;
if (docSnap.exists()) {
await deleteDoc(userLikeRef);
await updateDoc(postRef, { likeCount: increment(-1) });
document.getElementById("toggleImg").classList.remove("filled");
} else {
await setDoc(userLikeRef, {});
await updateDoc(postRef, { likeCount: increment(1) });
document.getElementById("toggleImg").classList.add("filled");
}
const postSnap = await getDoc(postRef);
if (postSnap.exists()) {
likeCount = postSnap.data().likeCount;
}
document.getElementById("likeCount").innerText = likeCount;
}
likeButton.addEventListener("click", toggleHeart); // 클릭 이벤트 리스너 추가
document.addEventListener("DOMContentLoaded", async () => { // DOMContentLoaded 이벤트 리스너 추가
const postId = "postId"; // 예제 포스트 ID
const postRef = doc(db, "testPosts", postId); // 경로 수정
const postSnap = await getDoc(postRef);
if (postSnap.exists()) {
document.getElementById("likeCount").innerText = postSnap.data().likeCount || 0; // 좋아요 수 표시 코드
}
});
</script>
</body>
</html>
최종 코드. 코드를 작성하고 튜터님과 대화하면서 정말 내가 모르는게 많구나! 라고 생각했다. 자바스크립트 쌩 기초부터 공부하기로 하고, 일단 만든 코드를 수정하려면 코드를 알고 있어야 하니 코드를 다 뜯어서 모르는 것을 공부하기로 했다.
1. const 가 대체 뭐지?
-자바스크립트에서 변수 선언 방식으로 쓰인다. var, let, const 가 모두 변수 선언 방식으로 쓰인다.
-let과 const 는 변수 재선언이 되지 않는다.
1-1 let 과 const의 차이점
- immutable 여부
> let은 변수에 재할당이 가능하다.
let name = 'bathingape'
console.log(name) // bathingape
let name = 'javascript'
console.log(name)
//Uncaught SyntaxError : Identifier 'name' has already been declared
name = 'react'
console.log(name) // react
>const는 변수 재선언, 변수 재할당 모두 불가능하다.
const name = 'bathingape'
console.log(name) // bathingape
const name = 'javascript'
console.log(name)
//Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'react'
console.log(name)
//Uncaught TypeError: Assignment to constant variable.
-따라서 변수 선언에는 기본적으로 const 를 사용하고, 재할당이 필요한 경우에 한정해 let 을 사용하는 것이 좋다.
그리고 객체를 재할당하는 경우는 생각보다 흔하지 않다. const를 사용하면 의도치 않은 재할당을 방지해 주기 때문에 보다 안전하다.
2. 자바스크립트 기본 문법이 알고 싶다!
- 따로 카테고리를 파서 공부할 예정. https://ko.javascript.info/hello-world 사이트를 참고할 것.
3. async 이 뭐지?
3-1. 프라미스
1. 제작 코드(producing code)는 원격에서 스크립트를 불러오는 것 같은 시간이 걸리는 일을 한다.
2. 소비 코드(consuming code)는 '제작 코드'의 결과를 기다렸다가 이를 소비한다. 이때 소비 주체(함수)는 여럿이 될 수 있다.
3. 프라미스(promise)는 '제작 코드'와 '소비 코드'를 연결해 주는 특별한 자바스크립트 객체이다. '프라미스'는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 '제작 코드'가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 해 준다.
-promise 객체는 아래와 같은 문법으로 만들 수 있다.
let promise = new Promise(function(resolve, reject) {
// executor(제작 코드, '가수')
});
new Promise에 전달되는 함수는 executor(실행자, 실행 함수)라고 부른다. executor는 new Promise가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함한다.
executor의 인수 resolve와 reject는 자바스크립트에서 자체 제공하는 콜백이다. 개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 된다.
*콜백 함수 : 다른 함수에 매개변수로 넘겨준 함수. 매개변수로 넘겨받은 함수는 일단 넘겨받고, 때가 되면 나중에 호출(called back)한다는 것이 콜백함수의 개념.
대신 executor에선 결과를 즉시 얻든 늦게 얻든 상관없이 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 한다.
- resolve(value) : 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
- reject(error) : 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
> executor는 자동으로 실행되는데 여기서 원하는 일이 처리된다. 처리가 끝나면 executor는 처리 성공 여부에 따라 resolve나 reject를 호출한다.
한편, newPromise 생성자가 반환하는 promise 객체는 다음과 같은 내부 프로퍼티를 갖는다.
*생성자 함수는 아래 두 관례를 따른다.
1. 함수 이름의 첫 글자는 대문자로 시작한다.
2. 반드시 'new'연산자를 붙여 실행한다.
예시)
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new user("보라");
alert(user.name); //보라
alert(user.isAdmin); //false
new User(...)를 써서 함수를 실행하면 아래와 같은 알고리즘이 동작한다.
1. 빈 객체를 만들어 this 에 할당한다.
2. 함수 본문을 실행한다. this에 새로운 프로퍼티를 추가해 this를 수정한다.
*프로퍼티 : 객체의 특성이나 속성을 나타내는 것. 객체는 여러 프로퍼티를 가질 수 있으며, 각 프로퍼티는 이름(또는 키)과 값으로 구성된다.
3. this를 반환한다.
예시를 이용해 new User(...)가 실행되면 무슨 일이 일어나는지 살펴보자.
function User(name) {
// this ={}; (1. 빈 객체가 만들어짐, this에 할당됨.)
// 함수 본문이 실행되어 새로운 프로퍼티를 this에 추가함
this.name = name; //this에 name 프로퍼티 추가
this.isAdmin = false; //this에 isAdmin 프로퍼티 추가
//return this; (this가 암시적으로 반환됨)
}
let user = new User("Alice");
console.log(user); // {name : "Alice", isAdmin: false}
이제 let user = new User("Alice")는 아래 코드를 입력한 것과 동일하게 동작한다.
let user = {
name : "Alice",
isAdmin : false
}
객체 리터럴 문법으로 일일이 객체를 만드는 방법보다 훨씬 간단하고 읽기 쉽게 객체를 만들 수 있게 되었다.
'스파르타 > 팀과제, 개인과제' 카테고리의 다른 글
팀과제 - 뉴스피드 만들기 (1. 추가, 삭제 코드 구현) (3) | 2024.09.02 |
---|---|
Medal Tracker 컴포넌트 분리, 리팩토링하기 (0) | 2024.08.14 |
개인과제 - Medal Tracker 코드 분석하기 (0) | 2024.08.14 |
자바스크립트 팀 과제 : 감정구슬(1) / 기획, 와이어프레임, 계획 짜기 단계 (0) | 2024.08.07 |
git 사용하기 (0) | 2024.07.18 |