백엔드에서 내려주는 응답값을 이용해 채팅방에 표시되는 이름(displayNick)과 이미지(displayPhotos)를 내려주는 작업을 진행했습니다.
복잡한 조건문을 읽기 쉽게 작성하기 위해 ts-pattern과 fxTs를 이용했습니다.
const { roomTypeName, representativeMemberList, roomName, roomProfileImagePath, roomId } =
roomProfile;
const isDM = roomTypeName === DM_ROOM_TYPE;
if (isDM) {
const memberListExcludingMe = representativeMemberList
.filter(({ personaId }) => personaId !== CURRENT_PERSONA_ID);
const isAlone = memberListExcludingMe.length === 0;
const displayNick = isAlone
? '탈퇴 회원'
: memberListExcludingMe.map(({ personaNick }) => personaNick).join(', ');
const displayPhoto = [
isAlone
? getDefaultImageURL({
type: 'PROFILE',
personaId: CURRENT_PERSONA_ID,
})
: getUserProfileImageURL({
profileImageFilepath: memberListExcludingMe[0].profileImageFilepath,
personaId: memberListExcludingMe[0].personaId,
})
];
return {
displayNick,
displayPhoto,
};
}
const displayNick = roomName ?? representativeMemberList.map(({ personaNick }) => personaNick).join(', ');
const displayPhoto = roomProfileImagePath ?
[
getRoomProfileImageURL({
roomId,
extension: roomProfileImagePath,
})
] :
representativeMemberList.map({ profileImageFilepath, personaId }) => getUserProfileImageURL({
profileImageFilepath,
personaId,
}),
);
return {
displayNick,
displayPhoto,
};
}
const getMemberListWithoutMe = (memberList: RepresentativeMember[]) =>
pipe(
memberList,
reject(({ personaId }) => personaId === CURRENT_PERSONA_ID),
);
const isEmptyMemberListWithoutMe = (memberList: RepresentativeMember[]) => pipe(memberList, getMemberListWithoutMe, toArray, isEmpty);
const displayNick = match<RoomProfile, string>(roomProfile)
.with(
{
roomTypeName: DM_ROOM_TYPE,
representativeMemberList: P.when(isEmptyMemberListWithoutMe),
},
() => '탈퇴 회원',
)
.with({ roomTypeName: DM_ROOM_TYPE }, ({ representativeMemberList }) =>
pipe(representativeMemberList, getMemberListWithoutMe, join(', ')),
)
.with({ roomName: P.nonNullable }, ({ roomName }) => roomName)
.otherwise(({ representativeMemberList }) =>
pipe(
representativeMemberList,
map(({ personaNick }) => personaNick),
join(', '),
),
);
const displayPhoto = match<RoomProfile, string[]>(roomProfile)
.with(
{ roomTypeName: DM_ROOM_TYPE },
{
representativeMemberList: P.when(isEmptyMemberListWithoutMe),
},
() => [
getUserProfileImageURL({ personaId: CURRENT_PERSONA_ID }),
],
)
.with({ roomTypeName: DM_ROOM_TYPE }, ({ representativeMemberList }) => {
const theOtherMember = pipe(representativeMemberList, getMemberListWithoutMe, head);
return [
getUserProfileImageURL({
extension: theOtherMember!.profileImageFilepath,
personaId: theOtherMember!.personaId,
}),
];
})
.with({ roomProfileImagePath: P.nonNullable }, ({ roomId, roomProfileImagePath }) => [
getRoomProfileImageURL({
roomId,
extension: roomProfileImagePath,
}),
])
.otherwise(({ representativeMemberList }) =>
pipe(representativeMemberList, map(getUserProfileImageURL), toArray),
);
return {
displayNick,
displayPhoto,
};
백엔드에서 내려주는 채팅방의 데이터에 따라 프론트에서 채팅방의 이름(displayNick)과 이미지(displayPhoto)를 조합해서 채팅방을 보여줘야하는데 로직이 복잡했습니다.
ts-pattern은 선언적 프로그래밍을 도와주는 라이브러리입니다.
패턴 매칭이 뭔지 Claude에게 물어보니 다음과 같이 말해주네요
패턴 매칭은 데이터 구조를 분석하고 그 구조에 따라 다른 동작을 수행하는 프로그래밍 기법입니다. 복잡한 if-else 문을 대체할 수 있어 코드를 더 읽기 쉽고 간결하게 만듭니다.
하지만 TypeScript에는 기본적으로 패턴 매칭 기능이 없기 때문에, ts-pattern을 통해 패턴 매칭의 이점을 활용할 수 있습니다
아래의 예제 코드를 살펴보겠습니다.
import { match, P } from 'ts-pattern';
type Result = { status: 'success'; data: string } | { status: 'error'; error: Error };
const handleResult = (result: Result) =>
match(result)
.with({ status: 'success' }, ({ data }) => console.log(`성공: ${data}`))
.with({ status: 'error' }, ({ error }) => console.error(`에러: ${error.message}`))
.exhaustive();
ts-pattern을 이용 전후의 displayNick 계산 로직들을 살펴보겠습니다
const isDM = roomTypeName === DM_ROOM_TYPE;
if (isDM) {
const memberListExcludingMe = representativeMemberList.filter(
({ personaId }) => personaId !== currentPersonaId,
);
const isAlone = memberListExcludingMe.length === 0;
const displayNick = isAlone
? '탈퇴 회원'
: memberListExcludingMe.map(({ personaNick }) => personaNick).join(', ');
return {
displayNick,
};
}
const displayNick = roomName ??
representativeMemberList.map(({ personaNick }) => personaNick).join(', ');
const getMemberListWithoutMe = (memberList: RepresentativeMember[]) =>
memberList
.filter(({ personaId }) => personaId !== selectedPersonaId)
const isEmptyMemberListWithoutMe = (memberList:RepresentativeMember[]) =>
getMemberListWithoutMe(memberList).length === 0;
const displayNick = match<RoomProfile, string>(roomProfile)
.with(
{
roomTypeName: DM_ROOM_TYPE,
representativeMemberList: P.when(isEmptyMemberListWithoutMe),
},
() => '탈퇴 회원',
)
.with({ roomTypeName: DM_ROOM_TYPE }, ({ representativeMemberList }) =>
getMemberListWithoutMe(representativeMemberList).join(', '))
.with({ roomName: P.nonNullable }, ({ roomName }) => roomName)
.otherwise(({ representativeMemberList }) =>
representativeMemberList
.map(({ personaNick }) => personaNick))
.join(', ')
);
마플의 개발자분들이 개발한 fxTs는 함수형 프로그래밍을 도와주는 라이브러리입니다. 선언적, 함수형으로 프로그래밍을 진행할 수 있으며 iterator를 이용한 지연평가로 성능 또한 높일 수 있습니다. 또한 array에서 제공하는 map, filter, reduce등의 함수를 다른 iterator에서 사용 가능하며 reject와 같이 더 많은 유틸 함수들을 제공합니다.
fxts가 제공하는 pipe와 유틸 함수들을 이용해 위의 코드를 수정해 보았습니다.
const getMemberListWithoutMe = (memberList: RepresentativeMember[]) =>
pipe(
memberList,
reject(({ personaId }) => personaId === CURRENT_PERSONA_ID),
);
const isEmptyMemberListWithoutMe = (memberList: RepresentativeMember[]) =>
pipe(memberList, getMemberListWithoutMe, toArray, isEmpty);
const displayNick = match<RoomProfile, string>(roomProfile)
.with(
{
roomTypeName: DM_ROOM_TYPE,
representativeMemberList: P.when(isEmptyMemberListWithoutMe),
},
() => '탈퇴 회원',
)
.with({ roomTypeName: DM_ROOM_TYPE }, ({ representativeMemberList }) =>
pipe(representativeMemberList, getMemberListWithoutMe, join(', ')),
)
.with({ roomName: P.nonNullable }, ({ roomName }) => roomName)
.otherwise(({ representativeMemberList }) =>
pipe(
representativeMemberList,
map(({ personaNick }) => personaNick),
join(', '),
),
);
오 잘봤읍니다. EffectTs로도 비슷하게 작업 가능하며, 더 엄격하게 작업가능하니 한번 찍어먹어보시는것도 추천드려용
타입매칭까지는 아니여도, 값 기반 매칭은 가능했던걸로 기억해요