일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 프리렌더링
- 리액트로 채팅 기능 구현하기
- useimperativehandle 사용법
- next.js
- express로 채팅 기능 구현하기
- type reference cannot be named error
- useimperativehandle 사용할때
- trigger additional callbacks
- express react
- app.listen
- 코드잇 스프린트 FE 1기
- 회고
- 스프린트 여정 마침
- pure functions
- React
- css module classNames
- dynamic metadata
- 이미지 업로드 과정
- 티스토리챌린지
- useref 타입
- http.createserver
- node.js(express) + ws(websocket) + react
- components as formulas
- inferred type error
- CSS module 장점
- 리스티웨이브
- usemutation custom hook
- 특정 dom node만 노출시키고 싶을 때
- 오블완
- 리액트 19 useref
- Today
- Total
Life is connecting the dots .
이미지 업로드 기능 사용성[UI/UX] 개선 본문
이번주는 기존에 완성한 개인 프로젝트를 다시 점검하는 시간을 가졌다.👩🔧 기능 동작에 이상이 없는지 재점검하고 리팩토링한 코드를 다시 리팩토링도 해보았다. 이 중 사용자가 게시물을 등록하기 위해 작성하는 양식 중 이미지 업로드 기능을 확인했을 때, 기능은 잘 동작하지만 사용성을 조금 더 개선하고자 계획하였다.
현재 이미지 업로드 기능 동작
문제 상황
❏ 업로드를 클릭한 공간에 이미지가 업로드된다.
❏ 이미지 수정은 가능하지만 삭제가 안된다.
❏ 이미지 업로드 완료까지 다소 시간이 걸린다.
challenge
❏ 어떤 업로드 버튼을 클릭하던 왼쪽부터 순서대로 사진 업로드 하기
❏ 업로드한 이미지 삭제 구현하기 (+. 삭제해도 이미지는 좌측정렬을 유지)
❏ 이미지 업로드 중인 동안 대체 UI 보여주기
이미지 업로드 로직을 간단히 설명하자면, [게시물 등록] 컴포넌트에서는 총 3개의 파일 URL 배열(ex. [ "image1.jpg", "image2.jpg", "image3.jpg" ])을 가지고 있고, 각각을 <UploadFile /> 컴포넌트로 뿌려준다. 실제 업로드 기능과 UI에 표시되는 부분은 [UploadFile] 컴포넌트에서 담당하는데, 해당 컴포넌트에서는 뮤테이션을 통해 이미지를 업로드한다. 업로드에 성공하면 그 파일 URL과 props로 전달받은 index를 다시 onChangeFileUrls에 인자로 전달해서 빈 배열에 각 index 위치에 파일 url을 채워준다.
[게시물 등록] 컴포넌트
// src/components/units/boards/BoardRegister.tsx
export default function BoardRegister() {
...
const [fileUrls, setFileUrls] = useState<string[]>(["", "", ""]);
const onChangeFileUrls = (fileUrl: string, index: number) => {
const newFileUrls = [...fileUrls]; // ["", "", ""]
newFileUrls[index] = fileUrl;
setFileUrls(newFileUrls);
};
return (
<div>
{fileUrls.map((filrUrl, index) => (
<UploadFile
key={uuidv4()}
fileUrl={fileUrl}
index={index}
onChangeFileUrls={onChangeFileUrls}
/>
))}
</div>
)
}
[UploadFile] 컴포넌트 - 업로드 기능과 UI에 표시되는 부분
// src/components/commons/Upload.tsx
export default function UploadFile(props: IUploadFileProps) {
...
const [uploadFile] = useMutation<Pick<IMutation, "uploadFile">,IMutationUploadFileArgs>(UPLOAD_FILE);
const onChangeFile = async (event: ChangeEvent<HTMLInputElement>) => {
const file = UploadValidation(event.target.files?.[0]); // UploadValidation => 이미지 검증 로직
try {
const result = await uploadFile({ variables: { file } });
props.onChangeFileUrls(String(result.data?.uploadFile.url), props.index);
} catch (error) {
if (error instanceof Error) {
Modal.error({ content: error.message });
}
}
};
return (
// 업로드 버튼과 이미지가 보이는 UI
);
}
✅ 어떤 업로드 버튼을 클릭하던 왼쪽부터 사진 업로드 하기
지금은 파일 URL을 배열에 넣어줄 때 각 index 위치에 맞게 넣어주었는데, 왼쪽부터 순서대로 들어가게 하기 위해 배열을 순회하면서 값이 없는 위치에 파일 URL을 넣어주는 것으로 수정했다. 대신, 기존에 값이 있다면(이미지가 있다면) 그 이미지를 덮어씌우는 것으로 조건을 추가했다.
// src/components/units/boards/BoardRegister.tsx
const onChangeFileUrls = (fileUrl: string, index: number) => {
const newFileUrls = [...fileUrls];
if (newFileUrls[index]) {
newFileUrls[index] = fileUrl;
} else {
for (let i = 0; i < newFileUrls.length; i++) {
if (!newFileUrls[i]) {
newFileUrls[i] = fileUrl;
break;
}
}
}
setFileUrls(newFileUrls);
};
✅ 업로드한 이미지 삭제 구현하기 (+. 삭제해도 이미지는 좌측정렬을 유지)
► UI
업로드한 이미지에 마우스를 올렸을 때 휴지통 아이콘이 표시되는 화면을 먼저 만들어 주었다. 이미지 컨테이너 크기와 똑같은 사이즈의 컨테이너를 만들고, 처음에는 투명하게 보였다가 마우스를 올리면 불투명하게 변하도록 적용하였다. 휴지통 아이콘은 Ant-design Icon을 사용하여 색상과 크기를 재조정해 주었다.
► 삭제 기능
사용자가 휴지통 아이콘을 클릭했을 때 필터링을 통해 그 인덱스 값을 제외한 배열을 저장해 주면 되겠다는 생각으로 코드를 작성해 보았다. 이때 해당 인덱스는 어떻게 가지고 올 수 있을까? 처음에 이미지를 업로드할 때 props로 전달한 index를 사용해서 삭제 기능 함수의 인자로 넣어주면 된다.
이후 deleteFileByIndex 함수를 props로 [Upload] 컴포넌트까지 전달하고, 휴지통 아이콘에 클릭이벤트가 발생했을 때 해당 함수를 호출하고 인자로 index를 넣어주는 것으로 최종 완성시켰다. 또한 삭제 시 배열 자체의 길이가 줄어드는 문제는 반복문을 사용해서 초기 배열의 길이(3개)가 유지되도록 하여 해결하였다.
중간의 이미지가 삭제되어도 filter를 할 때 다시 처음부터 배열이 생성되므로 삭제 후에 왼쪽부터 이미지가 다시 정렬된다.
// src/components/units/boards/BoardRegister.tsx
const deleteFileByIndex = (index: number) => {
const result = fileUrls.filter((_, fileUrlIndex) => fileUrlIndex !== index);
const fileurls = [];
for (let i = 0; i < fileUrls.length; i++) {
result[i] ? fileurls.push(result[i]) : fileurls.push("");
}
setFileUrls(fileurls);
};
✅ 이미지 업로드 중인 동안 대체 UI 보여주기
이 과정에서 React <Suspense>를 사용해 볼까 생각했었지만 hook을 이용해서 로딩 중인 상태 여부에 따라 다른 UI를 보여주는 게 좀 더 디테일한 조건에서 사용하기에 유용하지 않을까라는 생각에 후자를 선택했다.
UI에서는 삼항연산자를 사용해서 (1) 전달받은 값이 있다면 이미지를 보여주고, (2-1) 값이 비었는데 isLoading이 true이면 로딩 중 이미지를, (2-2) 값이 비었는데 isLoading이 false이면 upload버튼을 보여주었다.
// src/components/commons/Upload.tsx
export default function UploadFile(props: IUploadFileProps) {
const [isLoading, setIsLoading] = useState(false);
const onChangeFile = async (event: ChangeEvent<HTMLInputElement>) => {
const file = UploadValidation(event.target.files?.[0]); // UploadValidation => 이미지 검증 로직
setIsLoading(true); // 추가
try {
const result = await uploadFile({ variables: { file } });
props.onChangeFileUrls(String(result.data?.uploadFile.url), props.index);
setIsLoading(false); // 추가
} catch (error) {
if (error instanceof Error) {
Modal.error({ content: error.message });
setIsLoading(false); // 추가
}
}
};
return (
// 업로드 버튼과 이미지가 보이는 UI
);
}
🛠️ 향후 개선하면 좋을 부분
처음 목표로 세워두었던 부분을 모두 완료하였다. 조금 수정한 것만으로도 사용자 입장에서 훨씬 더 나은 사용성을 높여줄 수 있다. 또한 지금은 사진을 첨부하는 버튼이 총 3개로 되어있지만 다음에는 버튼을 한개만 두고 사용자가 업로드를 했을 때 추가 버튼이 만들어지고, 일정 개수에 도달하면 더 이상 버튼이 보이지 않는 방향으로 개선해도 좋을 것 같다. 그러면 사용자 입장에서 원하는 만큼의 UI만 볼 수 있게 되고, 기능 동작을 예측하기가 쉬울뿐더러 인터랙티브 한 느낌을 줄 수 있을 것 같다.
'Programming > React' 카테고리의 다른 글
Zustand를 선택한 이유 (0) | 2024.11.07 |
---|---|
React 컴포넌트를 순수 함수로 작성하는 이유 (0) | 2024.04.02 |
React Query - 서버 상태 관리 라이브러리 (0) | 2023.08.11 |
React를 사용하는 이유 (0) | 2023.08.04 |
Context API - 리액트에서 데이터를 다루는 방법 (0) | 2023.07.25 |