λλ΅μ μΈ κΈ°λ₯ ꡬνμ΄ λͺ¨λ λλ¬μ΅λλ€. ν루μ΄νλμ κΈνκ² λ§λ¬΄λ¦¬νμ. μ 리νμλ©΄,
- todoItemμ μ μ μ λ μ§μ λ°λΌ λ³λμ μνλ‘ κ΄λ¦¬νλ€.
- ν μΌμ μλ£ν λλ§λ€ ν΄λΉ λ μ§μ λ¬λ ₯μ μκΉκ³Ό λ¨μ κ°μκ° μ λ°μ΄νΈλλ€.
1. TodoItem κΈ°λ₯ νμ₯
λ¬λ ₯ κΈ°λ₯ μ μ λ¨Όμ ν¬λ νΌλλ₯Ό ꡬννλ©΄μ λ μ§λ₯Ό μκ°νμ§ μκ³ λ¨μνκ² νλμ μνλ‘λ§ κ΄λ¦¬νλ€. μ΄μ μ νν λ μ§μ μ νν νλ‘νμ λ°λΌ ν¬λλ€μ κ΄λ¦¬νκ³ λ³΄μ¬μ€μΌνλ€. λ§μ κ³ λ―Όμ νμ.
μ²μμ λ°±λ¨ λλΉμμ κ΄λ¦¬νλ€λ©΄ μ΄λ»κ² λμ΄μμμ§λ₯Ό μκ°νλ€ (μλλ μλ²μμ λ°μμμΌ νλ λ°μ΄ν°λ₯Ό νλ‘μ νΈμμ λͺ©λ°μ΄ν°μ²λΌ μ¬μ©νλ κ²μ΄κΈ° λλ¬Έμ). 'ν¬λ' ν μ΄λΈμ νλ λκ³ , userIdμ date 컬λΌμ μΆκ°ν΄μ κ·Έκ±Έ ν΅ν΄ μ 보λ₯Ό ν¨μΉν΄μ€μ§ μμμκΉ?
export interface ITodoItem {
label: string;
id: string;
isDone: boolean;
category: ICategory;
userId:string;
date:string;
}
export const todoState = atom<ITodoItem[]>({
key: 'todo',
default: initialState,
});
κΈ°μ‘΄κ³Ό ν¬κ² λ°κΎΈμ§ μκ³ μΈν°νμ΄μ€μ userIdκ³Ό dateλ§ μΆκ°ν΄μ μ΄λ κ² κ΄λ¦¬λ₯Ό ν΄λ³ΌκΉ.. νλλ κ΅μ₯ν λ§μμ λ€μ§ μμλ€. λͺ¨λ λ μ§μ λͺ¨λ μ μ κ° κ°κ³ μλ ν¬λλ₯Ό νλμ λ°°μ΄λ‘ κ°κ³ μλλ€λ μ μ΄μλ€. μ΄κ±΄ μμ² ν° λ¬Έμ λ€.
ν¬λλ₯Ό κ°μ Έμ¬λλ§λ€ λ°°μ΄μ λͺ¨λ λλ©΄μ ν΄λΉνλ μ μ μ λ μ§ κ°μ κ°κ³ μλ μ λ§ νν°ν΄μ κ°μ Έμ€λ©΄ λ ν λ°, λ κ·Όμ¬ν λ°©λ²μ΄ μμ κ² κ°μλ€. 무μλ³΄λ€ λͺ¨λ μ μ μ λ μ§μ ν¬λλ₯Ό νλμ μν(μν°)μΌλ‘ κ΄λ¦¬νκΈ° λλ¬Έμ κ°μ΄ λ°λλλ§λ€ μ λΆλ€ λ λλ§λλ μν©μ΄ μκΈΈ μ μλ€.
리μ‘νΈ μΏΌλ¦¬λ₯Ό μ¬μ©ν μν©μ΄μλ€λ©΄ μ΄λ»κ² νμκΉ? [userId, date]λ₯Ό 쿼리ν€λ‘ μ¬μ©νμ§ μμμκΉ. κ°κ°μ λ³λμ μνλ‘ μΊμ±νκ³ κ΄λ¦¬νλκ² λ ν¨μ¨μ μ΄λ€. 리μ½μΌμ atomFamily ν¨μκ° μ 곡νλ κΈ°λ₯μ΄λ€.
atomFamily λμ
/**
* @type [selectedDate, selectedProfile]
*/
export type ITodoItemKey = [string, string];
export const todoState = atomFamily<ITodoItem[], ITodoItemKey>({
key: 'todo',
default: [],
});
atomFamily λ λμΌν ννμ atomμ μμ±ν΄μ£Όλ ν©ν 리 ν¨μλ₯Ό μ 곡νλ€. λ¬΄μ¨ λ§μ΄λλ©΄, 맀κ°λ³μλ‘ λ°μμ¨ κ°μ λ°λΌ κ°κ°μ μν°μ μμ±νλλ°, κ·Έκ² λͺ¨λ λκ°μ μΈν°νμ΄μ€λ₯Ό κ°λ κ². λͺ¨λ λ μ§μ μ μ μ ν¬λλ₯Ό νλμ λ°°μ΄λ‘ κ΄λ¦¬νλ κ²μ΄ μλ κ°κ°μ μν°μΌλ‘ λΆλ¦¬νκΈ° μν΄ μ¬μ©νλ€.
맀κ°λ³μλ₯Ό 리μ‘νΈ μΏΌλ¦¬μ 쿼리ν€μ λΉμ·ν λ°©μμΌλ‘ μ¬μ©νκ³ μΆμλ€. μ€νΈλ§ λ°°μ΄μ ννλ‘ μ£Όκ³ @KeyλΌλ λ€μ΄λ°μ μΌλ€. μ§κ΄μ μ΄μ΄μ μ’λ€.
selectorFamilyμμ atomFamily μν κ°μ Έμ€κΈ°
κΈ°μ‘΄ νλ‘μ νΈ κ΅¬μ‘°μμλ FeedItemList.tsxμμ νΉμ μΉ΄ν κ³ λ¦¬μ ν¬λλ€λ§ κ°μ Έμμ λ λλ§νκ³ μλ€. μ΄μ λ νΉμ λ μ§μ νΉμ μ μ μ, νΉμ μΉ΄ν κ³ λ¦¬μ ν¬λλ€μ κ°μ ΈμμΌ νλ€. μλ μ¬μ©νλ selectorFamilyκ° atomFamilyμ κ°μ κ°μ Έμμ κ°κ³΅ν μ μλλ‘ μμ ν΄μ£Όμλ€.
export type ITodoItemSelectorParams = {
todoItemKey: ITodoItemKey;
categoryLabel: string;
};
export const todosByCategory = selectorFamily<
ITodoItem[],
ITodoItemSelectorParams
>({
key: 'todoSelector',
get:
({ todoItemKey, categoryLabel }: ITodoItemSelectorParams) =>
({ get }) =>
get(todoState(todoItemKey)).filter(
(todo) => todo.category.label === categoryLabel,
),
});
atomFamilyμ νλΌλ―Έν°μ μΉ΄ν κ³ λ¦¬ λΌλ²¨μ λ°μμμ μ¬μ©νλ€.
μΈν°νμ΄μ€ λ§λ€κΈ°
export interface ITodoItemSelectorParams {
todoItemKey: ITodoItemKey;
categoryLabel: string;
}
typeμ΄ μλλΌ interfaceλ‘ λ§λ€μ΄ μ¬μ©νλ©΄, μλμ κ°μ μ€λ₯κ° λ°μνλ€.
typeμΌλ‘ λ°κΎΈμ΄μ£ΌκΈ°λ§ νλλ° μ€λ₯κ° ν΄κ²°λμλ€. atomFamily paramμ νμ μ€μμ {[key: string]: SerializableParam} ννμ μΈν°νμ΄μ€λ‘ ꡬννλ €νλ€. μ λ°κ±Έ index signatureλΌκ³ νλλ°, νμ λ³μΉμ μ¬μ©ν΄μΌλ§ μ¬μ©ν μ μλ λ― νλ€.
μ»΄ν¬λνΈμμ μ¬μ©
const selectedDate = useRecoilValue(selectedDateState);
const selectedProfile = useRecoilValue(selectedProfileState);
const todos = useRecoilValue(
todosByCategory({
todoItemKey: [selectedDate, selectedProfile],
categoryLabel: category.label,
}),
);
FeedItemList μ»΄ν¬λνΈμμ μμ κ°μ΄ selectorFamily ν¨μλ₯Ό νΈμΆν΄ μ¬μ©νλ€. dateμ profile μν λν μ μμΌλ‘ κ΄λ¦¬νλ€.
2. λ¬λ ₯ ν¬λ μμ΄μ½ μκΉ μ±μ°κΈ°
λκ² μ¬λ°κ² μμ νλ€. ν¬λλ©μ΄νΈλ§μ μμ΄λ΄ν°ν°λΌκ³ ν μ μλ κΈ°λ₯μ΄λ€.
μλ£ν ν¬λμ μκΉμ΄ λ¬λ ₯μλ νμλλ€. ꡬ체μ μΌλ‘ μ΄λ€μμΌλ‘ νμλλμ§ μ΄ν΄λ΄€μ.
- μλ£λμ§ μμ ν¬λμ κ°μκ° νμλλ€. λͺ¨λ ν¬λκ° μλ£λλ©΄ 체ν¬κ° νμλλ€.
- λ¬λ ₯μ μμ΄μ½μ λκ·ΈλΌλ―Έ λ€κ°λ‘ μ΄λ£¨μ΄μ Έμλλ°, κ° λκ·ΈλΌλ―Έλ§λ€ λ€λ₯Έ μμμ λ£μ μ μλ€.
- μλ£λ ν¬λλ€μ μΉ΄ν κ³ λ¦¬ μ’ λ₯μ λ°λΌ λ€μνκ² νμλ μ μλ€.
- μΉ΄ν κ³ λ¦¬κ° νκ°μ§μΌλλ λ¨μμΌλ‘, λκ°μ§μΌλλ λ°λ°μ©. λ€μ’ λ₯μΌλλ λκ·ΈλΌλ―Έ νλμ© νμλλ€.
- μΈκ°μ§μΌλλ μκΉ νλκ° λμΉΈμ μ°¨μ§νλλ°, μλ£λ ν¬λ μ€ κ°μκ° λ§μ μΉ΄ν κ³ λ¦¬κ° λμΉΈμ΄λ€.
TodoIconSvg.tsx
interface TodoIconSvgProps {
colors: string[];
}
const TodoIconSvg = ({ colors }: TodoIconSvgProps) => {
let fill = [colors[0], colors[0], colors[0], colors[0]];
switch (colors.length) {
case 1:
fill = [colors[0], colors[0], colors[0], colors[0]];
break;
case 2:
fill = [colors[0], colors[1], colors[1], colors[0]];
break;
case 3:
fill = [colors[0], colors[0], colors[1], colors[2]];
break;
case 4:
fill = [colors[0], colors[1], colors[2], colors[3]];
break;
default:
fill = ['#DBDDDF', '#DBDDDF', '#DBDDDF', '#DBDDDF'];
break;
}
return (
<svg
width="21"
height="21"
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="6.46154" cy="6.46154" r="6.46154" fill={fill[0]} fillOpacity={'0.9'}/>
<circle cx="6.46154" cy="14.5387" r="6.46154" fill={fill[1]} fillOpacity={'0.9'}/>
<circle cx="14.5387" cy="14.5387" r="6.46154" fill={fill[2]} fillOpacity={'0.9'}/>
<circle cx="14.5387" cy="6.46154" r="6.46154" fill={fill[3]} fillOpacity={'0.9'}/>
</svg>
);
};
export default TodoIconSvg;
λ€κ°μ μμΌλ‘ μ΄λ£¨μ΄μ§ svg μ»΄ν¬λνΈλ₯Ό νλ λ§λ€μλ€.colors λ°°μ΄μ μ»΄ν¬λνΈ λ°μμ λ§λ€μ΄μ 쀬μ. μμμ μΈκΈνλ 쑰건μ λ§κ² μμ μ±μμ€λ€. ν¬λͺ λλ₯Ό 0.9λ‘ νλλ μ€μ ν¬λλ©μ΄νΈλ μμ ν λκ°λ€..! μ€μν건 μλμ§λ§ κ΄ν κΈ°λΆμ’μ ν¬μΈνΈ.
useTodoInfo.ts
const useTodoInfo = (date: string, userId: string) => {
const todos = useRecoilValue(todoState([date, userId]));
const colors = todos
.filter((todo) => todo.isDone === true)
.map((done) => done.category.color);
const colorSet = new Set(getSortedArray(colors));
const colorSetArr = Array.from(colorSet);
const count = todos.filter((todo) => !todo.isDone).length;
const isDone = count === 0 && todos.length !== 0;
return { count, colorSetArr, isDone };
};
export default useTodoInfo;
λ¬λ ₯ μμ΄ν λ λλ§μ νμν κ°λ€μ μ 곡νλ λ‘μ§μ΄λ€. μλ£ν ν¬λλ€μ μκΉμ κ°κ³΅ν΄μ 리ν΄νλ€. λΉλμλ‘ μ λ ¬ν νμ setμΌλ‘ μ€λ³΅μ μ κ±°νλλ‘ νλ€.
CalenderItem.tsx
interface CalenderItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
date: string;
userId: string;
isSelected: boolean;
}
const CalenderItem = ({
date,
userId,
isSelected,
...props
}: CalenderItemProps) => {
const { count, colorSetArr, isDone } = useTodoInfo(date, userId);
return (
<>
<button {...props}>
<span className="count">{count !== 0 && count}</span>
<TodoIconSvg colors={colorSetArr} />
{isDone && <CheckIcon className="check" />}
</button>
<span className="date">{dayjs(date).date()}</span>
</>
);
};
μ΄λ κ² UIμ»΄ν¬λνΈλ‘ λΆλ¬μ κ°λ μ±μκ² ννν μ μλ€.
λ μ΄μ§ μ²μ°λ―γ