πŸ§‘‍πŸ’» μ§§μ€ν˜Έν‘/React

[React] λ‹€μ‹œ λ§Œλ“œλŠ” todo list (νˆ¬λ‘λ©”μ΄νŠΈ 클둠) (2)

ν•œκ·œμ§„ 2022. 11. 3. 03:03

쀑간고사 κΈ°κ°„ 잘 λ³΄λ‚΄μ…¨λ‚˜μš”.. μ „ λͺ»λ³΄λƒˆμŠ΅λ‹ˆλ‹€. 흑흑
λ‹€μŒ ν•™κΈ°μ—” 무쑰건 κΈ°μˆ™μ‚¬μ— λ“€μ–΄κ°€κ² λ‹€λŠ” 생각을 λ‹€μ‹œ ν•œλ²ˆ ν•΄λ³΄λ©΄μ„œ, 이번주 WIL을 적고 μžˆμŠ΅λ‹ˆλ‹€.

쀑간 νœ΄μ‹κΈ°κ°„λ™μ•ˆ μ§œμž˜μ§œμž˜ν•˜κ²Œ ν”„λ‘œμ νŠΈλ₯Ό κ±΄λ“œλ €λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

1. 이전에 μžˆμ—ˆλ˜, λ°”ν…€μ‹œνŠΈ κ΄€λ ¨ 문제

각 TodoItemλ§ˆλ‹€ 있던 λ°”ν…€μ‹œνŠΈλ₯Ό 더 μƒμœ„λ‘œ μ˜¬λ €μ„œ κ΄€λ¦¬ν•˜λ„λ‘ ν–ˆλ‹€. 이젠 μ–΄λ–€ νˆ¬λ‘μ—μ„œ μ—° λ°”ν…€μ‹œνŠΈμΈμ§€ λ°”λ‘œ μ•Œ 수 μ—†κΈ° λ•Œλ¬Έμ—, μ„ νƒλœ νˆ¬λ‘λ₯Ό μ „μ—­μƒνƒœλ‘œ κ΄€λ¦¬ν•˜λ„λ‘ ν–ˆλ‹€.

 

useBottomSheet.tsx

import { useRecoilState } from 'recoil';
import { ITodoItem } from '../interfaces/ITodoItem';
import { bottomSheetState } from '../stores/bottomSheet';

function useBottomSheet(initial: boolean) {
  const [bottomSheet, setBottomSheet] = useRecoilState(bottomSheetState);
  const { isOpen, selectedItem } = bottomSheet;

  function onOpen(item: ITodoItem) {
    setBottomSheet({ selectedItem: item, isOpen: true });
  }
  function onDismiss() {
    setBottomSheet({ selectedItem: null, isOpen: false });
  }

  return { isOpen, selectedItem, onOpen, onDismiss };
}

export default useBottomSheet;

μ—΄λ¦Ό/λ‹«νž˜ μƒνƒœμ™€ μ„ νƒλœ νˆ¬λ‘λ₯Ό 리코일 atom으둜 λ§Œλ“€μ–΄ κ΄€λ¦¬ν•œλ‹€. 간단간단쓰.

 

2. Todo κ΄€λ ¨ 둜직 좔상화

UIμ»΄ν¬λ„ŒνŠΈμ—μ„œ μ΅œλŒ€ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ λΆ„λ¦¬ν•˜κ³ , μ»€μŠ€ν…€ν›…μ„ μ΄μš©ν•΄ λ„˜κ²¨λ°›μ•„ μ‚¬μš©ν•œλ‹€.
μ €λ²ˆμ£Ό μŠ€ν„°λ”” μ‹œκ°„μ— λ³΄μ—¬λ“œλ Έλ˜ λ‚΄μš©μ΄μ£ ??

const useTodo = () => {
  const [todo, setTodo] = useRecoilState(todoState);

  const insertTodo = (inputValue: string, category: ICategory) => {
    if (inputValue) {
      const newTodo = {
        label: inputValue,
        id: uuid(),
        isDone: false,
        category: category,
      };
      setTodo((prev) => [...prev, newTodo]);
    }
  };

  const editTodo = (inputValue: string, id: string) => {
    if (inputValue) {
      const index = todo.findIndex((v) => v.id === id);
      const temp = [...todo];
      temp[index] = { ...temp[index], label: inputValue };
      setTodo(temp);
    }
  };

  const toggleTodo = (id: string) => {
    const index = todo.findIndex((v) => v.id === id);
    const temp = [...todo];
    temp[index] = { ...temp[index], isDone: !temp[index].isDone };
    setTodo(temp);
  };

  const deleteTodo = (id: string) => {
    setTodo(todo.filter((v) => v.id !== id));
  };

  return { insertTodo, editTodo, toggleTodo, deleteTodo };
};

export default useTodo;


νˆ¬λ‘ μ‚­μ œν•˜κΈ° λ“±μ˜ ν•¨μˆ˜λ₯Ό μ»€μŠ€ν…€ν›…μ„ 톡해 λ°›μ•„μ˜¬ 수 있고, νˆ¬λ‘ μƒνƒœ(μ‚­μ œν•  νˆ¬λ‘μ˜ id) λ˜ν•œ μ „μ—­μœΌλ‘œ κ΄€λ¦¬λ˜κΈ° λ•Œλ¬Έμ— ν”„λ‘œμ νŠΈ μ–΄λ””μ—μ„œλΌλ„ μ‚¬μš©ν•  수 μžˆλ‹€. νˆ¬λ‘ μ»΄ν¬λ„ŒνŠΈμ™€ λ–¨μ–΄μ ΈμžˆλŠ” λ°”ν…€μ‹œνŠΈ μ»΄ν¬λ„ŒνŠΈμ—μ„œ 받아와 μ‚¬μš©ν•˜κΈ°μ— μ’‹λ‹€.

 

3. 친ꡬ λͺ©λ‘ (λ¬Έμžμ—΄μ—μ„œ 이λͺ¨μ§€ 닀루기)

λ‚˜λ¦„ 고민을 많이 ν–ˆλ‹€.

 

const initialState: IFriend[] = [
  {
    userId: 'user1',
    name: 'πŸ¬κ·œμ§„',
    profileImage: '',
    statusMessage: 'μ–΄μ©Œλ‹€ 갓생',
  },
  {
    userId: 'user2',
    name: 'μœ μ§„',
    profileImage: 'https://i.ibb.co/MgmDcz1/1021805078815985664.webp',
    statusMessage: 'μ£Όλ‹ˆμ–΄ PM이 되기 μœ„ν•œ λ…Έλ ₯',
  },
  {
    userId: 'user3',
    name: 'κΉ€ν…ŒμŠ€νŠΈ',
    profileImage: '',
    statusMessage: 'user3',
  }
];



친ꡬ κ°μ²΄λŠ” 이런 ν˜•μ‹μ΄λ‹€. ν”„λ‘œν•„ 이미지 λΆ€λΆ„μ—μ„œ μ‹œκ°„μ„ 많이 썼닀.
ν”„λ‘œν•„μ΄λ―Έμ§€κ°€ μžˆμ„ λ•ŒλŠ” ν•΄λ‹Ή 이미지λ₯Ό λΆˆλŸ¬μ™€ λ°±κ·ΈλΌμš΄λ“œλ‘œ 보여주고, μ—†μ„λ•ŒλŠ” μ΄λ¦„μ˜ μ²«κΈ€μžλ₯Ό 보여쀀닀.

 

FriendsIcon.tsx

const ProfileImage = styled.div<{ friend: IFriend; selected: boolean }>`
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 50px;
  background-color: ${({ theme }) => theme.palette.mono.gray_f5};
  ${({ friend }) => getImageStyle(friend)}
  border: ${({ selected, theme }) =>
    selected && `2px solid ${theme.palette.mono.gray_44}`}
`;

const getImageStyle = (friend: IFriend) => {
  switch (friend.profileImage.length) {
    case 0:
      return css`
        ::after {
          content: '${sliceFirstWord(friend.name)}';
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translate3d(-50%, -50%, 0);
          font-weight: 500;
        }
      `;
    default:
      return css`
        background-image: url(${friend.profileImage});
        background-size: 40px 40px;
        background-position: center;
      `;
  }
};


ν”„λ‘œν•„ 이미지에 κ΄€ν•œ λΆ€λΆ„λ§Œ λ”°λ‘œ λΉΌμ„œ λ„£μ–΄μ£Όμ—ˆλ‹€. μ΄λ¦„μ˜ μ²«κΈ€μžλ₯Ό 보여쀄 λ•ŒλŠ” after 가상 μ„ νƒμžλ₯Ό μ΄μš©ν•œλ‹€.


이 κ³Όμ •μ—μ„œ λ¬Έμ œκ°€ ν•˜λ‚˜ μžˆμ—ˆλ‹€. νˆ¬λ‘λ©”μ΄νŠΈμ— μ΄λ¦„μ˜ μ²«κΈ€μžλ‘œ 이λͺ¨μ§€λ₯Ό μ“°λŠ” κ²½μš°κ°€ λ§Žμ€λ°, ν”„λ‘œν•„ 이미지에 이λͺ¨μ§€κ°€ λ“€μ–΄κ°€λ©΄ κ½€ μ΄μ˜λ‹€. λ¬΄μž‘μ • λ¬Έμžμ—΄μ˜ 첫번째 인덱슀λ₯Ό κ°€μ Έμ˜€λ‹ˆκΉŒ 이λͺ¨μ§€κ°€ κΉ¨μ‘Œλ‹€. 이λͺ¨μ§€λŠ” μœ λ‹ˆμ½”λ“œλ₯Ό μ“°λŠ”λ°, λ¬Έμžμ—΄λ‘œ μ²˜λ¦¬λ λ•Œ 길이가 막 2,3 μ΄λ ‡κ²Œ λ‚˜μ˜€λ”λΌ.


sliceFirstWord

const sliceFirstWord = (string: string) => {
  // https://avengersrhydon1121.tistory.com/268
  if (
    /^([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/.test(
      string,
    )
  ) {
    return string.slice(0, 2);
  } else {
    return string[0];
  }
};

받은 λ¬Έμžμ—΄μ΄ 이λͺ¨μ§€λ‘œ μ‹œμž‘ν•˜λ©΄ 0,1 인덱슀λ₯Ό λ¦¬ν„΄ν•˜κ³ , μ•„λ‹ˆλ©΄ 0번째 인덱슀만 λ¦¬ν„΄ν•˜λ„λ‘ ν–ˆλ‹€. μ •κ·œμ‹μ„ μ‚¬μš©ν–ˆλŠ”λ°, /^ / λ₯Ό 톡해 μ–΄λ– ν•œ 문자둜 μ‹œμž‘ν•˜λŠ”μ§€ μ•Œ 수 μžˆλ‹€.

μ§ 

 

4. λ Œλ”λ§ μ΅œμ ν™”?