-
Notifications
You must be signed in to change notification settings - Fork 5
트러블 슈팅
이미지 POST요청시 이미지를 불러오지 못하는 문제
기존코드
// 이미지 업로드 시 미리보기
const imgPreView = async (event) => {
const reader = new FileReader();
reader.onload = (event) => {
$productImg.setAttribute('src', event.target.result);
$productImg.style.display = 'block';
};
reader.readAsDataURL(event.target.files[0]);
};
// 이미지 POST요청
const storeImage = async (event) => {
const formData = new FormData();
formData.append('image', event.target.files[0]);
try {
const res = await fetch(url + '/image/uploadfile', {
method: 'POST',
body: formData,
});
const resJson = await res.json();
console.log(resJson);
} catch (err) {
console.error(err);
}
};
해결방법
- 서버로부터 받은 응답 중 숫자로 이루어진 filename을 사용
- 그 이름을 서버 주소에 붙여서 상품명, 가격 등의 다른 상품 정보와 함께 서버로 전송
- 전역에 응답받은 파일명을 저장할 변수를 선언
let filename = '';
- 리턴 받은 파일명을 filename에 저장
filename = await storeImage(event.target);
- 서버주소와 파일명 붙여서 서버로 전송
itemImage: `${url}/${filename}`,
해결 코드
// 이미지 업로드 시 미리보기
const imgPreView = async (event) => {
const reader = new FileReader();
reader.onload = (event) => {
$productImg.setAttribute('src', event.target.result);
$productImg.style.display = 'block';
};
reader.readAsDataURL(event.target.files[0]);
filename = await storeImage(event.target); // 리턴 받은 파일명을 filename에 저장
};
// 이미지 POST요청
const storeImage = async (target) => {
const formData = new FormData();
formData.append('image', target.files[0]);
try {
const res = await fetch(url + '/image/uploadfile', {
method: 'POST',
body: formData,
});
const resJson = await res.json(); // 서버로부터 받은 응답
return resJson.filename; // 응답 중 filename 리턴
} catch (err) {
console.error(err);
}
};
async function productData() {
try {
const res = await fetch(url + '/product', {
method: 'POST',
body: JSON.stringify({
product: {
itemName: $inputProductTitle.value,
price: parseInt($inputProductPrice.value.replaceAll(',', '')),
link: $inputProductLink.value,
itemImage: `${url}/${filename}`,
},
}),
headers: {
'Authorization': `Bearer ${token}`,
'Content-type': 'application/json',
},
});
const resJson = await res.json();
console.log(resJson);
// alert('상품이 정상적으로 등록되었습니다');
// isProductTrue();
} catch (err) {
console.error(err);
}
}
myProfile.html에서 판매중인 상품을 클릭하고 [수정] 버튼 클릭시, 상품의 상세페이지로 이동하면서 상품을 수정할 수 있는 기능을 구현해야한다.
해결방법
- 상품 수정 버튼과 연결된 url에 상품 id를 파리미터로 붙이기
- 상품 수정 페이지에서 상품 상세 요청(GET)과 상품 수정 요청(PUT)을 수행해야 하는데 이때 상품 아이디가 필요하기 때문에 id를 사용할 수 있도록 파라미터에 붙여준다.
- productList.js에서 [수정] 링크의 href를 다음과 같이 수정한다.
a.href = `../pages/productModification.html?id=${상품 id}`
(productList.js)
const btnModifyProduct = document.querySelector('.btnModifyProduct');
btnModifyProduct.addEventListener('click', (event) => {
location.href =
'../pages/productModification.html?id=' +
event.target.getAttribute('dataId');
});
-
location.search
로 개별 상품 id를 추출하여 prodId에 할당한다.
(productModification.js)
const prodId = location.search.split('id=')[1];
- 상품 데이터 GET요청으로 불러온다.
(productModification.js)
// 상품 데이터 GET요청으로 가져오기
(async function getProductData() {
try {
const res = await fetch(url + `/product/detail/${prodId}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-type': 'application/json',
},
});
const resJson = await res.json();
setProductData(resJson.product);
} catch (err) {
console.error(err);
}
})();
-
문제 상황
- 하나의 html파일과 API로 yourProfile, myProfile를 동시에 구현해야 했음
- following/follower 리스트에서 프로필로 넘어갈 때, 해당 유저에 맞는 yourProfile을 보여줘야 함
- 토큰에서는 상대방의 accountname을 가져올 수 없다는 것과 클릭 했을 때 해당 유저의 accountname만을 정확하게 집어내지 못하고 있었음
-
원인 추론
- GET 요청 url뒤에 accountname을 넣어줘야 하는데, 나의 프로필은 로컬스토리지에서 getItem으로 사용하면 되지만 상대방 프로필 accountname을 가져오지 못했음
-
해결 방법
- Location (현재 URL) Property 중에서 query string이라고 불리는 search string을 사용하여 해결
- search string은 url에서
?
를 포함한 뒷부분을 string 형식으로 반환해주는 역할
실제 적용 코드)
followingList.html에서 유저를 선택할 때 넘어가는 a태그의 href 주소를
?accountname=유저의 accountname
붙여서 보냄link.setAttribute( 'href', '../pages/myProfile.html?accountname=' + resJson[i].accountname );
프로필 html로 넘어와서 상대방의 accountname을 location.search로 변수에 담아 사용
내 계정일 경우에는 로컬스토리지에서, 상대방 프로필이면 url을 query string값이 accountname에 들어가게 처리
-
location.search⇒
?accountname=유저아이디
-
location.search.replace(’?’,’’) ⇒
accountname=유저아이디
-
location.search.replace(’?’,’’).split(’=’) ⇒
['accountname', '유저아이디']
📌 userInfo.js (프로필 상단, 유저 정보 API GET 요청 코드)
const url = 'https://mandarin.api.weniv.co.kr'; const myAccountname = `${window.localStorage.getItem('accountname')}`; const yourAccountname = location.search.replace('?', '').split('=')[1]; const accountname = yourAccountname ? yourAccountname : myAccountname; const userSettings = document.querySelector('.userSettings'); //프로필 정보 보여주기 async function infoUser() { try { const res = await fetch(url + '/profile/' + accountname, { method: 'GET', ..생략..} }
-
문제 상황
- JavaScript에서 요소를 생성할 때(특히 반복되는 리스트들), map과 innerHTML을 사용함
- innerHTML은 간혹 인식되 않아 null 값을 반환하는 경우가 생겨 Error가 발생
- 사용은 편리하지만 성능이 가장 나쁨
- 보안 상의 문제도 존재(스크립트 해킹을 당할 수 있음)
-
해결 방법
- 코드가 길어지더라도 안전하고 성능이 좋은 createElement로 모든 요소를 생성하고 반복되는 요소를 생성할 때는 for문을 사용
- 팀원 모두 createElement를 공통적으로 사용
기존 코드)
function createProductList() {
productList.innerHTML = prodcutListDummy
.map(
(element) =>
`
<li>
<button type="button" class="btnProductItem" id="btnProductItem">
<img src=${element.itemImage} alt="상품1" />
<span class="prodcutTitle">${element.itemName}</span>
<strong class="prodcutPrice">${element.price
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</strong>
</button>
</li>
`
)
.join('');
}
해결 코드)
function createProductList() {
for (let i = 0; i < prodcutListDummy.length; i++) {
const li = document.createElement('li');
const button = document.createElement('button');
const img = document.createElement('img');
const span = document.createElement('span');
const strong = document.createElement('strong');
**생략 ...**
}
-
문제 상황
- 마이프로필의 경우, 상품리스트나 게시물 리스트 등 반복되어 같은 class나 id를 갖고 있는 요소들을 선택해야 하는 경우가 많았음
- 그래서 querySelectorAll으로 요소를 선택하고 click evnent를 줘야 했는데, 선택된 요소가 null 값으로 반환되어 error
-
원인 추론
- 노드리스트 배열를 반환하는 querySelectorAll이기 때문에 반복문을 사용하지 않음
- 함수 내 지역변수로 선택 요소를 선언하지 않고, 함수 밖의 전역변수로 설정하여 for 구문이 요소를 null 값을 반환
문제의 코드
const btnProductItem = document.qeurySelectorAll('.btnProductItem')
function onProductSettingModal() {
btnProductItem.addEventListenr('click', () => {
...**생략...**
})
}
- 해결
- 반복문 사용
- 선택할 요소를 함수 내부로 옮겨 지역변수로 바꿈
function onProductSettingModal() {
let btnProductItem = document.querySelectorAll('.btnProductItem');
for (let i = 0; i < btnProductItem.length; i++) {
btnProductItem[i].addEventListener('click', (event) => {
...**생략...**
}
로그인 성공 시 토큰을 로컬 스토리지에 담았습니다. 배포 페이지에 재 진입 시 서버에 토큰 유무를 파악하여 페이지를 달리하는 로직을 구현하였습니다. 즉시 실행 함수를 사용하여 비동기 통신으로 구현하였습니다.
//토큰 유효성 검사
(async function tokenValid() {
try {
const res = await fetch(url + '/user/checktoken', {
method: 'GET',
headers: {
'Authorization': `Bearer ${localToken}`,
'Content-Type': 'application/json',
},
});
const resJson = await res.json();
console.log(resJson);
if (resJson.isValid === true) {
changePageeHome();
} else {
splash();
}
} catch (err) {
changePageTo404();
}
})();
소셜 로그인 페이지 진입 시 서서히 등장 하는 애니메이션 구현하였습니다. CSS에 keyframes를 활용하여 구현 하였습니다. 성능 향상을 위해 window.requestAnimationFrame() API를 활용하여 리팩토링할 예정입니다.
기존코드
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-moz-keyframes fadein {
/* Firefox */
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-webkit-keyframes fadein {
/* Safari and Chrome */
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-o-keyframes fadein {
/* Opera */
from {
opacity: 0;
}
to {
opacity: 1;
}
}