[TIL] 내일배움캠프 React 과정 2022.12.14

Today I Learned

  • React 숙련 과정 개인 과제 수행

 


Redux로 TO DO LIST 구현하기

Spread Operator 이해하기

1. array에 사용하면 대괄호를 제거해주고, object에 사용하면 중괄호를 제거해준다.

 

2. 문자열에 사용하면 문자를 하나하나 쪼개 준다.

+) 문자도 array처럼 인덱싱이 가능하다! string[0] => 'h'

 

3. spread를 사용하면 array, object를 복사할 때 값을 공유하지 않고 각각 독립적인 값을 저장하도록 할 수 있다. -> Deep Copy

 

let a = [1, 2, 3]
let b = [4, 5]

let c = [...a]
console.log(c) // [1, 2, 3]

let c = [...a, ...b, 8]
console.log(c) // [1, 2, 3, 4, 5, 8]

...a는 1, 2, 3인데 이것을 대괄호 안에 넣었기 때문에 [...a]가 [1, 2, 3]이 되는 것이다.

 

let object1 = { a: 1, b: 2}

let object2 = { a: 5, ...object1 }
concole.log(object2) // {a: 1, b: 2}

let object3 = { ...object1, a: 5 }
console.log(object3) // {a: 5, b: 2}

만약에 object를 spread로 복사하다가 값이 중복되는 상황이 발생한다면? 무조건 뒤에 있는 값이 적용된다.

 

 

Reducer의 ADD_TODO 이해하기

const initialState = [
        { id: uuidv4(), title: "운동하기", content: "운동해서 체력 기르자", isDone: false },
        { id: uuidv4(), title: "코딩 공부하기", content: "성실하게! 열심히!", isDone: true },
    ]
const todos = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO:
        return (
            [...state, action.payload]
        )
};

 

console.log(...state)를 찍어 보면 이렇게 생겼다. 기존 state에서 대괄호만 빠진 모습이다.

 

action.payload는 Form.jsx에서 dispatch로 보낸 그대로 객체 형태이다. 

 

[...state, action.payload]

결국 이 코드는 ...state로 기존에 있던 각 객체와 action.payload로 받은 새로운 객체 하나를 받아서 대괄호 안에 넣은 후 return하면서 setState의 역할을 하는 것이다.

 

 

DELETE-TODO 구현하기

// TodoList.jsx
const deleteHandler = (id) => {
    dispatch(
        todos.filter((list) => (list.id !== id))
    )
}
   

// todos.js
const todos = (state = initialState, action) =>
  switch (action.type) {
    case DELETE_TODO:
        return (
            action.payload
        )
};

action.type이 DELETE_TODO인 경우에 필터링된 배열을 return하도록 코드를 작성했는데 다음과 같은 에러가 뜬다.

Uncaught Error: Actions must be plain objects. Instead, the actual type was: 'array'.

 

const deleteHandler = (id) => {
    dispatch(
        deleteTodo(
            todos.filter((list) => (list.id !== id))
        )
    )
}

문제 해결! TodoList 컴포넌트에 Action Creator인 deleteTodo 함수를 import한 후에 dispatch 함수 안에서 실행해야 되는 거였다. 

 

근데 또 다른 문제가 발생 ㅋㅋ 갑자기 웹페이지에 투두리스트 카드가 뜨지 않는다.

 

const deleteHandler = () => {
    console.log(2)
}

<button onClick={deleteHandler}>
// 결과물
<button onClick={console.log(2)}>

<button onClick={deleteHandler()}>
// 결과물
<button onClick={2}>

onClick으로 함수 연결할 때 주의할 점!

 

<button onClick={deleteHandler(list.id)}>삭제</button>

내가 원래 작성했던 코드대로라면 클릭했을 때 onClick에 deleteHandler를 실행한 결과물(return 값)이 들어간다. 이렇게 작성했을 때 삭제 버튼을 클릭하지 않았는데도 렌더링 하면서 deleteHandler가 실행되는 문제가 발생했다.

 

<button onClick={() => deleteHandler(list.id)}>삭제</button>

버튼을 클릭했을 때 함수를 실행해서 deleteHandler(list.id)를 return해라 == deleteHandler를 실행해라

 

검색해보니 react에서 onClick으로 함수를 실행할 때의 문제점에 대해서 많은 글이 있다.

https://muhly.tistory.com/70

https://heeyeonjeong.tistory.com/55

 

 

react-router-dom part 설정하기

const Router = () => {
    return (
        <BrowserRouter>
            <Header />
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/:id" element={<Detail />} />
            </Routes>
        </BrowserRouter>
    )
}

Uncaught Error: You cannot render a <Router> inside another <Router>. You should never have more than one in your app.

상단의 코드에서 <Home />이 있는 위치에 바로 <App /> 컴포넌트를 넣어 봤더니 에러 메시지가 뜬다. 그래서 Home 컴포넌트를 새로 만들고 그 안에 Header, Form, TodoList를 넣어 줬다.

 

 

useParams 사용해서 디테일 페이지에 제목과 내용 띄우기

function Detail()  {

    const todos = useSelector((state) => state.todos);
    const param = useParams();

    const todo = todos.find((list) => list.id === parseInt(param.id));
    return (
        <div>
            <div>제목: {todo.title}</div>
            <div>내용: {todo.content}</div>
        </div>
    )
}

todos에 map을 돌려서 list의 id와 param.id가 일치하는 것을 todo에 할당해서 해당 id 값을 가진 객체의 제목과 내용을 띄우려고 했는데 지금 코드 상태에서 todo를 콘솔로 찍어 봤더니 undefined가 나온다.

 

문제는 parseInt 때문이었다! parseInt를 사용했던 이유는 id 값을  1, 2, 3 등 숫자로 설정해 놓았을 때 문자열로 받은 param.id를 다시 숫자 형태로 바꾸기 위함이었는데 나는 id 값을 uuid로 생성했기 때문에 parseInt를 사용할 필요가 없었다.

 

 

Router 이해하기

const Router = () => {
    return (
        <BrowserRouter>
            <Header />
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/:id" element={<Detail />} />
            </Routes>
        </BrowserRouter>
    )
}

Header를 어느 페이지에 가든 고정되게 만들고 싶어서 튜터님께 질문하니 Router에 Header를 import해서 컴포넌트로 넣으면 된다고 알려주셨다. 

 

function App () {
  return (
    <Router>
    	<Home>
    </Router>
  );
};

그리고 내가 원래는 App.jsx에 Home 컴포넌트를 넣어놨었는데 이것도 필요가 없는 거였다. Router를 컴포넌트라고 이해하면 되는데 Router 함수의 return 부분이 App 컴포넌트 안에 들어 있는 <Router>에  모두 들어오고, Router에 이미 Home 컴포넌트가 들어가 있기 때문에 App에는 Router만 작성해도 문제가 없다. 아래처럼!

 

function App () {
  return (
    <div className="wrap">
      <Router />
    </div>
  );
};

wrap css를 모든 페이지에 적용하기 위해 div를 하나 추가해서 그 안에 Router를 넣어 놨다.


회고

투투리스트 구현 과제에서 spread operator를 자주 쓰는데 이해가 완벽하게 되지 않아서 추가 공부가 필요하다는 생각에 코딩애플에서 spread operator 부분만 강의를 들었다. 강의를 들으면서 느낀 점은 나는 spread를 잘 모르면서 그냥 쓰고 있었던 거 같다는 것이다. 오늘 강의 들으면서 어떤 식으로 사용할 수 있는 건지 확실하게 이해가 되었다!

 

튜터님들께 질문할 때마다 아직 부족한 부분이 많다는 점을 느낀다. 그래도 질문하는 과정에서 하나씩 알게 되는 부분들이 많고, 이렇게 직접 틀려가면서 배운 것들은 더욱 기억에 잘 남아서 다행인 거 같다. 그리고 내가 지금대로만 하면 좋은 결과가 있을 거라는 말씀을 해주셔서 위로가 되었다. 지금 올바른 길을 가고 있으니까 포기하지 말고 열심히 해보자! 🥳

 

함수를 import할 때 경로가 자동 완성되는 기능이 작동하지 않아서 상대 경로 작성하는 게 너무 귀찮아서 익스텐션을 하나 설치했다. auto import라는 건데 너무 편하다!