코딩/Today I Learn

React Js Master #8

코딩쪼렙 2022. 5. 31. 17:24
728x90


지금 작성하는 atom이라던가 모든 것 분리 할 것 임.

1. src >  components 폴더 생성 > toDoList.tsx 파일 이동

2.

const toDoState = atom({
    key:"toDo",
    default: [ ] //빈 배열
})

 

3.

function ToDoList(){

const value = useRecoilValue(toDoList); // 위 아래 각각 atom에 기존 값을 가져오고, 변경하는 함수로 사용 하였는데

const modFn = useSetRecoilState(toDoList) //

}

 

function ToDoList(){

 const [value, modFn]= useRecoilState(toDoList);//  const[x, setX] =useState()처럼 사용가능.

}


useRecoilValue: state값을 리턴
useSetRecoilState: setter 함수를 리턴
useRecoilState: state, setter 함수를 모두 리턴

 

 

4.

 

const handleValid = (data: IForm) => {
        console.log("add to do ", data.toDo);
        setValue("toDo", "");

        setToDo(["hello"]) //typescript에서는 ToDos가 항상 빈 배열이여야 하기에 이동작은 허용되지 않음  

이를 위해 typescript에게 toDo가 어떻게 생긴지를 알려줄 interface를 하나 만듬.
    }; 

interface IToDo {
    text: string;
    category: "TO_DO" | "DOING" | "DONE";
}
const toDoState = atom<IToDo[]>({
    key: "toDo",
    default: [],
});

 

5.

 const handleValid = ({ toDo }: IForm) => {
        setToDo((oldToDos) => [{ text: toDo, category: "TO_DO" }, ...oldToDos]); 
// (oldToDos => [oldTodos])와 같이 반환하게 되면 [[oldToDos]] 배열 안에 배열을 갖게 됨. 

[...oldToDos]처럼 사용하게 된다면 oldToDos 배열 안의 값만 들어가게 된다.
    };

 

6.

화면에 그리기 

 <ul>
                {toDos.map((toDo) => (
                    <li key={toDo.id}>{toDo.text}</li>
                ))}
            </ul>

 

 


분리하기

CreateToDo.tsx 컴포넌트 만들어 form 이동!

ToDo.tsx 컴포넌트 만들어 li 부분 이동! 

atoms.tsx 만들어서 atom과 해당 interface 이동!

기본틀은 ToDoList.tsx

 

분리된 CreateToDo에서 

    const [toDos, setToDo] = useRecoilState(toDoState); 값을 수정만하면되지 가져올 필요는 없다.!

같은맥락에서 ToDoList에서 가져오기 기능만 필요

 

에러가났었는데 이유는 

const {~~}= useForm<IForm>() useForm에 타입스크립트 참조 안해서

 

function ToDoList() {
    return (
        <div>
            <h1>To Dos</h1>
            <hr />
            <CreateToDo />
        <ul>
            {toDos.map((toDo) => (
                <ToDo text={toDo.text} category={toDo.category} id={toDo.id}/>

                <ToDo {...toDo}/>

                위와 같이 작성하여도 작동된다. toDos 배열의 toDo 원소 하나하나가  ToDo 컴포넌트에 필요한 props와 같은 모양이기 때문이다.
            ))}
        </ul>
        </div>
    );
}

 

 


사용자들이 버튼을 이용해서 toDo의 카테고리를 바꿀 수 있게 하는 기능 추가.

카테고리에 맞게 hide&show

 

function ToDo({ text, category }: IToDo) {
    return (
        <li>
            <span>{text}</span>
            {category !== "DOING" && <button>DOING</button>} //category가 "Doing"이아니면  DOING버튼을 나타냄
            {category !== "TO_DO" && <button>TO_DO</button>}
            {category !== "DONE" && <button>DONE</button>}
        </li>
    );

}

https://ko.reactjs.org/docs/conditional-rendering.html

&& 연산자의 리턴값을 보시면 이해가 되실겁니다.A && B
  • A가 참이면 B
  • A가 거짓이면 A
가 리턴됩니다.똑같이
A || B
  • A가 참이면 A
  • A가 거짓이면 B
or또한 이렇게 사용할 수 있죠.
편하게 삼항연산자를 사용하듯, 자주 사용됩니다.
const A = 'abc';
const B = '';

console.log(A && B); // A가 참이여서 B가 return -> 빈 공백이 콘솔에 찍힘
console.log(B && A); // B가 거짓이여서 B가 return -> 빈 공백이 콘솔에 찍힘​

 방법1 . 함수를 만들어서 새 익명 함수에 호출하고 인자를 넘겨주는 방법


function ToDo({ text, category }: IToDo) {
    const onClick = (newCategory: IToDo["category"]) => { //ITODO interface에 있는 category 항목 
        console.log("i Wanna go to ", newCategory);
    };
    return (
        <li>
            <span>{text}</span>
            {category !== "DOING" && (
                <button onClick={() => onClick("DOING")}>DOING</button>
            )}
            {category !== "TO_DO" && (
                <button onClick={() => onClick("TO_DO")}>TO_DO</button>
            )}
            {category !== "DONE" && (
                <button onClick={() => onClick("DONE")}>DONE</button>
            )}
        </li>
    );
}

 

방법 2 

function ToDo({ text, category }: IToDo) {
    const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
        console.log("i Wanna go to ", event.currentTarget.name);
    };
    return (
        <li>
            <span>{text}</span>
            {category !== "DOING" && (
                <button  name= "DOING" onClick={onClick}>DOING</button>
            )}
            {category !== "TO_DO" && (
                <button  name="TO_DO" onClick={onClick}>TO_DO</button>
            )}
            {category !== "DONE" && (
                <button  name="DONE" onClick={onClick}>DONE</button>
            )}
        </li>
    );
}

 

1.find to do based on id 

 

 

 

function ToDo({ text, category, id }: IToDo) {
    const setToDos = useSetRecoilState(toDoState);
    const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
        const {
            currentTarget: { name },
        } = event;
        setToDos((oldToDos) => {
            const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id  //prop으로 넘겨진 id);  //oldToDos는 배열이니 배열의 function인 인덱스 번호 찾기
            console.log(targetIndex); // 0 ,1 등 번호로 찾아줌.
            return oldToDos;
        });
    };
    return (

 

참고

function ToDoList() {
    const toDos = useRecoilValue(toDoState);
    return (
        <div>
            <h1>To Dos</h1>
            <hr />
            <CreateToDo />
            <ul>
                {toDos.map((toDo) => (
                    <ToDo key={toDo.id} {...toDo} /> prop으로 text, category id 모두 받음.
                ))}
            </ul>
        </div>
    );

2.

function ToDo({ text, category, id }: IToDo) {
    const setToDos = useSetRecoilState(toDoState);
    const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
        const {
            currentTarget: { name },
        } = event;
        setToDos((oldToDos) => {
            const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
            const oldToDo = oldToDos[targetIndex];
            const newToDo = { text, id, category: name };
            console.log(oldToDo, newToDo);
            return oldToDos;
        });
    };

category 항목이 바뀜

 

 

Array.prototype.findIndex()

findIndex() 메서드는 주어진 판별 함수를 만족하는 배열의 첫 번째 요소에 대한 인덱스를 반환합니다. 만족하는 요소가 없으면 -1을 반환합니다.
인덱스 대신 값을 반환하는 find() 메서드도 참고하세요.
```
const array1 = [5, 12, 8, 130, 44];

const isLargeNumber = (element) => element > 13;

console.log(array1.findIndex(isLargeNumber));
// expected output: 3
```
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

 

Array.prototype.findIndex() - JavaScript | MDN

findIndex() 메서드는 주어진 판별 함수를 만족하는 배열의 첫 번째 요소에 대한 인덱스를 반환합니다. 만족하는 요소가 없으면 -1을 반환합니다.

developer.mozilla.org

 

3.replace the todo in the index targetIndex with newToDo

 

배열의 원소 교체에 대한 이론

 

바꾸고 싶은 곳의 인덱스 번호를 찾은 후

인덱스 앞 , 뒤 모든 요소를 알아낸다.

const newArray = [...front "교체할것" ...back ]

 

 

 

function ToDo({ text, category, id }: IToDo) {
    const setToDos = useSetRecoilState(toDoState);
    const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
        const {
            currentTarget: { name },
        } = event;
        setToDos((oldToDos) => {
            const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
            const oldToDo = oldToDos[targetIndex];
            const newToDo = { text, id, category: name as any };//category가 todo,doing ,done중에 하나여야하는데 그냥 string이라고 경고 as any로 상관없다고 신경쓰지말라고 타입스크립트에게 전달.
            console.log(oldToDo, newToDo);
            return [
                ...oldToDos.slice(0, targetIndex),
                newToDo,
                ...oldToDos.slice(targetIndex + 1),
            ];
        });
    };

 

상태관리툴을 사용 할 때 Mutate하면 안되고  새로운 변수로 State를 바꿔줘야합니다.
Mutate는 let a = 1; 에서 a = 2 와 같이 a를 직접적으로 변경시키는걸 말합니다.

그래서 완전히 새로운 object나 array를 만들어주고 거기에 요소들을 그대로 입력해준다.

 

splice => side effect : 원래 array가 변형되고 삭제된 elements를 return 함
slice => no side effect : 원래 array를 복제해 행해지고 결과물을 return 함

react로 저장한 state들은 직접적으로 수정할 수 없기때문에 늘 setter함수를 사용해야 함
그래서 splice 같은 side effectr가 있는 함수를 사용할 때에는 복제해서 사용해야 함

 


recoil의 Selector

 

카테고리:TODO, DOING, DONE에 따라 정리를 하고 싶음.

그러나 이것을 위해 TODO ATOM, DOING ATOM ,등을 또 만들생각은 없다.

 

Selector function은 atom의 OUTPUT을 변형시키는 도구 

 

 

 

export const toDoState = atom<IToDo[]>({  //이 ATOM은 그냥 배열을 줄뿐     key: "toDo",
    default: [],
});

 

ATOM의 OUTPUT을 변형시키는건 SELECTOR!

 

selector은 get을 받는데 인자는 객체를 받고 그안에 get이있음

getfunction이 있어야 atom을 받을 수 있다.

selector은 atom을 보고 있어서 atom이변경된다면 selector의 output도 변경됨.

 

export const toDoSelector = selector({
    key: "toDoSelector",
    get: ({ get }) => {
        const toDos = get(toDoState)   
        return toDos.length;
    },
});

 

toDoList.tsx
function ToDoList() {
    const toDos = useRecoilValue(toDoState);
    const selectorOutput = useRecoilValue(toDoSelector);
    console.log(selectorOutput);
    return (
        <div>
            <h1>To Dos</h1>
            <hr />
            <CreateToDo />
            <ul>
                {toDos.map((toDo) => (
                    <ToDo key={toDo.id} {...toDo} />
                ))}
            </ul>
        </div>
    );
}

 

export const toDoSelector = selector({
    key: "toDoSelector",
    get: ({ get }) => {
        const toDos = get(toDoState);
        return [
            toDos.filter((toDo) => toDo.category === "TO_DO"),
            toDos.filter((toDo) => toDo.category === "DOING"),
            toDos.filter((toDo) => toDo.category === "DONE"),
        ];
    },
});

function ToDoList() {
    const [toDo, doing, done] = useRecoilValue(toDoSelector); //위엘 보면 큰 배열안에 3개 배열이있다.

미리 배열을 열고 그 안에 각각에대한 이름을 정의해주었음
    return (

  <div>
            <h1>To Dos</h1>
            <hr />
            <CreateToDo />
            <hr />
            <h2>toDo</h2>
            <ul>
                {toDo.map((toDo) => (
                    <ToDo key={toDo.id} {...toDo} />
                ))}
            </ul>
            <hr />
            <h2>Doinig</h2>
            <ul>
                {doing.map((toDo) => (
                    <ToDo key={toDo.id} {...toDo} />
                ))}
            </ul>

 


category에 따라서  새 toDo의 카테고리가 정해졌으면 좋겠음

 

function CreateToDo() {
    const setToDos = useSetRecoilState(toDoState); 
    const category = useRecoilValue(categoryState); //카테고리 스테이트에서 카테고리 받아오기

const handleValid = ({ toDo }: IForm) => {
        setToDos((oldToDos) => [
            { text: toDo, id: Date.now(), category: category },
            ...oldToDos,
        ]);
        setValue("toDo", "");
    };

 

type은 재사용이 가능하다. 복붙을 안하게 해주는 단순한 문법 


type categories = "TO_DO" | "DOING" | "DONE";

export interface IToDo {
    text: string;
    id: number;
    category: categories ;
}

export const categoryState = atom<categories >({
    key: "category",
    default: "TO_DO",
});

 

function ToDoList() {
    const toDos = useRecoilValue(toDoSelector);
    const [category, setCategory] = useRecoilState(categoryState);
    const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
        setCategory(event.currentTarget.value as any); setCategory할 때 인자로 string인 값을 넘김.

    };

 

타입스크립트가 보기에 OPTION의 VALUE는 그냥 STRING 


            <select value={category} onInput={onInput}>
                <option value="TO_DO">To Do</option>
                <option value="DOING">Doing</option>
                <option value="DONE">Done</option>
            </select> 

 

ATOM.TSX

type categories = "TO_DO" | "DOING" | "DONE";

 

ATOM에 있는 CATEGORIES와 OPTION의 STRING형태의 VALUE가 같은 것인지 타입스크립트는 알지 못한다.


enum: enumerable 셀수 있는

계속해서 써야하는 값을 저장하는 도구 

 

export enum Categories {
    "TO_DO",
    "DOING" ,
    "DONE",
}

export interface IToDo {
    text: string;
    id: number;
    category: Categories;
}

export const categoryState = atom<Categories>({
    key: "category",
    default: Categories.TO_DO,
});

 

toDoList.tsx

 

    return (
        <div>
            <h1>To Dos</h1>
            <hr />
            <select value={category} onInput={onInput}>
                <option value={Categories.TO_DO}>To Do</option>
                <option value={Categories.DOING}>Doing</option> //value="Dooing" 같은 실수를 줄 일 수 있다.
                <option value={Categories.DONE}>Done</option>
            </select>

 

toDo.tsx

    return (
        <li>
            <span>{text}</span>
            {category !== Categories.DOING && (
                <button name={Categories.DOING} onClick={onClick}>
                    DOING
                </button>
            )}
            {category !== Categories.TO_DO && (
                <button name={Categories.TO_DO} onClick={onClick}>
                    TO_DO
                </button>
            )}
            {category !== Categories.DONE && (
                <button name={Categories.DONE} onClick={onClick}>
                    DONE
                </button>
            )}
        </li>
    );

Cateogries의 "TO_DO"값은 0

Categoreis의 "TO_DO"의 값은 0, 순차대로 1,2이다. 

따라서 위의          {category !== Categories.DOING && 이것은 cateogry와 숫자 2와 같냐고 물어보는 격

console.log(ToDos)하면 category:0이라고 나옴 

코드 상에서는  각각 0,1,2일 뿐 

 

enum은 개발자가 코드를 더 쉽게 작성 할 수 있도록 도와준다.

export const categoryState = atom<Categories>({
    key: "category",
    default: Categories.TO_DO, // 실제 값은 0
});

 

export enum Categories {
    "TO_DO" = "TO_DO", //console.log(toDos)하고 category보면 "TO_DO"라고나옴 
    "DOING" = "DOING",
    "DONE" = "DONE",
}

 

Enums

열거형은 TypeScript가 제공하는 기능 중 하나입니다.
enum은 열거형으로 이름이 있는 상수들의 집합을 정의할 수 있습니다.
열거형을 사용하면 의도를 문서화 하거나 구분되는 사례 집합을 더 쉽게 만들수 있습니다. TypeScript는 숫자와 문자열-기반 열거형을 제공합니다.

숫자 열거형 (Numeric enums)
enum Direction {
Up = 1,
Down,
Left,
Right,
}

문자열 열거형 (String enums)
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
등등..

https://www.typescriptlang.org/ko/docs/handbook/enums.html

 

 

정리 

TODO를 추가하면 TODOSTATE에 추가된다.

TODO를 보려고 할 때는 SELECTOR를 사용하는데 (STATE를 가져다가 조금 변형해주는 함수 )

SELECTOR에서 값을 얻어오려면 STATE처럼 USERECOILVALUE 사용하면딘다.

 

SELECOTR는  KEY, GET FUNCTION이 있다. GETFUNCTION은 SELECTOR가 어떤 것을 반환할지 결정하는데 GET FUNCTION 은 인자로 객체를 받는데 이 객체는 또 다른 FUNCTION이 들어있고 이함수를 사용하면 원하는 ATOM을 가져올 수있다.