오전9시부터-24시 회의하는.. 요즘.. 회의가 끝나면 늘 리액트 쿼리를 공부하고 있습니다.. 저는 정말 자고 싶었지만 지금 정리하지 않으면 하지 않을 것 같아 자리에 앉아 스타벅스 핑크 드링크를 쪽쪽마시며.. 게시글을 적어봅니다..
먼저 메인프로젝트 기간 동안 캐러셀을 구현해야했던 저는.. 라이브러리를 선택하고야 말았읍니다. 시간이 너무 짧았기에 좋은 캐러셀 라이브러리를 찾다가 편리하고 유용하게 커스텀이 되는 Swiper 라이브러리를 활용하게 되었습니다. 지금은 가로로 넘어가는 캐러셀을 구현을 위해 사용했지만 추후에 세로로도 넘어가는 캐러셀을 구현해야했기때문에 쉽게 세로로도 커스텀이 가능한 Swiper 라이브러리를 선택하였습니다.
● 첫 번째 역경.. Swiper
Swiper - The Most Modern Mobile Touch Slider
Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.
swiperjs.com
리액트에서 swiper를 사용하는 방법이 공식문서 doc에 잘 설명이 되어있습니다. 저는 vite환경에서 진행하였습니다.
npm i swiper
swiper의 좋은 점은 이미 구현이 된 Demo들을 제공한다는 점인데요, 따라서 저는 Demo중에서도 아주 기본적인 캐러셀 구현만 필요했기 때문에 Navigation Demo를 활용하였습니다.
❍ Swiper Demo
데모는 아래 사이트에서 확인할 수 있습니다.
Swiper Demos
Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.
swiperjs.com
저는 리액트 구현 코드를 활용하였습니다. 아래는 Swiper에서 제공해주는 데모 코드입니다.
import React, { useRef, useState } from 'react';
// Import Swiper React components
import { Swiper, SwiperSlide } from 'swiper/react';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import './styles.css';
// import required modules
import { Navigation } from 'swiper/modules';
export default function App() {
return (
<>
<Swiper navigation={true} modules={[Navigation]} className="mySwiper">
<SwiperSlide>Slide 1</SwiperSlide>
<SwiperSlide>Slide 2</SwiperSlide>
<SwiperSlide>Slide 3</SwiperSlide>
<SwiperSlide>Slide 4</SwiperSlide>
<SwiperSlide>Slide 5</SwiperSlide>
<SwiperSlide>Slide 6</SwiperSlide>
<SwiperSlide>Slide 7</SwiperSlide>
<SwiperSlide>Slide 8</SwiperSlide>
<SwiperSlide>Slide 9</SwiperSlide>
</Swiper>
</>
);
}
맨위의 useState, useRef는 리액트 환경임을 명시하기 위해 예시로 넣은 코드 같았습니다. 코드를 보면 Swiper컴포넌트 안에 SwiperSlide컴포넌트를 넣어서 하나씩 캐러셀을 구현하는 것이라고 파악할 수 있었습니다. 사용법이 너무 쉽다!! 프리 프로젝트때에도 2주였던 것 같은데.. 메인 프로젝트는 3주.. 그리고 일주일은 거의 테스트와 발표때문에.. 없어져서 메인프로젝트도 2주가 남은 상황에서 새로운 라이브러리를 도입한다?
사실 캐러셀 구현에 자신이 없었기 때문에 새로운 라이브러리를 적용하는 것도 두려웠지만 코드를 보니 쉽게 도입할 수 있을 것 같았습니다. 코드를 보면 Swiper태그 안에 navigation 이라는 값과 modules에는 추가적으로 내가 넣고싶은 모듈을 import해 넣을 수 있는 것 같았습니다. 미리 css도 구현이 되어있었기 때문에 아주 쉽게 캐러셀 스타일도 바꿀 수 있었습니다.
● 두 번째의 역경.. 이미지 .. 업로드하기..
예전에 html 공부할때 Input type으로 file을 주면 이미지를 업로드 할 수 있었던 기억이 납니다.. 너 노마드코더 들었잖아.. 너 밈메이커 만들어본적있잖아.. 하지만.. 저는 태초마을인걸요?
(Meme Maker) 밈메이커 완성!
Meme Maker 밈메이커 https://ddaeunbb.github.io/project/meme-maker/index.html Meme Maker You really want to reset your canvas? Yes No ddaeunbb.github.io
ddaeunbb.tistory.com
아니 어떻게 캐러셀이 버튼을 넣고 이미지를 넣지? 에 대한 심각히 막막함이 있었습니다. 그리고 input태그를 버튼으로 넣고 input태그를 커스텀해야하나? 라는 생각이 들었는데요. 여러 블로그를 찾아보며 대체로 label태그를 활용해 사용하는 것을 알게 되었습니다.
<SwiperSlide>
<Form>
<Pluslabel htmlFor="feedimg">
<Plus className="w-full mt-[180px]" />
</Pluslabel>
<input
id="feedimg"
type="file"
accept="image/*"
onChange={e => handleUpload(e)}
style={{ display: 'none' }}
multiple
/>
</Form>
</SwiperSlide>
코드를 복붙해와서 좀 모양이 이상하지만 ^^;;
- input 태그에 id값을 주고 Pluslabel 컴포넌트를 만들어 htmlFor 속성을 주었습니다.
- 그리고 Input태그는 보기 흉측하니•••!!! style값에 display: 'none'으로 주었습니다
- .multiple로 여러개의 이미지를 받을 수 있게 하였습니다.
이후 onChange 핸들러 값을 주어 구현하였습니다. 핸들러 및 구현은 아래 추가적으로 또 내용을 적겠읍니다.. 봐주십시오..
● 세 번째의 역경.. 큰 역경.. 시렵다.. 그래 업로드는 되네.. 근데 어케 보여줌?;; 첨보는 이벤트 객체인디?
아니 그래요.. Input으로 열어서 이미지 업로드 했어요.. 근데 어케 이 사진들을 보여주죠?
일단 저는 onChange핸들러에 이벤트 객체를 넘겨서 어떤 이벤트 객체를 받는지.. 확인해보았습니다. 여러 블로그를 확인해보니 무슨.. files?.. 또 무슨 FileReader 내장함수를 활용하더군요.. 여기서 심각..함을 느끼게 되었습니다. 아 뭔가 잘못됐다.
- e.target.files 배열에 파일의 정보가 담겨있다.
일단 이를 먼저 알게 되었습니다.
날짜 그리고 이름, 등 정보들이 담기게 되는데요, 이를 바로 받아 이미지의 src에 넣어주면 안되냐? 이 배열을 map으로 돌아서 src 값으로 넘겨주면 걍되는거아님?
이라고 생각했는데, 사실 fileList는 array-like 유사배열객체이기 때문에 (이전에 스터디에서 디립다 팠는디.. 잘했다.. 참잘했어요..) 배열 메서드를 활용할 수 없었습니다.
[JavaScript] FileList는 배열이 아니다.
<iuput type='file'>를 사용하면서 하나가 아닌 여러 파일을을 받을 때 데이터를 filter를 사용하거나, map을 사용하려고 했지만, not function이라는 경고와 함께 메소드가 실행되지 않는 것을 볼 수 있었
velog.io
그리고 추가적으로 유사배열객체담긴 name만으로는 img의 src값으로 넘겨줄 수 없었고 또한 백엔드분들께 이미지 원본을 보내드려아하는데 어떠한 파싱 작업 이후 백엔드에 보내드려야 백엔드분들이 S3에 저장해 http 같은 url화 하여 프론트단에서 이미지를 보여줄 수 있다는 것도 알게 되었습니다.
따라서 이미지를 파싱해야된다•••!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! OMG 어케 파싱하는건데 그래서••• 제발••• 멈춰
● 네 번째의 역경.. 이미지 파슁.. 리더로 읽혀본다..
- 내장객체인 fileReader의 메서드인 readAsDataURL 함수를 써서 이미지 변환이 필요하다.
- 그리고 이 함수는 비동기적으로 이뤄지기때문에 promise를 반환한다.
일단 여러 블로그를 찾아보니 알게된 팩트는 위와 같았습니다. 먼저 구현을 하기 이전에 위를 바탕으로 제가 생각한 구현로직은 아래와 같았습니다.
- 이벤트 객체인 e.target.files 를 통해 fileList 를 받아온다.
- fileList를 Array.from으로 array로 만든다.
- 배열로 만든 뒤, map을 돌면서 readAsDataURL 메서드를 돌린다.
- 하나씩 다 돌려지면 배열로 구성된 state에 추가한다.
- 그리고 배열인 state를 map으로 돌려 이미지로 보여준다. 즉, Swiper의 SwiperSlide 컴포넌트로 넣어준다.
위 로직을 아래 코드로 구현했습니다.
export const parseImg = ({
e,
setIsOpen,
setSavedFile,
savedFile,
}: ParseImgProp) => {
// 먼저 files를 file타입으로 구성된 배열로 설정합니다.
let files: File[] | undefined;
if (e.target.files) {
if (e.target.files.length >= 4) {
// 만약에 처음에 input으로 올린 친구가 4개 이상이라면 모달창을 띄우게 했습니다.
setIsOpen(true);
// 다음 모든 array-like인 filelist들을 배열화합니다.
files = Array.from(e.target.files);
// 그 중 3개만 가져옵니다.
files = files.slice(0, 3);
} else if (savedFile.length + e.target.files.length >= 4) {
// 추후에 파일을 추가적으로 올렸을 경우 4개 이상이라면 모달창을 띄우게 합니다.
setIsOpen(true);
const maximum = 3 - savedFile.length;
files = Array.from(e.target.files);
files = files.slice(0, maximum);
} else {
files = Array.from(e.target.files);
}
// 파일리스트들 중 하나의 파일씩 받아오는 함수
const readAndPreview = (file: File) => {
// gif를 제외하고 jpeg, jpg, png파일만 받아옵니다.
if (/\.(jpe?g|png)$/i.test(file.name)) {
// fileReader 생성자를 사용합니다.
const reader = new FileReader();
//하나씩 받아온 파일을 읽힙니다.
reader.readAsDataURL(file);
// 읽힌 후 promise를 반환합니다.
return new Promise<void>(resolve => {
// 만약에 이미지 작업이 완료되면 배열 state에 추가 되게 합니다.
reader.onload = () => {
setSavedFile(prev => [...prev, reader.result as string]); // 파일의 컨텐츠
resolve();
};
});
}
};
//만약 유저가 올린 이미지 파일들이 있다면 위의 함수를 하나씩 돌아가면서 실행합니다.
if (files) {
[].forEach.call(files, readAndPreview);
}
}
};
이후, state의 변동값에 따라 리턴 문을 만들었습니다.
{savedFile.map((file, idx) => (
<SwiperSlide key={idx}>
<img src={file} />
<Close
className="absolute top-3 right-3 cursor-pointer"
fill="#a1a1aa"
onClick={() => handleSemiClose(idx)}
/>
</SwiperSlide>
))}
handleSemiClose 핸들러는 이미지의 X버튼을 누를때마다 배열state가 지워지게 끔 했읍니다..
구현 완료! 행복하다! 게시물 수정 구현도 해야되는데 허허 큰일이다.
참고 블로그
image 파일 서버로 전송 및 미리보기
목차
nostalgic-marquis-7af.notion.site
React 이미지 업로드 전 미리보기 기능
이미지를 미리 보여 주는 기능을 만들려고 생각했을 때 아래 두 가지 방법을 생각했다.이미지를 서버에 올리고 반환된 이미지 주소로 이미지를 미리 보여주는 방법이미지를 서버에 올리기 전
velog.io
[Typescript] <React.SetStateAction<type>> useState의 set함수 타입
const [count, setCount] = useState(0) typescript에서 다음과 같은 경우에는 useState를 통해 처음 정의해준 값이 0이기 때문에 count : number라는 것을 쉽게 알 수 있지만 setCount는 어떤 type인지 감이 안잡힌다. 드
w-world.tistory.com
흐흐 리액트 쿼리 언제 정리하지 ㅎㅎ;;