๐Ÿง‘‍๐Ÿ’ป ์งง์€ํ˜ธํก/React

[React] ๋‹ค์‹œ ๋งŒ๋“œ๋Š” React-Messenger (selectorFamily, FaCC)

ํ•œ๊ทœ์ง„ 2022. 11. 4. 23:16

์„ธ์˜ค์Šค ๋ฆฌ์•กํŠธ ๋ฉ”์‹ ์ € ๊ณผ์ œ๋ฅผ ์ค€๋น„ํ•˜๋ฉด์„œ 6๊ฐœ์›” ์ „์— ์ œ์ถœํ–ˆ๋˜ ๊ณผ์ œ๋ฅผ ๋‹ค์‹œ ๋ณด์•˜๋‹ค. ์‹œ๊ฐ„์— ์ซ“๊ฒจ๊ฐ€๋ฉด์„œ ํ•˜๋‹ค๋ณด๋‹ˆ, ๊ต‰์žฅํžˆ ๋”๋Ÿฌ์šด ๋ถ€๋ถ„์ด ๊ฝค ์žˆ๋‹ค. ์ด๋Ÿฐ์ €๋Ÿฐ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์ด๋Ÿฐ์ €๋Ÿฐ ์˜ต์…˜๊ณผ props๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๊ณ , ๊ทธ๋ ‡๊ฒŒ ๋ชฌ์Šคํ„ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋˜์–ด๊ฐ„๋‹ค. ์Šคํƒ€์ผ๋ง์„ ํ• ๋•Œ๋„ ์›ํ•˜๋Š” ๋Œ€๋กœ ๋ ๋•Œ๊นŒ์ง€ ์ˆ˜์ •์— ์ˆ˜์ •์„ ๊ฑฐ๋“ญํ•˜๋‹ค๋ณด๋‹ˆ ๋งค์šฐ ๋ณต์žกํ•ด์กŒ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฒˆ ํฌ์ŠคํŒ…์€ 6๊ฐœ์›” ์ „ ๊ณผ์ œ๋ฌผ ๋ฆฌํŒฉํ† ๋ง์˜ ๊ธฐ๋ก!

 

  • Recoil selectorFamily
  • ์ปค์Šคํ…€ํ›…์„ ์ด์šฉํ•ด ๋กœ์ง ์ถ”์ƒํ™”
  • Funtion as Child Component
  • ํƒ€์ž… ๊ฐ€๋“œ

์ธ์ œ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ,

 

 

1. ๋„ค์ด๋ฐ

์ œ์ผ ์‹œ๊ธ‰ํ–ˆ๋‹ค. ๊ณ„์† ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ๊ธ‰๊ธ‰ํ•˜๊ฒŒ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด๊ฐ”๋‹ค. Room, chats, chatList ๋“ฑ์˜ ์ด๋ฆ„์ด ๋ณ„๋‹ค๋ฅธ ๊ธฐ์ค€ ์—†์ด ์‚ฌ์šฉ๋˜๋Š”๊ฒŒ ์Šค์Šค๋กœ๋„ ๊ฐ๋‹นํ•˜๊ธฐ ํž˜๋“ค ์ •๋„์˜€๋‹ค. ๊ฐœ์ธํ”„๋กœ์ ํŠธ์ž„์—๋„ ๋„ค์ด๋ฐ์€ ๊ฝค ์ค‘์š”ํ•˜๋‹ค..!

 

๐Ÿ’ฉ

์œ„ ์‚ฌ์ง„์ด ๋ฐ”๋กœ ๊ทธ ๋‹จ์ ์ธ ์˜ˆ๋‹ค. atom์˜ ์ด๋ฆ„์€ 'chatState'์ธ๋ฐ key๋Š” 'chats'. ์‹ฌ์ง€์–ด ํƒ€์ž…์€ 'RoomType'์˜ ๋ฐฐ์—ด์ด๋‹ค. ๋ญ ์ด๋Ÿฐ ์“ฐ๋ ˆ๊ธฐ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ์ˆ˜๊ฐ€. ์ตœ๋Œ€ํ•œ ๋ช…ํ™•ํžˆ ๊ธฐ์ค€์„ ์žก๊ณ  ํƒ€์ž…๊ณผ ์ƒํƒœ๋“ค์˜ ์ด๋ฆ„๋ถ€ํ„ฐ ์ง€์—ˆ๋‹ค.

 

export interface IChat {
  userId: number;
  content: string;
  date: string;
  like: boolean;
  chatId: string;
}
export interface IChatting {
  chattingId: number;
  userIdList: number[];
  chatList: IChat[];
}

export interface IUser {
  userId: number;
  userName: string;
  profileImage: string;
  statusMessage: string;
}

์ฑ— ํ•˜๋‚˜ํ•˜๋‚˜๋Š” IChat, ์ฑ„ํŒ…๋ฐฉ ๊ด€๋ จ ํƒ€์ž…์€ IChatting.

Chatting์˜ chatList์—๋Š” IChatํ˜•ํƒœ์˜ ๋ฐฐ์—ด์ด ๋“ค์–ด๊ฐ„๋‹ค.

 

import chattingsData from '../mocks/chattingsData.json';
import userData from '../mocks/userData.json';
import friendsData from '../mocks/friendsData.json';

export const chattingsState = atom<IChatting[]>({
  key: 'chattings',
  default: chattingsData.chattings,
});

export const userState = atom<IUser>({
  key: 'user',
  default: userData.users[0],
});

export const friendsState = atom<IUser[]>({
  key: 'friends',
  default: friendsData.users,
});

๊ธฐ๋ณธ atom์—๋Š” key + State ํ˜•์‹์œผ๋กœ ์ด๋ฆ„์„ ๋ถ™์ธ๋‹ค. ๋‚ด ์ •๋ณด(user)์™€ ์นœ๊ตฌ์ •๋ณด(friends)๋ฅผ ๋ถ„๋ฆฌํ•ด์ฃผ์—ˆ๋‹ค.

 

์ฑ„ํŒ…๋ชฉ๋ก์€ Chattings, ๊ฐ ์ฑ„ํŒ…๋ฐฉ๋“ค์€ Room์ด๋ผ๋Š” ๋„ค์ด๋ฐ. Chattings๋ฅผ map์œผ๋กœ ๋Œ๋ ค์„œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ Œ๋”๋งํ•ด์ฃผ๋Š”๋ฐ, map์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ chatting์ด๋ผ๋Š” ์ด๋ฆ„์„ ์‚ฌ์šฉํ•ด์ฃผ์—ˆ๋‹ค. Room๊ณผ chatting ๋‘ ์ด๋ฆ„์„ ๋ชจ๋‘ ์“ฐ๋Š” ๊ฒƒ์— ๋Œ€ํ•ด์„œ ๊ณ ๋ฏผ์„ ๊ฝค ํ–ˆ์—ˆ๋‹ค. Room์€ ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„, chatting์€ ์ƒํƒœ์˜ ์ด๋ฆ„์œผ๋กœ ์ƒ๊ฐํ•˜๊ณ  ์ง€๊ธˆ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ–ˆ๋‹ค.

 

 

2. ๋ฆฌ์ฝ”์ผ ํ™œ์šฉํ•˜๊ธฐ (selectorFamily)

๋จผ์ €, ๋ฌธ์„œ์—์„œ๋Š” Selector๋ฅผ ์ด๋ ‡๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค.

 

Selector๋Š” ํŒŒ์ƒ๋œ ์ƒํƒœ(derived state)์˜ ์ผ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ํŒŒ์ƒ๋œ ์ƒํƒœ๋ฅผ ์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ๋“  ์ฃผ์–ด์ง„ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์— ์ „๋‹ฌ๋œ ์ƒํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ผ๊ณ  ํ•˜๋Š”๋ฐ.. ํฌ๊ฒŒ ์™€๋‹ฟ์ง€๋Š” ์•Š๋‹ค. ํŒŒ์ƒ๋œ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค(get)๋Š”๊ฑด, atom์„ ๊ณง๋Œ€๋กœ ๊ฐ€์ ธ์˜ค๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์›ํ•˜๋Š”๋Œ€๋กœ ๊ฐ€๊ณตํ•ด์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ - ์ด๋ผ๊ณ  ์ดํ•ดํ–ˆ๋‹ค. SQL์—์„œ SELECT๋ฌธ์„ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์›ํ•˜๋Š” ๊ฐ’์„ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๋‹ค. ๊ทธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ SQL์˜ aggregateํ•จ์ˆ˜์ฒ˜๋Ÿผ ๊ณ„์‚ฐ๋œ ๊ฐ’์„ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

 

SelectorFamily๋Š” Selector์™€ ๋น„์Šทํ•œ ํŒจํ„ด์ด์ง€๋งŒ, get๊ณผ set์˜ ์ฝœ๋ฐฑ์— ์ธ์ž๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.



atoms/user.ts

export const userStateByUserId = selectorFamily<IUser, number>({
  key: 'userByUserId',
  get:
    (userId: number) =>
    ({ get }) => {
      const user = get(userState);
      const friends = get(friendsState);
      if (userId === 0) {
        return user;
      } else {
        return friends.filter((friend) => friend.userId === userId)[0];
      }
    },
});

export const usersStateByUserIdList = selectorFamily<IUser[], number[]>({
  key: 'usersByUserIdList',
  get:
    (userIdList: number[]) =>
    ({ get }) => {
      return userIdList.map((userId) => get(userStateByUserId(userId)));
    },
});

๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋Ÿฐ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. userId๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ํ•ด๋‹น ์œ ์ €์˜ ์ •๋ณด๋งŒ ๊ฐ€์ ธ์˜ค๋Š” selector์™€ (๊ทธ selector๋ฅผ ์‚ฌ์šฉํ•˜๋Š”) userId์˜ ๋ฐฐ์—ด์„ ์ธ์ž๋กœ ๋ฐ›์•„ ์œ ์ €๋“ค์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” selector์ด๋‹ค.

get ๋‚ด์—์„œ ๋‹ค๋ฅธ atom์˜ ์ƒํƒœ๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚ด ์ •๋ณด์™€ ์นœ๊ตฌ ์ •๋ณด๊ฐ€ ๋‹ค๋ฅธ atom์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๋Š”๋ฐ,ํ•˜๋‚˜์˜ selector์—์„œ ๋ชจ๋‘ ๋ถˆ๋Ÿฌ์™€ ๋ฆฌํ„ดํ•˜๋„๋ก ํ–ˆ๋‹ค.

2.1 selector์—์„œ set ์‚ฌ์šฉํ•˜๊ธฐ

export const chattingStateByChattingId = selectorFamily<IChatting, number>({
  key: 'chattingByChattingId',
  get:
    (chattingId: number) =>
    ({ get }) =>
      get(chattingsState).filter(
        (chatting) => chatting.chattingId === chattingId,
      )[0],
  set:
    (chattingId: number) =>
    ({ set }, newChatting) => {
      set(chattingsState, (prev) =>
        prev.map((chatting) =>
          chatting.chattingId === chattingId
            ? (newChatting as IChatting)
            : chatting,
        ),
      );
    },
});

get๋งŒ ์žˆ์„ ๋• readonlyํ•œ ํ•จ์ˆ˜๋งŒ ์ œ๊ณตํ•˜์ง€๋งŒ set์„ ํ™œ์šฉํ•œ๋‹ค๋ฉด selector์—์„œ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋„ ์“ธ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ƒฅ useRecoilState๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ์ฒ˜๋Ÿผ. ์ฑ—์„ ๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ๋„ selectorFamily ์•ˆ์—์„œ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค. ํ•ด๋‹นํ•˜๋Š” ์ฑ„ํŒ…๋ฐฉ์˜ id์™€ newChatting์„ ์ธ์ž๋กœ ๋ณด๋‚ธ๋‹ค. ์–ด๋–ค ์‹์œผ๋กœ ๋ณด๋‚ด์•ผ ํ•˜์ง€??

 

 

https://recoiljs.org/docs/api-reference/utils/selectorFamily/

set ์†์„ฑ์—๋Š” ์œ„์˜ ํƒ€์ž…์˜ ํ•จ์ˆ˜๊ฐ€ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค. ์ •๋ฆฌํ•˜๋ฉด P => ({set}, newValue) => void; ์˜ ํ˜•ํƒœ์ด๋‹ค. ์„ธํ„ฐ์™€ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ธ์ž๋กœ ๋ณด๋‚ธ๋‹ค. ์œ„์— ์ค‘์ฒฉ๋œ ํ•จ์ˆ˜์˜ ์ธ์ž๋Š” selectorFamily์˜ ์ธ์ž์ด๋‹ค.


useChat.tsx

const useChat = (chattingId: number) => {
  const currentId = useRecoilValue(currentState);
  const [chatting, setChatting] = useRecoilState(
    chattingStateByChattingId(chattingId),
  );

  const sendChat = (value: string) => {
    if (value.length !== 0 && value.replace(/ /g, '').length !== 0) {
      const newChat: IChat = {
        userId: currentId,
        content: value,
        date: dayjs().format(),
        like: false,
        chatId: uuid(),
      };

      setChatting({ ...chatting, chatList: [...chatting.chatList, newChat] });
    }
  };

  return { sendChat };
};

export default useChat;

์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

useRecoilState(chattingStateByChattingId(chattingId));

 

 

3. ์ปค์Šคํ…€ํ›…์œผ๋กœ ๋กœ์ง ๋ถ„๋ฆฌ

์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ UI ๋ Œ๋”๋ง์„ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ตœ๋Œ€ํ•œ ๋ถ„๋ฆฌํ•˜๋Š”๋ฐ์— ๊ด€์‹ฌ์ด ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ์˜ ์ค‘์š”์„ฑ์„ ๋‹ค๋ฃจ๋Š” ์•„ํ‹ฐํด๋“ค์ด ๊ฝค ๋งŽ์•˜๋‹ค. ์กฐ๊ธˆ ๋” ์„ ์–ธ์ ์ธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ค ์ผ์„ ํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ ์ˆ˜ ์žˆ๊ณ , ๋ณ€๊ฒฝ์ด ์žฆ์€ UI ์ปดํฌ๋„ŒํŠธ์™€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋กœ์ง์„ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์„œ ๋ณ€๊ฒฝ์— ์šฉ์ดํ•œ ์ข‹์€ ์ฝ”๋“œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค. ๋Œ€์ฒด๋กœ ์ด๋Ÿฐ ์ด์•ผ๊ธฐ๋ฅผ ์ ์–ด๋†“์•˜๋‹ค.

๋ฐ”๋กœ ์œ„์—์„œ ์ž‘์„ฑํ–ˆ๋˜ useChat์ด ๊ทธ ๋ถ„๋ฆฌ๋œ ๋กœ์ง์ด๋‹ค.

RoomFooter.tsx

const RoomFooter = ({ chattingId }: { chattingId: number }) => {
  const inputRef = useRef<HTMLTextAreaElement>(null);
  const { value, onChange, resetValue } = useInput('');
  const { sendChat } = useChat(chattingId);

  const focusInput = () => inputRef.current?.focus();

  useEffect(() => {
    focusInput();
  }, []);

  const handleSendChat = () => {
    sendChat(value);
    resetValue();
    focusInput();
  };

  const onEnter = (e: KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (e.nativeEvent.isComposing === false) {
        e.preventDefault();
        handleSendChat();
      }
    }
  };

  return (
    <Wrapper>
      <div>
        <MessageInput
          value={value}
          onChange={onChange}
          ref={inputRef}
          onKeyDown={onEnter}
        />
        <SendButton
          onClick={handleSendChat}
          disabled={value.length === 0 ? true : false}
        >
          ์ „์†ก
        </SendButton>
      </div>
    </Wrapper>
  );
};

export default RoomFooter;

์•„์ง ์ข‹์€ ์ฝ”๋“œ๋ผ๊ณ  ๋งํ•˜๊ธด ๋ญํ•˜์ง€๋งŒ, UI๋ฅผ ๋‹ค๋ฃจ๋Š” ๋กœ์ง๋งŒ ๋‚จ์•„์žˆ๊ณ  ์ƒํƒœ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋กœ์ง์€ ์ „๋ถ€ ์ปค์Šคํ…€ํ›…์œผ๋กœ ์ถ”์ƒํ™”๋˜์–ด ์žˆ๋„๋ก ํ–ˆ๋‹ค.
์ด์™ธ์—๋„ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋‹ค๋ฃจ๋Š” ๋กœ์ง ๋“ฑ์„ ์ปค์Šคํ…€ํ›…์œผ๋กœ ๋นผ๋ฒ„๋ฆฌ๋ฉด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

3.1 ๋ฆฌ๋ Œ๋”๋ง ๋ฌธ์ œ?

์›๋ž˜๋Š” ์ปค์Šคํ…€ํ›…์„ ์ด์šฉํ•ด ๊ฐ ํ•˜๋‚˜์˜ atom์„ ์ œ์–ดํ•˜๋Š” ํ•จ์ˆ˜๋“ค์„ ๋ชจ์•„๋†“๊ณ  ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ถˆ๋Ÿฌ์™€ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค. ๊ทธ ์ปดํฌ๋„ŒํŠธ์—์„  ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ํ›…์—์„œ ๊ตฌ๋…ํ•˜๊ณ ์žˆ๋Š” ๋‹ค๋ฅธ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋ ๋•Œ๋งˆ๋‹ค ๋ถˆํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ๋„ ๋ฆฌ๋ Œ๋”๋ง๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค.

 

์˜ˆ๋ฅผ๋“ค์–ด chatting atom์— ์žˆ๋Š” userIdList๋กœ user ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š”, chattings์™€ friends ๋ชจ๋‘๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ–ˆ๋‹ค. user ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง์ด๊ธฐ ๋•Œ๋ฌธ์— useUsers๋ผ๋Š” ํ›…์— ๋งŒ๋“ค๊ณ  ๋ฐ˜ํ™˜ํ–ˆ์—ˆ๋‹ค. ๋ฌธ์ œ๋Š” ๋‹จ์ˆœํžˆ users ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ useUsers ํ›…์„ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒ๊ฒผ๋‹ค. ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” chattings ์ƒํƒœ์— ๊ด€์‹ฌ์ด ์—†์ง€๋งŒ, useUsers ํ›…์—์„œ chattings๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋ ๋•Œ๋งˆ๋‹ค ์“ธ๋ฐ์—†๋Š” ๋ฆฌ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ฆฌ์ฝ”์ผ์˜ selectorFamily๋ฅผ ๋” ์ ๊ทน์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ฐ๊ฐ์˜ ๋กœ์ง์„ selector ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋ฉด์„œ ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์„ ๋ง‰์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

 

4. Function as Child Component

์ฑ„ํŒ…๋ชฉ๋ก๊ณผ ์นœ๊ตฌ๋ชฉ๋ก์˜ item์„ ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค. ์ €๋ฒˆ ๊ณผ์ œ์—๋Š” ListItem ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” RoomListItem, FriendsListItem ๋‘ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ–ˆ๋‹ค. ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•˜๋‚˜ ๋‘๊ณ  ์ƒํƒœ๋ฅผ ๊ฐ€๊ณตํ•ด์„œ props์œผ๋กœ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ๋‹ค (๋™์ผํ•œ ListItemProps ํƒ€์ž…์˜ props๋ฅผ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ–ˆ๋Š”๋ฐ, ๊ฐ ํŽ˜์ด์ง€์—์„œ ๊ฐ–๊ณ  ์žˆ๋Š” ์ƒํƒœ๋Š” users์™€ chattings๋กœ ๋‹ค๋ฅด๋‹ค). ๋˜๋ ค ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ฝ”๋“œ๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด์„œ ์˜คํžˆ๋ ค ๋ถˆํŽธํ•ด์ง€๋Š” ๊ฐ์ด ์žˆ์—ˆ๋‹ค.

 

๊ธฐ์กด ๊ณผ์ œ ์ฝ”๋“œ. ๋ฒˆ๊ฑฐ๋กญ๊ฒŒ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ ํƒ€๊ณ  ํƒ€๊ณ  ๋„˜์–ด๊ฐ€์•ผ ํ•˜๋Š” ๋А๋‚Œ

์ด๋ฒˆ ๊ณผ์ œ์—์„œ๋Š” ๊ฐ™์€ ํŒŒ์ผ ๋‚ด์—์„œ ์ž‘์„ฑํ•˜๋˜ ListItem ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•ด๋ณด์•˜๋‹ค. Function as Child Component ๋ผ๋Š” ํŒจํ„ด์„ ์ ์šฉํ–ˆ๋‹ค. Headless ์ปดํฌ๋„ŒํŠธ์˜ ์ผ์ข…์œผ๋กœ, ์ƒํƒœ๋Š” ์ œ์–ดํ•˜์ง€๋งŒ ์Šคํƒ€์ผ๋ง์€ ๋‹ด๋‹นํ•˜์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งํ•œ๋‹ค.

Friends.tsx

const Friends = () => {
  const friends = useRecoilValue(friendsState);
  const me = useRecoilValue(userState);

  return (
    <Wrapper>
      {/* ...์ƒ๋žต */}
      <MyProfile>
        <Squircle imageUrl={me.profileImage} selected={false} size={55} />
        <div>{me.userName}</div>
      </MyProfile>
      <Divider />
      <SubHeading>์นœ๊ตฌ {friends.length}</SubHeading>
      {friends.map((friend) => (
        <FriendListHeadless friend={friend} key={friend.userId}>
          {({ friend, handleClickListItem }) => (
            <ListItem data={friend} handleClickListItem={handleClickListItem} />
          )}
        </FriendListHeadless>
      ))}
    </Wrapper>
  );
};

export default Friends;

friends ๋ฐฐ์—ด์„ map์œผ๋กœ ๋Œ๋ ค ํ•˜๋‚˜์”ฉ ์•„์ดํ…œ์„ ๋ Œ๋”๋งํ•œ๋‹ค. ํŠน์ดํ•œ๊ฑด ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋ชจ์–‘์ด๋‹ค.

 

<FriendListHeadless /> ๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ child์—๋Š” ๋˜๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ(ListItem)์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค. ๊ทธ ํ•จ์ˆ˜๋Š” friend ๊ฐ์ฒด์™€ ํด๋ฆญ์ด๋ฒคํŠธ ์‹œ ์‹คํ–‰๋  ํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ListItem์˜ props์œผ๋กœ ์ง‘์–ด๋„ฃ๋Š” ํ˜•ํƒœ์ด๋‹ค.


FriendListHeadless

interface FriendListHeadlessProps {
  friend: IUser;
  children: (args: any) => JSX.Element;
}

const FriendListHeadless = ({ friend, children }: FriendListHeadlessProps) => {
  const navigate = useNavigate();
  const chattingId = useRecoilValue(
    chattingStateByUserId(friend.userId),
  ).chattingId;

  return children({
    friend: friend,
    handleClickListItem: () => navigate(`/room/${chattingId}`),
  });
};

Headless ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ chattingId ์ƒํƒœ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค. ํ•ด๋‹น ์ฑ„ํŒ…๋ฐฉ์œผ๋กœ navigate ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  children ์ปดํฌ๋„ŒํŠธ์˜ props์œผ๋กœ ๋„˜๊ฒจ์ค€๋‹ค. ๊ตณ์ด ํ•œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋” ํ•„์š”ํ•œ ์ด์œ ๋Š” chattings๊ฐ€ ์•„๋‹Œ ๊ฐ chatting์˜ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ. map์œผ๋กœ ๋Œ๋ฆฌ๊ณ  ์žˆ๋Š” ๊ทธ ์•ˆ์—์„œ ์ด๋ฃจ์–ด์ ธ์•ผํ–ˆ๋‹ค. ๋ฒˆ๊ฑฐ๋กœ์ด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ ํƒ€๊ณ ํƒ€๊ณ  ๋‚ด๋ ค๊ฐˆ ํ•„์š” ์—†์ด, Headless์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ฅผ child๋กœ ๋‘๊ณ ์žˆ๋Š”์ง€๋„ ํ•œ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋„ ์žฅ์ ์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

์นœ๊ตฌ ๋ชฉ๋ก
์ฑ„ํŒ… ๋ชฉ๋ก

 

4.1 ํƒ€์ž… ๊ฐ€๋“œ

๋˜๋‹ค์‹œ ๋งˆ์ฃผ์นœ ๊ณ ๋ฏผ ํฌ์ธํŠธ. ๋˜‘๊ฐ™์€ ListItem ์ปดํฌ๋„ŒํŠธ์˜ data props๋กœ ๋‚ด๋ ค์˜ค๋Š” ๊ฐ์ฒด๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ฑ„ํŒ…๋ชฉ๋ก์—์„œ๋Š” IChatting ํƒ€์ž…, ์นœ๊ตฌ๋ชฉ๋ก์—์„œ๋Š” IUser ํƒ€์ž…์˜ ๊ฐ์ฒด์ด๋‹ค. ํ”„๋กœํ•„์ด๋ฏธ์ง€์™€ ์‚ฌ์šฉ์ž ์ด๋ฆ„์€ ๊ณตํ†ต์œผ๋กœ ์“ฐ์ง€๋งŒ IChatting ํƒ€์ž…์˜ ๊ฐ์ฒด์—๋Š” userId ๋ฐ–์— ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ friends ์ƒํƒœ์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋“ฑ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฑ„ํŒ…๋ชฉ๋ก์—์„œ๋Š” ์ƒํƒœ๋ฉ”์‹œ์ง€ ๋Œ€์‹  ๋งˆ์ง€๋ง‰ ์ฑ„ํŒ…๊ณผ ๊ทธ ๋‚ ์งœ๊ฐ€ ๋ณด์—ฌ์ ธ์•ผ ํ•œ๋‹ค.

 

const isChattingType = (data: any): data is IChatting => {
  return data.chattingId !== undefined;
};
// const isChatting = isChattingType(data);

<InfoSub>
  {isChatting
    ? data.chatList[data.chatList.length - 1].content
    : data.statusMessage}
</InfoSub>

ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ํ†ตํ•ด ์กฐ๊ฑด๋ฌธ์—์„œ ๊ฐ์ฒด์˜ ํƒ€์ž…์„ ์ขํ˜€ ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋‹ค. ๋‹จ์ˆœํžˆ ์–ด๋–ค ์ธ์ž๋ช…์€ ์–ด๋– ํ•œ ํƒ€์ž…์ด๋‹ค๋ผ๋Š” ๊ฐ’ (is ํ‚ค์›Œ๋“œ)์„ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค. ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ํƒ€์ž…๊ฐ€๋“œ ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ทผ๋ฐ ์ด๋ ‡๊ฒŒ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํ•˜๋‚˜๋กœ ๋งŒ๋“œ๋Š”๊ฒŒ ์ข‹์€ ์ฝ”๋“œ์ผ๊นŒ..?

์ฑ„ํŒ…๋ชฉ๋ก๊ณผ ์œ ์ €๋ชฉ๋ก ๋ชจ๋‘์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋””์ž์ธ๋˜์–ด์žˆ๊ธด ํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ ๋ณ€๊ฒฝ์ด ์ƒ๊ธฐ๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค. ์• ์ดˆ์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„๋ฉ”์ธ๊ณผ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ•ด์„œ { profileImage, mainText, subText, time? } ๋“ฑ์˜ ๋ฒ”์šฉ์ ์ธ props๋ฅผ ๋ฐ›๋„๋ก ํ•˜๋ฉด ๋” ์ข‹์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋˜๋ ค๋‚˜..?

๋ณ€๊ฒฝ์— ์œ ์—ฐํ•œ ์ปดํฌ๋„ŒํŠธ

 

5. ๋‡Œ ๋นผ๊ณ  ์งฐ๋˜ JSX, Styled-components

(์ขŒ) ์ƒˆ๋กœ์ง ๊ฑฐ (์šฐ) ์˜›๋‚ ๊ฑฐ

JSX๋ถ€ํ„ฐ Styled components ํ•จ์ˆ˜๊นŒ์ง€ ๋ญํ•˜๋‚˜ ์ œ๋Œ€๋กœ ๋œ๊ฒŒ ์—†๋Š” ์ฝ”๋“œ์˜€๋‹ค. ๋‚˜์™€ ์ƒ๋Œ€๋ฐฉ์˜ ๋งํ’์„ ์ด ์„œ๋กœ ๋ฐ˜๋Œ€๋ฐฉํ–ฅ์— ์žˆ์–ด์•ผํ–ˆ๊ณ , (ํ”„์‚ฌ) - ๋‚ด์šฉ - ์‹œ๊ฐ„ ์˜ ์ˆœ์„œ๋„ ๋‚˜์™€ ์ƒ๋Œ€๋ฐฉ์ด ๋ฐ˜๋Œ€๋กœ ๋˜์–ด์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•œ๋ช…์ด ์—ฐ์†ํ•ด์„œ ์ฑ„ํŒ…์„ ๋ณด๋‚ผ๋•Œ, ์ฒซ๋ฒˆ์งธ๋กœ ๋ณด๋‚ธ ๋งํ’์„ ์—๋งŒ ๊ผฌ๋ฆฌ๊ฐ€ ๋‹ฌ๋ ค ์žˆ์–ด์•ผ ํ–ˆ๋‹ค. ๊ทธ ๊ผฌ๋ฆฌ์˜ ์Šคํƒ€์ผ๋„ ๋‚˜์™€ ์ƒ๋Œ€๋ฐฉ์ด ๋‹ค๋ฅด๋‹ค.


๊ณ„์†ํ•ด์„œ ๋ณต์žกํ•œ ์กฐ๊ฑด์„ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•ด๊ฐ€๋ฉฐ ๊ฐœ๋ฐœํ–ˆ๋‹ค๋ณด๋‹ˆ ์ ์  ๋”๋Ÿฌ์šด ์ฝ”๋“œ๊ฐ€ ๋˜์–ด๊ฐ”๋‹ค. ์ด๋ฒˆ ๊ณผ์ œ์—์„œ๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ์—ด์‹ฌํžˆ ๊ณ ๋ฏผํ•˜๊ณ  ๊ตฌ์„ฑ์„ ํ•ด๋ณด์•˜๋‹ค.

1. flex-end์™€ flex-start๋ฅผ ์ด์šฉํ•ด ์šฐ์ธก์ •๋ ฌ ์ขŒ์ธก์ •๋ ฌ์„ ๊ฐ„๋‹จํžˆ ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ์š”์†Œ์˜ ์ˆœ์„œ๋Š” flexbox์—์„œ order ์†์„ฑ์„ ์ด์šฉํ•ด css ๋‚ด์—์„œ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

3. ๋งํ’์„  ๊ผฌ๋ฆฌ๊ฐ™์€ ๋ณต์žกํ•œ ์Šคํƒ€์ผ์€ ๋”ฐ๋กœ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด ๋„ฃ์–ด์ค€๋‹ค.

์ด๋ ‡๊ฒŒ 36์ค„์ด์—ˆ๋˜ JSX ์ฝ”๋“œ๋ฅผ 12์ค„๋กœ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. Styled component ๋ถ€๋ถ„์€ ๋”์šฑ ์ค„์–ด๋“ค์—ˆ๋‹ค. ๊ฑฐ์˜ ๋‘๋ฐฐ ๊ฐ€๊นŒ์ด ๊ฐ„๊ฒฐํ•ด์ง„ ์ฝ”๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 




์ด๋ฒˆ์— ๋ฆฌํŒฉํ† ๋งํ•œ ๊ณผ์ œ ์ฝ”๋“œ์™€
์ด์ „์— ํ–ˆ๋˜ ๊ณผ์ œ ์ฝ”๋“œ.

๋˜‘๊ฐ™์€ ๊ธฐ๋Šฅ์ด๋”๋ผ๋„ ๊ณ„์† ๊ณ ๋ฏผํ•ด๊ฐ€๋ฉฐ ๋ฆฌํŒฉํ† ๋งํ•˜๋Š” ๊ฒฝํ—˜์ด ์ค‘์š”ํ•˜๋‹ค.
์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ๊ฒƒ์„ ์–ป์–ด๊ฐ€๋Š” ๊ณผ์ œ์˜€๋‹ค.