개발자 되는 중/개발 공부

React 심화 강의

SeonChoco 2022. 12. 22. 14:21

Redux Toolkit

yarn add react-redux @reduxjs/toolkit

DevTool

redux dev tools 라는 기능이 있다. 디버깅 할 때 좋다.

구글 웹 스토어에서 다운 받는 기능이다.

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko

우리가 toolKit 다운 받을 때 이미 devTools도 다운받았다

= toolKit 안 깔면 devTools 설정 따로 해줘야한다.

thunk

immer

JSON 서버

프론트 엔드 만들 때 임시로 서버 역할을 하는 것

mock 데이터를 가지고 있다.

yarn add json-server

백 엔드 서버이기 때문에 리액트와 별개로 켜줘야 한다.

yarn json-server --watch db.json --port 3001

항상 이 서버에 접근하고 싶다면 리액트 프로젝트를 배포하는 것 처럼 배포해야한다.

axios

브라우저와 node.js를 위한 promise 기반 HTTP 클라이언트 라이브러리..?

  • HTTP를 이용해서 서버와 통신하기 위해 사용하는 패키지

api 서버와 통신하는 방법을 필수로 알아야 한다.

axios method 4가지

yarn add axios

GET - read POST - create

DELETE -delete

PATCH -update

요청하는 법은 라이브러리에서 정해준 대로 따라야 한다.

api 명세를 작성하는 법

path variable

정해져 있는 아이디 같은 것들을 사용해서 넘겨주는 경우

예시

GET /posts/1

query

키값이 있는 경우

예시

GET / posts?title=joson-server&author=typicode

await async를 써주었다. 이건 promise 객체에 쓰는 것이다. axios 로 만든 json 데이터를 받을 때 promise 객체인가 보다.

보통 백 엔드에서 post는 서버에 새로운 데이터를 넣어줄 때 쓰지만

여기서는 클라이언트의 데이터를 body 형태로 서버로 보내줄 때 쓴다.

네트워크의 헤더 텝

네트워크의 페이로드 탭 - 우리가 보낸 페이로드가 있는 경우만 뜬다.

네트워크의 응답 탭 - 우리의 요청에 대한 서버의 응답

json서버는 post 요청을 보냈을 때 클라이언트가 보낸 body를 그대로 응답해주도록 되어있다. (자동이 아니라 api 명세서에 설정되어 있는 내용)

patch: 기존 데이터를 요청하는 부분만 수정할 때

{
  "todos": [
    {
      "id": 1,
      "title": "json-server", => "title" : "a"
      "content": "json-server를 배워봅시다."
    }
  ]
}

put: 기존 테이터를 모두 수정할 때

{
  "todos": [
    {
      "id": 1, => "id" : null
      "title": "json-server", => "title" : "a"
      "content": "json-server를 배워봅시다." => "content" : null
    }
  ]
}

사실 post를 통해서도 수정 기능을 만들 수 있지만 put과 patch를 쓰자고 HTTP환경에서 서로 약속한 것

axios get, post, patch, delete 이용하여 CRUD 구현한 app.js 코드

// src/App.js

import React, { useEffect, useState } from "react";
import axios from "axios"; // axios import 합니다.

const App = () => {
  const [todos, setTodos] = useState(null);
  const [todo, setTodo] = useState({
    id: "",
    title: "",
    content: "",
  });

  // axios를 통해서 get 요청을 하는 함수를 생성합니다.
  // 비동기처리를 해야하므로 async/await 구문을 통해서 처리합니다.
  const fetchTodos = async () => {
    const { data } = await axios.get("<http://localhost:3001/todos>");
    setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set 합니다.
  };

  // 생성한 함수를 컴포넌트가 mount 됐을 떄 실행하기 위해 useEffect를 사용합니다.
  useEffect(() => {
    // effect 구문에 생성한 함수를 넣어 실행합니다.
    fetchTodos();
  }, []);

  const handleOnSubmitAddTodo = async (todo) => {
    await axios.post("<http://localhost:3001/todos>", todo);
    const newTodo = {
      id: todos.length + 1,
      title: todo.title,
      content: todo.content,
    };
    setTodos([...todos, newTodo]);
  };

  const handleOnClickDeleteTodo = async (id) => {
    await axios.delete(`http://localhost:3001/todos/${id}`);
    setTodos(todos.filter((item) => item.id !== id));
  };

  const handleOnClickUpdateTodo = async (id, newTodo) => {
    await axios.patch(`http://localhost:3001/todos/${id}`, newTodo);
    const newTodoList = todos.map((item) =>
      item.id === id
        ? { ...todo, title: newTodo.title, content: newTodo.content }
        : item
    );
    setTodos(newTodoList);
  };

  // data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다.
  console.log(todos); // App.js:16
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleOnSubmitAddTodo(todo);
      }}
    >
      <label htmlFor="title">제목</label>
      <input
        type="text"
        onChange={(e) => {
          const { name, value } = e.target;
          setTodo({ ...todo, [name]: value });
        }}
        id="title"
        name="title"
      />
      <label htmlFor="content">내용</label>
      <input
        type="text"
        onChange={(e) => {
          const { name, value } = e.target;
          setTodo({ ...todo, [name]: value });
        }}
        id="content"
        name="content"
      />
      <button>추가</button>

      <div>
        {todos?.map((item) => {
          return (
            <div key={item.id}>
              <div>제목: {item.title}</div>
              <div>내용: {item.content}</div>
              <button
                type="button"
                onClick={() => handleOnClickDeleteTodo(item.id)}
              >
                삭제
              </button>

              <button
                type="button"
                onClick={() => handleOnClickUpdateTodo(item.id, todo)}
              >
                수정
              </button>
              <input
                type="text"
                placeholder="제목 수정 값 입력"
                onChange={(e) => {
                  const { name, value } = e.target;
                  setTodo({ ...todo, [name]: value });
                }}
                id="edit"
                name="title"
              />
              <input
                type="text"
                placeholder="내용 수정 값 입력"
                onChange={(e) => {
                  const { name, value } = e.target;
                  setTodo({ ...todo, [name]: value });
                }}
                id="edit"
                name="content"
              />
            </div>
          );
        })}
      </div>
    </form>
  );
};

export default App;

redux 미들웨어

액션⇒ 미들웨어⇒리듀서⇒스토어

ex) 카운터에서 버튼누르면 3초 있다가 +1하게 하려면

3초 있다가라는 설정은 미들웨어에서 해준다. = 비동기처리가 필요할 때 미들웨어를 이용한다.

미들웨어 중 가장 많이 사용되고 있는 것이 redux-thunk이다.

원리

dispatch 할 때 원래를 객체를 넣었는데 대신에 함수를 넣어주어 함수를 실행하고 함수 안에서 객체를 실행하게 하는 것이다.

dispatch(함수) ⇒ 함수 실행 ⇒ 함수 안에서 dispatch(객체)

사용법

모듈 안에서 toolkit의 createAsyncthunk 이라는 api를 가져와서 쓰면 된다.

couterSlice.js

export const _addNumber = createAsyncThunk(
  //첫번째 인자: action value
  "addNumber",
  //두번째 인자 : 콜백함수
  (payload, thunkAPI) => {
    setTimeout(() => {
      thunkAPI.dispatch(addNumber(payload));
    }, 3000);
  }
);

기존 액션 크리에이터 말고 thunk를 적용한 새 액션 크리에이터 넣어준다.

**const handleOnClickPlusNum = (payload) => {
    dispatch(_addNumber(+payload));
  };**

Thunk에서 Promise 객체를 다루는 법 + redux를 이용해서 state 관리

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
  todos: [],
  isLoading: false,
  error: null,
};

// // 액션 객체를 dispatch 해주는 api fulfillvalue, rejectwithvalue를  포함하는 Thunk 함수
export const __getTodos = createAsyncThunk(
  "todos/getTodos",
  async (payload, thunkAPI) => {
    try {
      const data = await axios.get("<http://localhost:3001/todos>");
      console.log(data);
      return thunkAPI.fulfillWithValue(data.data);
    } catch (error) {
      console.log(error);
      return thunkAPI.rejectWithValue(error);
    }
  }
);

const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {},
  extraReducers: {
    [__getTodos.pending]: (state) => {
      state.isLoading = true;
    },
    [__getTodos.fulfilled]: (state, action) => {
      state.isLoading = false;
      state.todos = action.payload;
    },
    [__getTodos.rejected]: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});

export const {} = todoSlice.actions;

export default todoSlice.reducer;
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __getTodos } from "../modules/todoSlice";

const Todos = () => {
  const dispatch = useDispatch();
  const { isLoading, error, todos } = useSelector((state) => state.todo);
  console.log(isLoading, error, todos);

  useEffect(() => {
    dispatch(__getTodos());
  }, [dispatch]);

  if (isLoading) {
    return <div>로딩 중</div>;
  }
  if (error) {
    return <div>{error.message}</div>;
  }
  return todos.map((item) => {
    return (
      <div key={item.id}>
        <div>{item.title}</div>
        <div>{item.content}</div>
      </div>
    );
  });
};

export default Todos;

memo(hook은 아니고 알아야할 개념), useMemo, useCallback

memo:

컴포넌트에 불필요한 렌더링을 하지 않도록 하는 함수

불필요한 렌더링 == 화면에서 변경이 없는데도 리렌더링되는 경우

리렌더링 조건 (복습)

  1. 부모 컴포넌트 렌더링
  2. 컴포넌트의 state 변경
  3. 부모로부터 전달받는 props 값이 변경된 경우

불필요한 렌더링을 줄이면 프로젝트의 부하를 줄이고 퍼포먼스 능력을 향상 시킬 수 있다.

부모 컴포넌트가 리렌더링이 되더라도 자식 컴포넌트가 리렌더링 되지 않게 하는 처리

import {memo} from “react”

const Button = () => {
return <div>
<button>버튼</button>
</div>}

export default memo(Button);

useCallback vs useMemo

useCallback : 프롭스로 넘겨주는 함수는 선언된 장소인 컴포넌트가 리렌더링 될 때 재 생성 되는데, useCallback을 써주면 재 생성되지 않는다.

import {useCallback} from "react";

const onClickHandler = useCallback(() ⇒ {

console.log("hellobutton")

},[]);

useMemo : 프롭스로 넘겨주는 객체가 위치한 장소의 컴포넌트가 리렌더링 될 때 객체도 재생성 되는데, useMemo를 써주면 재 생성되지 않는다.

useCallback과 같은 데 배열이나, 객체의 재 생성을 막아 줄 때 쓴다.

import {useMemo} from "react";

const data = useMemo(()=>{
return [
{
id:1,
title:"react",
},
]},[]);

원시 데이터: let a= 1 이런 식의 데이터들은 useMemo를 안 써도 리렌더링 하지 않는다.

위의 최적화 hook을 남용하면 안되는 이유

  1. 렌더링 전 후의 값을 비교해서 같지 않으면 재생성하고 같으면 재생성을 하지 않는데 ? (강사님이 내가 생각한 것과 반대로 얘기하시는데 뭘 비교하는 거지) 이러한 비교 과정이 불필요하게 들어갈 수 있다.

Custom Hook

반복되는 로직이나 기능을 우리가 Custom Hook으로 만들어서 관리 할 수 있다.

useInput이라는 커스텀 훅을 만들어보자 - 이름은 마음대로 지어되나, 앞에 use를 붙여야한다.

Redux Toolkit

yarn add react-redux @reduxjs/toolkit

DevTool

redux dev tools 라는 기능이 있다. 디버깅 할 때 좋다.

구글 웹 스토어에서 다운 받는 기능이다.

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko

우리가 toolKit 다운 받을 때 이미 devTools도 다운받았다

= toolKit 안 깔면 devTools 설정 따로 해줘야한다.

thunk

immer

JSON 서버

프론트 엔드 만들 때 임시로 서버 역할을 하는 것

mock 데이터를 가지고 있다.

yarn add json-server

백 엔드 서버이기 때문에 리액트와 별개로 켜줘야 한다.

yarn json-server --watch db.json --port 3001

항상 이 서버에 접근하고 싶다면 리액트 프로젝트를 배포하는 것 처럼 배포해야한다.

axios

브라우저와 node.js를 위한 promise 기반 HTTP 클라이언트 라이브러리..?

  • HTTP를 이용해서 서버와 통신하기 위해 사용하는 패키지

api 서버와 통신하는 방법을 필수로 알아야 한다.

axios method 4가지

yarn add axios

GET - read POST - create

DELETE -delete

PATCH -update

요청하는 법은 라이브러리에서 정해준 대로 따라야 한다.

api 명세를 작성하는 법

path variable

정해져 있는 아이디 같은 것들을 사용해서 넘겨주는 경우

예시

GET /posts/1

query

키값이 있는 경우

예시

GET / posts?title=joson-server&author=typicode

await async를 써주었다. 이건 promise 객체에 쓰는 것이다. axios 로 만든 json 데이터를 받을 때 promise 객체인가 보다.

보통 백 엔드에서 post는 서버에 새로운 데이터를 넣어줄 때 쓰지만

여기서는 클라이언트의 데이터를 body 형태로 서버로 보내줄 때 쓴다.

네트워크의 헤더 텝

네트워크의 페이로드 탭 - 우리가 보낸 페이로드가 있는 경우만 뜬다.

네트워크의 응답 탭 - 우리의 요청에 대한 서버의 응답

json서버는 post 요청을 보냈을 때 클라이언트가 보낸 body를 그대로 응답해주도록 되어있다. (자동이 아니라 api 명세서에 설정되어 있는 내용)

patch: 기존 데이터를 요청하는 부분만 수정할 때

{
  "todos": [
    {
      "id": 1,
      "title": "json-server", => "title" : "a"
      "content": "json-server를 배워봅시다."
    }
  ]
}

put: 기존 테이터를 모두 수정할 때

{
  "todos": [
    {
      "id": 1, => "id" : null
      "title": "json-server", => "title" : "a"
      "content": "json-server를 배워봅시다." => "content" : null
    }
  ]
}

사실 post를 통해서도 수정 기능을 만들 수 있지만 put과 patch를 쓰자고 HTTP환경에서 서로 약속한 것

axios get, post, patch, delete 이용하여 CRUD 구현한 app.js 코드

// src/App.js

import React, { useEffect, useState } from "react";
import axios from "axios"; // axios import 합니다.

const App = () => {
  const [todos, setTodos] = useState(null);
  const [todo, setTodo] = useState({
    id: "",
    title: "",
    content: "",
  });

  // axios를 통해서 get 요청을 하는 함수를 생성합니다.
  // 비동기처리를 해야하므로 async/await 구문을 통해서 처리합니다.
  const fetchTodos = async () => {
    const { data } = await axios.get("<http://localhost:3001/todos>");
    setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set 합니다.
  };

  // 생성한 함수를 컴포넌트가 mount 됐을 떄 실행하기 위해 useEffect를 사용합니다.
  useEffect(() => {
    // effect 구문에 생성한 함수를 넣어 실행합니다.
    fetchTodos();
  }, []);

  const handleOnSubmitAddTodo = async (todo) => {
    await axios.post("<http://localhost:3001/todos>", todo);
    const newTodo = {
      id: todos.length + 1,
      title: todo.title,
      content: todo.content,
    };
    setTodos([...todos, newTodo]);
  };

  const handleOnClickDeleteTodo = async (id) => {
    await axios.delete(`http://localhost:3001/todos/${id}`);
    setTodos(todos.filter((item) => item.id !== id));
  };

  const handleOnClickUpdateTodo = async (id, newTodo) => {
    await axios.patch(`http://localhost:3001/todos/${id}`, newTodo);
    const newTodoList = todos.map((item) =>
      item.id === id
        ? { ...todo, title: newTodo.title, content: newTodo.content }
        : item
    );
    setTodos(newTodoList);
  };

  // data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다.
  console.log(todos); // App.js:16
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleOnSubmitAddTodo(todo);
      }}
    >
      <label htmlFor="title">제목</label>
      <input
        type="text"
        onChange={(e) => {
          const { name, value } = e.target;
          setTodo({ ...todo, [name]: value });
        }}
        id="title"
        name="title"
      />
      <label htmlFor="content">내용</label>
      <input
        type="text"
        onChange={(e) => {
          const { name, value } = e.target;
          setTodo({ ...todo, [name]: value });
        }}
        id="content"
        name="content"
      />
      <button>추가</button>

      <div>
        {todos?.map((item) => {
          return (
            <div key={item.id}>
              <div>제목: {item.title}</div>
              <div>내용: {item.content}</div>
              <button
                type="button"
                onClick={() => handleOnClickDeleteTodo(item.id)}
              >
                삭제
              </button>

              <button
                type="button"
                onClick={() => handleOnClickUpdateTodo(item.id, todo)}
              >
                수정
              </button>
              <input
                type="text"
                placeholder="제목 수정 값 입력"
                onChange={(e) => {
                  const { name, value } = e.target;
                  setTodo({ ...todo, [name]: value });
                }}
                id="edit"
                name="title"
              />
              <input
                type="text"
                placeholder="내용 수정 값 입력"
                onChange={(e) => {
                  const { name, value } = e.target;
                  setTodo({ ...todo, [name]: value });
                }}
                id="edit"
                name="content"
              />
            </div>
          );
        })}
      </div>
    </form>
  );
};

export default App;

redux 미들웨어

액션⇒ 미들웨어⇒리듀서⇒스토어

ex) 카운터에서 버튼누르면 3초 있다가 +1하게 하려면

3초 있다가라는 설정은 미들웨어에서 해준다. = 비동기처리가 필요할 때 미들웨어를 이용한다.

미들웨어 중 가장 많이 사용되고 있는 것이 redux-thunk이다.

원리

dispatch 할 때 원래를 객체를 넣었는데 대신에 함수를 넣어주어 함수를 실행하고 함수 안에서 객체를 실행하게 하는 것이다.

dispatch(함수) ⇒ 함수 실행 ⇒ 함수 안에서 dispatch(객체)

사용법

모듈 안에서 toolkit의 createAsyncthunk 이라는 api를 가져와서 쓰면 된다.

couterSlice.js

export const _addNumber = createAsyncThunk(
  //첫번째 인자: action value
  "addNumber",
  //두번째 인자 : 콜백함수
  (payload, thunkAPI) => {
    setTimeout(() => {
      thunkAPI.dispatch(addNumber(payload));
    }, 3000);
  }
);

기존 액션 크리에이터 말고 thunk를 적용한 새 액션 크리에이터 넣어준다.

**const handleOnClickPlusNum = (payload) => {
    dispatch(_addNumber(+payload));
  };**

Thunk에서 Promise 객체를 다루는 법 + redux를 이용해서 state 관리

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
  todos: [],
  isLoading: false,
  error: null,
};

// // 액션 객체를 dispatch 해주는 api fulfillvalue, rejectwithvalue를  포함하는 Thunk 함수
export const __getTodos = createAsyncThunk(
  "todos/getTodos",
  async (payload, thunkAPI) => {
    try {
      const data = await axios.get("<http://localhost:3001/todos>");
      console.log(data);
      return thunkAPI.fulfillWithValue(data.data);
    } catch (error) {
      console.log(error);
      return thunkAPI.rejectWithValue(error);
    }
  }
);

const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {},
  extraReducers: {
    [__getTodos.pending]: (state) => {
      state.isLoading = true;
    },
    [__getTodos.fulfilled]: (state, action) => {
      state.isLoading = false;
      state.todos = action.payload;
    },
    [__getTodos.rejected]: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});

export const {} = todoSlice.actions;

export default todoSlice.reducer;
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __getTodos } from "../modules/todoSlice";

const Todos = () => {
  const dispatch = useDispatch();
  const { isLoading, error, todos } = useSelector((state) => state.todo);
  console.log(isLoading, error, todos);

  useEffect(() => {
    dispatch(__getTodos());
  }, [dispatch]);

  if (isLoading) {
    return <div>로딩 중</div>;
  }
  if (error) {
    return <div>{error.message}</div>;
  }
  return todos.map((item) => {
    return (
      <div key={item.id}>
        <div>{item.title}</div>
        <div>{item.content}</div>
      </div>
    );
  });
};

export default Todos;

memo(hook은 아니고 알아야할 개념), useMemo, useCallback

memo:

컴포넌트에 불필요한 렌더링을 하지 않도록 하는 함수

불필요한 렌더링 == 화면에서 변경이 없는데도 리렌더링되는 경우

리렌더링 조건 (복습)

  1. 부모 컴포넌트 렌더링
  2. 컴포넌트의 state 변경
  3. 부모로부터 전달받는 props 값이 변경된 경우

불필요한 렌더링을 줄이면 프로젝트의 부하를 줄이고 퍼포먼스 능력을 향상 시킬 수 있다.

부모 컴포넌트가 리렌더링이 되더라도 자식 컴포넌트가 리렌더링 되지 않게 하는 처리

import {memo} from “react”

const Button = () => {
return <div>
<button>버튼</button>
</div>}

export default memo(Button);

useCallback vs useMemo

useCallback : 프롭스로 넘겨주는 함수는 선언된 장소인 컴포넌트가 리렌더링 될 때 재 생성 되는데, useCallback을 써주면 재 생성되지 않는다.

import {useCallback} from "react";

const onClickHandler = useCallback(() ⇒ {

console.log("hellobutton")

},[]);

useMemo : 프롭스로 넘겨주는 객체가 위치한 장소의 컴포넌트가 리렌더링 될 때 객체도 재생성 되는데, useMemo를 써주면 재 생성되지 않는다.

useCallback과 같은 데 배열이나, 객체의 재 생성을 막아 줄 때 쓴다.

import {useMemo} from "react";

const data = useMemo(()=>{
return [
{
id:1,
title:"react",
},
]},[]);

원시 데이터: let a= 1 이런 식의 데이터들은 useMemo를 안 써도 리렌더링 하지 않는다.

위의 최적화 hook을 남용하면 안되는 이유

  1. 렌더링 전 후의 값을 비교해서 같지 않으면 재생성하고 같으면 재생성을 하지 않는데 ? (강사님이 내가 생각한 것과 반대로 얘기하시는데 뭘 비교하는 거지) 이러한 비교 과정이 불필요하게 들어갈 수 있다.

Custom Hook

반복되는 로직이나 기능을 우리가 Custom Hook으로 만들어서 관리 할 수 있다.

useInput이라는 커스텀 훅을 만들어보자 - 이름은 마음대로 지어되나, 앞에 use를 붙여야한다.

Redux Toolkit

yarn add react-redux @reduxjs/toolkit

DevTool

redux dev tools 라는 기능이 있다. 디버깅 할 때 좋다.

구글 웹 스토어에서 다운 받는 기능이다.

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko

우리가 toolKit 다운 받을 때 이미 devTools도 다운받았다

= toolKit 안 깔면 devTools 설정 따로 해줘야한다.

thunk

immer

JSON 서버

프론트 엔드 만들 때 임시로 서버 역할을 하는 것

mock 데이터를 가지고 있다.

yarn add json-server

백 엔드 서버이기 때문에 리액트와 별개로 켜줘야 한다.

yarn json-server --watch db.json --port 3001

항상 이 서버에 접근하고 싶다면 리액트 프로젝트를 배포하는 것 처럼 배포해야한다.

axios

브라우저와 node.js를 위한 promise 기반 HTTP 클라이언트 라이브러리..?

  • HTTP를 이용해서 서버와 통신하기 위해 사용하는 패키지

api 서버와 통신하는 방법을 필수로 알아야 한다.

axios method 4가지

yarn add axios

GET - read POST - create

DELETE -delete

PATCH -update

요청하는 법은 라이브러리에서 정해준 대로 따라야 한다.

api 명세를 작성하는 법

path variable

정해져 있는 아이디 같은 것들을 사용해서 넘겨주는 경우

예시

GET /posts/1

query

키값이 있는 경우

예시

GET / posts?title=joson-server&author=typicode

await async를 써주었다. 이건 promise 객체에 쓰는 것이다. axios 로 만든 json 데이터를 받을 때 promise 객체인가 보다.

보통 백 엔드에서 post는 서버에 새로운 데이터를 넣어줄 때 쓰지만

여기서는 클라이언트의 데이터를 body 형태로 서버로 보내줄 때 쓴다.

네트워크의 헤더 텝

네트워크의 페이로드 탭 - 우리가 보낸 페이로드가 있는 경우만 뜬다.

네트워크의 응답 탭 - 우리의 요청에 대한 서버의 응답

json서버는 post 요청을 보냈을 때 클라이언트가 보낸 body를 그대로 응답해주도록 되어있다. (자동이 아니라 api 명세서에 설정되어 있는 내용)

patch: 기존 데이터를 요청하는 부분만 수정할 때

{
  "todos": [
    {
      "id": 1,
      "title": "json-server", => "title" : "a"
      "content": "json-server를 배워봅시다."
    }
  ]
}

put: 기존 테이터를 모두 수정할 때

{
  "todos": [
    {
      "id": 1, => "id" : null
      "title": "json-server", => "title" : "a"
      "content": "json-server를 배워봅시다." => "content" : null
    }
  ]
}

사실 post를 통해서도 수정 기능을 만들 수 있지만 put과 patch를 쓰자고 HTTP환경에서 서로 약속한 것

axios get, post, patch, delete 이용하여 CRUD 구현한 app.js 코드

// src/App.js

import React, { useEffect, useState } from "react";
import axios from "axios"; // axios import 합니다.

const App = () => {
  const [todos, setTodos] = useState(null);
  const [todo, setTodo] = useState({
    id: "",
    title: "",
    content: "",
  });

  // axios를 통해서 get 요청을 하는 함수를 생성합니다.
  // 비동기처리를 해야하므로 async/await 구문을 통해서 처리합니다.
  const fetchTodos = async () => {
    const { data } = await axios.get("<http://localhost:3001/todos>");
    setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set 합니다.
  };

  // 생성한 함수를 컴포넌트가 mount 됐을 떄 실행하기 위해 useEffect를 사용합니다.
  useEffect(() => {
    // effect 구문에 생성한 함수를 넣어 실행합니다.
    fetchTodos();
  }, []);

  const handleOnSubmitAddTodo = async (todo) => {
    await axios.post("<http://localhost:3001/todos>", todo);
    const newTodo = {
      id: todos.length + 1,
      title: todo.title,
      content: todo.content,
    };
    setTodos([...todos, newTodo]);
  };

  const handleOnClickDeleteTodo = async (id) => {
    await axios.delete(`http://localhost:3001/todos/${id}`);
    setTodos(todos.filter((item) => item.id !== id));
  };

  const handleOnClickUpdateTodo = async (id, newTodo) => {
    await axios.patch(`http://localhost:3001/todos/${id}`, newTodo);
    const newTodoList = todos.map((item) =>
      item.id === id
        ? { ...todo, title: newTodo.title, content: newTodo.content }
        : item
    );
    setTodos(newTodoList);
  };

  // data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다.
  console.log(todos); // App.js:16
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleOnSubmitAddTodo(todo);
      }}
    >
      <label htmlFor="title">제목</label>
      <input
        type="text"
        onChange={(e) => {
          const { name, value } = e.target;
          setTodo({ ...todo, [name]: value });
        }}
        id="title"
        name="title"
      />
      <label htmlFor="content">내용</label>
      <input
        type="text"
        onChange={(e) => {
          const { name, value } = e.target;
          setTodo({ ...todo, [name]: value });
        }}
        id="content"
        name="content"
      />
      <button>추가</button>

      <div>
        {todos?.map((item) => {
          return (
            <div key={item.id}>
              <div>제목: {item.title}</div>
              <div>내용: {item.content}</div>
              <button
                type="button"
                onClick={() => handleOnClickDeleteTodo(item.id)}
              >
                삭제
              </button>

              <button
                type="button"
                onClick={() => handleOnClickUpdateTodo(item.id, todo)}
              >
                수정
              </button>
              <input
                type="text"
                placeholder="제목 수정 값 입력"
                onChange={(e) => {
                  const { name, value } = e.target;
                  setTodo({ ...todo, [name]: value });
                }}
                id="edit"
                name="title"
              />
              <input
                type="text"
                placeholder="내용 수정 값 입력"
                onChange={(e) => {
                  const { name, value } = e.target;
                  setTodo({ ...todo, [name]: value });
                }}
                id="edit"
                name="content"
              />
            </div>
          );
        })}
      </div>
    </form>
  );
};

export default App;

redux 미들웨어

액션⇒ 미들웨어⇒리듀서⇒스토어

ex) 카운터에서 버튼누르면 3초 있다가 +1하게 하려면

3초 있다가라는 설정은 미들웨어에서 해준다. = 비동기처리가 필요할 때 미들웨어를 이용한다.

미들웨어 중 가장 많이 사용되고 있는 것이 redux-thunk이다.

원리

dispatch 할 때 원래를 객체를 넣었는데 대신에 함수를 넣어주어 함수를 실행하고 함수 안에서 객체를 실행하게 하는 것이다.

dispatch(함수) ⇒ 함수 실행 ⇒ 함수 안에서 dispatch(객체)

사용법

모듈 안에서 toolkit의 createAsyncthunk 이라는 api를 가져와서 쓰면 된다.

couterSlice.js

export const _addNumber = createAsyncThunk(
  //첫번째 인자: action value
  "addNumber",
  //두번째 인자 : 콜백함수
  (payload, thunkAPI) => {
    setTimeout(() => {
      thunkAPI.dispatch(addNumber(payload));
    }, 3000);
  }
);

기존 액션 크리에이터 말고 thunk를 적용한 새 액션 크리에이터 넣어준다.

**const handleOnClickPlusNum = (payload) => {
    dispatch(_addNumber(+payload));
  };**

Thunk에서 Promise 객체를 다루는 법 + redux를 이용해서 state 관리

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
  todos: [],
  isLoading: false,
  error: null,
};

// // 액션 객체를 dispatch 해주는 api fulfillvalue, rejectwithvalue를  포함하는 Thunk 함수
export const __getTodos = createAsyncThunk(
  "todos/getTodos",
  async (payload, thunkAPI) => {
    try {
      const data = await axios.get("<http://localhost:3001/todos>");
      console.log(data);
      return thunkAPI.fulfillWithValue(data.data);
    } catch (error) {
      console.log(error);
      return thunkAPI.rejectWithValue(error);
    }
  }
);

const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {},
  extraReducers: {
    [__getTodos.pending]: (state) => {
      state.isLoading = true;
    },
    [__getTodos.fulfilled]: (state, action) => {
      state.isLoading = false;
      state.todos = action.payload;
    },
    [__getTodos.rejected]: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});

export const {} = todoSlice.actions;

export default todoSlice.reducer;
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { __getTodos } from "../modules/todoSlice";

const Todos = () => {
  const dispatch = useDispatch();
  const { isLoading, error, todos } = useSelector((state) => state.todo);
  console.log(isLoading, error, todos);

  useEffect(() => {
    dispatch(__getTodos());
  }, [dispatch]);

  if (isLoading) {
    return <div>로딩 중</div>;
  }
  if (error) {
    return <div>{error.message}</div>;
  }
  return todos.map((item) => {
    return (
      <div key={item.id}>
        <div>{item.title}</div>
        <div>{item.content}</div>
      </div>
    );
  });
};

export default Todos;

memo(hook은 아니고 알아야할 개념), useMemo, useCallback

memo:

컴포넌트에 불필요한 렌더링을 하지 않도록 하는 함수

불필요한 렌더링 == 화면에서 변경이 없는데도 리렌더링되는 경우

리렌더링 조건 (복습)

  1. 부모 컴포넌트 렌더링
  2. 컴포넌트의 state 변경
  3. 부모로부터 전달받는 props 값이 변경된 경우

불필요한 렌더링을 줄이면 프로젝트의 부하를 줄이고 퍼포먼스 능력을 향상 시킬 수 있다.

부모 컴포넌트가 리렌더링이 되더라도 자식 컴포넌트가 리렌더링 되지 않게 하는 처리

import {memo} from “react”

const Button = () => {
return <div>
<button>버튼</button>
</div>}

export default memo(Button);

useCallback vs useMemo

useCallback : 프롭스로 넘겨주는 함수는 선언된 장소인 컴포넌트가 리렌더링 될 때 재 생성 되는데, useCallback을 써주면 재 생성되지 않는다.

import {useCallback} from "react";

const onClickHandler = useCallback(() ⇒ {

console.log("hellobutton")

},[]);

useMemo : 프롭스로 넘겨주는 객체가 위치한 장소의 컴포넌트가 리렌더링 될 때 객체도 재생성 되는데, useMemo를 써주면 재 생성되지 않는다.

useCallback과 같은 데 배열이나, 객체의 재 생성을 막아 줄 때 쓴다.

import {useMemo} from "react";

const data = useMemo(()=>{
return [
{
id:1,
title:"react",
},
]},[]);

원시 데이터: let a= 1 이런 식의 데이터들은 useMemo를 안 써도 리렌더링 하지 않는다.

위의 최적화 hook을 남용하면 안되는 이유

  1. 렌더링 전 후의 값을 비교해서 같지 않으면 재생성하고 같으면 재생성을 하지 않는데 ? (강사님이 내가 생각한 것과 반대로 얘기하시는데 뭘 비교하는 거지) 이러한 비교 과정이 불필요하게 들어갈 수 있다.

Custom Hook

반복되는 로직이나 기능을 우리가 Custom Hook으로 만들어서 관리 할 수 있다.

useInput이라는 커스텀 훅을 만들어보자 - 이름은 마음대로 지어되나, 앞에 use를 붙여야한다.

'개발자 되는 중 > 개발 공부' 카테고리의 다른 글

후발대 - axios, 비동기, promise  (0) 2023.01.02
TIL 제대로 쓰는 법  (0) 2022.12.22
React uuid 쓰는 법  (0) 2022.12.20
애자일 방법론 특강  (0) 2022.12.16
CS 특강 - CPU  (0) 2022.12.15