특정 규칙이 있는 많은 사진을 불러 오기
인턴십 클론 코딩 활동 중, 댓글의 프로필 이미지를 랜더링 해야할 일이 생겼다. 일반적인 프로필이라면 그냥 JSON의 프로필 이미지 url을 땡겨오면 그만이겠지만 옥소폴리틱스는 특이하게도 유저의 성향과 응답에 따라 댓글의 프로필이미지가 달라졌다.
성향은 총 5개, 응답은 4개였으므로 총 20개의 이미지 파일 중에 골라 프로필 사진으로 띄워야 했다.
전부 import?
처음 떠올린 것은 20장 이미지를 전부 import한 뒤 원하는 키워드로 등록해서 img를 리턴하는 함수를 만드는 것이었다. 코드가 길어지는 것도 문제지만 수정사항이 생겼을 때 골치가 아파질 것이 너무 자명했다.
import ans1-tribe1 from '../img/ans1-tribe1.png';
import ans2-tribe1 from '../img/ans2-tribe1.png';
import ans3-tribe1 from '../img/ans3-tribe1.png';
import ans4-tribe1 from '../img/ans4-tribe1.png';
import ans1-tribe2 from '../img/ans1-tribe2.png';
import ans2-tribe2 from '../img/ans2-tribe2.png';
import ans3-tribe2 from '../img/ans3-tribe2.png';
import ans4-tribe2 from '../img/ans4-tribe2.png';
.
.
.
못할 짓이다. 심지어 이 뒤에는 기나긴 switch문이 들어가야 한다..
이미지 URL을 String으로 만들어 리턴하기
그래서 떠올린 방법이 이미지의 url을 문자열로 만드는 것이었다. 각 응답과 성향을 받아 배열 또는 객체로 만든 뒤 img 컴포넌트를 return하는 것이었다. 코드가 위의 import 예시보다도 짧아졌다.
const getTribeImg = (ans, tribeID) => {
let tribes = [null, 'tirbe1', 'tirbe2', 'tirbe3', 'tirbe4', 'tirbe5'];
let answer = { o: 'O', x: 'X', '?': 'DUNNO', u: 'NORMAL',
};
return (
<img
src={`./src/components/.../${tribes[parseInt(tribeID)]
}_${answer[ans]}.png`}
/>
);
};
export default getTribeImg;
**이미지를 컴포넌트 형태로 return할 때 주의사항이 있다! src 어트리뷰트는 수정되지 않고 index.html에 들어갈 때까지 유지되기 때문에 반드시 경로를 index.html를 시작점으로 잡아야 한다!
스타일드 컴포넌트(Styled-Components) 사용법
공부할 때 잠깐 훑어만 봤었던 스타일드 컴포넌트를 실제로는 처음으로 사용해봤다. 강의와 유튜브로 사용법은 알고 있다고 생각했기 때문에 Docu를 자세히 보지 않았는데, 문제가 좀 생겼다.
return (
<GroupsContainer>
<GroupImg className='GroupImg' src={imgLink} alt='그룹 이미지' />
<GroupInfoAria>
<GroupTitle>{title}</GroupTitle>
<GroupDescribtionAria>
<GroupDescribtion>{groupDescribtion}</GroupDescribtion>
<GroupEtcInfo>
팔로워 {followers} · {dayString}
</GroupEtcInfo>
</GroupDescribtionAria>
</GroupInfoAria>
</GroupsContainer>
);
위의 컴포넌트 들은 모두 어떤 기능이 있는게 아닌 그저 CSS 스타일링이 필요한 div 태그들이었다. 스타일 컴포넌트는 무조건 컴포넌트를 선언하고 스타일링 해야되는 줄 알고 모든 div 태그를 컴포넌트 화 했는데 아무리 봐도 이건 아니었다.
인턴십 멘토링 시간에 현직자 분께 여쭤보니 컴포넌트 내부에 SCSS처럼 자식 요소를 넣을 수 있었다... 그래 안 될리가 없다고..
스타일 컴포넌트의 핵심은 같은 이름을 사용해도 다른 컴포넌트의 클래스명과 겹치지 않는다는 점이기 때문에 딱히 겹칠일이 없는 클래스 명은 그냥 div태그에 클래스명을 선언하여 그대로 사용해도 되었다.
return (
<GroupContainer imgUrl={imgLink}>
<div className='groupImg' />
<div className='groupInfoAria'>
<div className='groupTitle'>{title}</div>
<div className='groupDescriptionAria'>
<div className='groupDescription'>{groupdescription}</div>
<div className='groupEtcInfo'>
팔로워 {followers} · {dayString}
</div>
</div>
</div>
</GroupContainer>
);
}
//* Styled Components
const GroupContainer = styled.div`
display: flex;
-webkit-box-pack: start;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
height: 70px;
cursor: pointer;
.groupImg {
...
}
.groupInfoAria {
...
.groupTitle {
...
}
}
.
.
.
div 태그가 여전히 많지만(애증의 HTML) 컴포넌트 투성이였던 이전 보다는 훨씬 보기 편해졌다. CSS용 클래스 네임 붙이는 방법인 BEM이라는 게 있던데 추가로 공부가 필요하다.
이벤트 위임과 버블링
어플리케이션을 개발하다 보면 클릭이벤트를 처리해야할 일이 정말 많다. 이때 버튼이 여러 개라면 각 버튼마다 onClick 이벤트 리스너주고는 했다. 요즘 틈날 때마다 자바스크립트 자습서를 읽는 중인데 이벤트 위임이라는 개념을 알게 되었다.
이벤트 버블링?
<div onclick="divEventHandler()"><img src="./asd.jpg" alt="asd" /></div>
이벤트 위임을 위해서는 우선 이벤트 버블링을 알아야한다. 위 코드와 같이 <div>태그로 감싸진 <img>태그를 누른다면 <div>에 등록된 이벤트 핸들러 역시 작동한다. 어떤 요소에 이벤트가 발생하면 그 요소를 포함하고 있는 모든 부모의 핸들러가 작동하는 것이다. 이것이 거품이 이는 것과 비슷하여 버블링(Bubbling)이라고 부른다. 이벤트는 최종적으로 <body>를 지나 <html>을 거쳐 document 객체까지 도달한다.
버블링을 중단하고 싶다면 events.stopPropagation() 함수를 사용하면 된다. 이벤트는 stopPropagation이 사용된 엘리먼트까지만 버블링 된다. **공식 문서에서는 버블링을 막는 것을 권장하지 않고 있다 - 나중에 에러를 잡기 어려워지는 등의 문제가 생길 수 있다.
이벤트 위임
이벤트 버블링을 활용하면 여러 개의 버튼도 한 개의 이벤트 핸들러를 활용해 핸들링할 수 있다. 버튼 요소들을 감싼 컨테이너(보통 div)에 한 개의 이벤트 핸들러만을 등록한 뒤, event의 target을 이용하면 해당 이벤트가 일어난 가장 깊은 자식요소를 알아낼 수 있다! 하지만 내가 원한 버튼이 가장 깊은 자식요소가 아니라면 어떻게 할까? 다음과 같이 closest() 메서드를 이용하면 해당 요소를 포함하는 가장 가까운 부모요소(또는 자기 자신)의 여부를 확인할 수 있다.
// 내가 원하는 것은 td의 이벤트 확인
table.onclick = function(event) {
let td = event.target.closest('td');
if (!td) return;
if (!table.contains(td)) return; //
highlight(td); // (4)
};
td 속의 어떤 요소를 클릭하더라도 결국 조상 요소 중에 td가 있다면 이벤트 핸들러(highlight)는 실행될 것이다.
HTML의 데이터 속성을 이용하면 이벤트 위임을 더 활용할 수 있다.
데이터 속성
HTML5 특정 요소와 연관되어 있지만 확정된 의미는 갖지 않는 데이터에 대한 확장 가능성을 염두에 두고 디자인되었습니다. data-* 속성은 표준이 아닌 속성이나 추가적인 DOM 속성, Node.setUserData()과 같은 다른 조작을 하지 않고도, 의미론적 표준 HTML 요소에 추가 정보를 저장할 수 있도록 해줍니다. - MDN Document
알다가도 모르겠는 MDN 문서 번역
데이터 속성(data-*)은 HTML의 공식 어트리뷰트(속성)이며, data로 시작하는 속성은 무엇이든(data-action, data-columns 등) 사용할 수 있다. 보통 JavaScript에서 어트리뷰트에 접근하기 위해서는 getAttribute()를 사용하면 되지만 data-* 속성은 해당 엘리먼트의 dataset 객체를 통해 접근 할 수 있다.
<script>
var article = document.getElementById('electriccars');
article.dataset.columns // "3"
article.dataset.indexNumber // "12314"
article.dataset.parent // "cars"
</script>
<article
id="electriccars"
data-columns="3"
data-index-number="12314"
data-parent="cars">
...
</article>
data- 뒷 부분만 이용하면 되며, 위에서 보듯 대시(-)는 카멜 케이스(camelCase)로 자동 변환된다. 또한 순수한(커스텀이 아닌) HTML 속성이기 때문에 CSS의 attr() 함수를 이용해서도 접근이 가능하다. 아래와 같이 이벤트 위임에 활용할 수 있지만 안 써도 큰 무리는 없을 듯하다(?)
<div id="menu">
<button data-action="save">저장하기</button>
<button data-action="load">불러오기</button>
<button data-action="search">검색하기</button>
</div>
<script>
class Menu {
constructor(elem) {
this._elem = elem;
elem.onclick = this.onClick.bind(this);
}
save() {
alert('저장하기');
}
load() {
alert('불러오기');
}
search() {
alert('검색하기');
}
onClick(event) {
let action = event.target.dataset.action;
if (action) {
this[action]();
}
};
}
new Menu(menu);
</script>
더 공부해야할 것
- CSS Class name convention (ex. BEM)
- Git... Git...!!!! 편하게 쓰는 법
- 실무에서 많은 양의 사진 파일을 사용하는 법
- 이벤트에 대한 더 많은 공부
'Dev > TIL' 카테고리의 다른 글
TIL#1 - Today I Learned (0) | 2022.08.04 |
---|