>> 리덕스-툴킷
yarn add react-redux @reduxjs/toolkit
리덕스-툴킷에 slice 있음
(slice는 액션, 액션크리에이터, 리듀서를 동시에 함)
// src/redux/modules/counterSlice.js
// 액션벨류나 엑션크리에이터를 사용한 부분이 없어졌다(일반 리덕스랑 달리)
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
// 리덕스툴킷의 슬라이스 : 액션벨류,엑션크리에이터,리듀서가 합쳐짐
// createSlice라는 api를 통해 만들 수 있다.
// createSlice의 인자로는 모듈의 이름, 그 모듈의 초기상태값, 모듈의 리듀서 로직이 인자로 들어감
// 리듀서 안에 들어간 로직은 리듀서의 로직이 됨과 동시에 액션크리에이터가 된다.
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
//액션 벨류는, 함수의 이름을 따 자동으로 생성된다.
// 이 함수의 이름이 액션벨류가 되고, 함수안의 로직과 액션크리에이터가 생성된다.
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
>> json-server
json-server는 db랑 api서버를 생성해주는 패키지이다.
yarn add json-server
yarn json-server --watch db.json --port 3001
// db.json을 db로 사용하고, 3001번을 서버로 쓰겠다.
// db.json 파일이 만들어져 있음
yarn start
// 리액트도 실행시켜주기
** json-server와 리액트는 별개이니 둘 다 각각 실행시켜줘야 한다.
우리가 브라우저에 url을 친다는 것은 우리가 만든 api서버에 GET요청을 하는 것이다.
서버쪽 터미널에 가보면,
GET /todos 302 6.080 ms - -
가 적혀있는데,
‘누군가 get요청을 했고 난 6.080만에 대답했어 라고 찍힌 것
<json-server 공식문서>
Routes
Based on the previous db.json
file, here are all the default routes. You can also add other routes using --routes
.
Plural routes ( 이게 path variable 이라는 거 같은데)
: 정해진 아이디같은 것을 넘겨서 찾아올때는 이 방식을 사용하고GET /posts
GET /posts/1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1
Singular routes
GET /profile
POST /profile
PUT /profile
PATCH /profile
Filter
Use .
to access deep properties
// 타이틀이 json-server이고 author가 typicode인 애를 줘라 라고 요청하는 것
// 검색이나 특정한 값을 찾고 싶을때는 쿼리 방식을 사용하고GET /posts?title=json-server&author=typicode
GET /posts?id=1&id=2
GET /comments?author.name=typicode
+) 서연님이 yarn server를 패키지.json에 만드셨는데
그거 실행이 안되서 여쭤보니
yarn install 하고 실행해봐라했는데, 그렇게하니까 바로 서버 작동이 되었음
>> Axios
—악시우스는 promise 객체를 반환한다. —> 반환된 프로미스가 잘 성공적으로 처리되었는지를 fulfilled, 안되었으면 reject라고 한다.
yarn add axios
1) GET(CRUD의 READ기능)
// src/App.js
import React, { useEffect, useState } from "react";
import axios from "axios"; // axios import 합니다.
const App = () => {
const [todos, setTodos] = useState(null);
// 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();
}, []);
// data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다.
console.log(todos); // App.js:16
return <div>App</div>;
};
export default App;
const { data } = await axios.get("http://localhost:3001/todos");
→ 악시우스의 get 메소드 인자로는 (url, config) 가 있음
>> json-server(db.json)의 데이터 받아서, set함수로 state에 넣어주는 작업
const fetchTodos = async () => {
// json서버에 데이터를 요청해서(이걸 fetching한다고 하는거같아) json서버에서 가져온 데이터를
const { data } = await axios.get("http://localhost:3001/todos");
// setTodos해서 todos에 넣겠다. ()
setTodos(data);
};
>> 컴포넌트가 mount가 되었을 때, fetchTodos를 실행하겠다 라고 짠 것
useEffect(() => {
fetchTodos();
}, []);
(공식문서) : config에 어떤 것들이 들어가는지
2) POST(CRUD의 CREATE기능)
post 전체코드
// 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({ title: "" }); // 비동기처리를 해야하므로 async/await 구문을 통해서 처리 // axios는 프로미스 기반이라, async, await로 한다. const fetchTodos = async () => { // json서버에 데이터를 요청해서(이걸 fetching한다고 하는거같아) json서버에서 가져온 데이터를 const { data } = await axios.get("http://localhost:3001/todos"); // setTodos해서 todos에 넣겠다. () setTodos(data); }; const onSubmitHandler = (todo) => { axios.post("http://localhost:3001/todos", todo); }; // 생성한 함수를 컴포넌트가 mount 됐을 떄 실행하기 위해 useEffect를 사용합니다. useEffect(() => { // effect 구문에 생성한 함수를 넣어 실행합니다. fetchTodos(); }, []); // data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다. console.log(todos); // App.js:16 return ( <> <form onSubmit={(e) => { // 👇 submit했을 때 브라우저의 새로고침을 방지합니다. e.preventDefault(); onSubmitHandler(todo); }} > <input type="text" onChange={(ev) => { const { value } = ev.target; setTodo({ ...todo, title: value, }); }} /> <button>추가하기</button> </form> <div> {todos?.map((todo) => ( <div key={todo.id}>{todo.title}</div> ))} </div> </> ); }; export default App;
const onSubmitHandler = (todo) => {
axios.post("http://localhost:3001/todos", todo);
};
위 코드는 ‘새로고침’을 누르지 않으면, 마치 새로고침한것처럼
리랜더링이 발생하지 않는데 그 이유는?
todos에 todo를 추가해서, state가 업뎃되는 상황을 안만들어줬기 때문이다.
순전히 axios로 json-server의 데이터만 넣어준 것이지, state를 건드리진 않았짜농
아래처럼 요렇게 추가해주면 될 것
const onSubmitHandler = (todo) => {
axios.post("http://localhost:3001/todos", todo);
// 위 코드까지만 작성하면, 자연스러운 '새로고침'이 되지 않을 것
// 이유 : todos(스테이트)를 건드리지 않았기 때문에
// 따라서 자연스러운 새로고침이 일어나게 하려면, todos를 건드려주기
setTodos({ ...todos, todo });
};
3) DELETE(CRUD의 DELETE기능)
const onClickDeleteButtonHandler = (todoId) => {
axios.delete(`http://localhost:3001/todos/${todoId}`);
};
4) PATCH(CRUD의 UPDATE기능)
수정하기 코드스니펫 붙여넣기 전
// 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({ title: "" }); // 비동기처리를 해야하므로 async/await 구문을 통해서 처리 // axios는 프로미스 기반이라, async, await로 한다. const fetchTodos = async () => { // json서버에 데이터를 요청해서(이걸 fetching한다고 하는거같아) json서버에서 가져온 데이터를 const { data } = await axios.get("http://localhost:3001/todos"); // setTodos해서 todos에 넣겠다. () setTodos(data); }; const onSubmitHandler = (todo) => { axios.post("http://localhost:3001/todos", todo); // 위 코드까지만 작성하면, 자연스러운 '새로고침'이 되지 않을 것 // 이유 : todos(스테이트)를 건드리지 않았기 때문에 // 따라서 자연스러운 새로고침이 일어나게 하려면, todos를 건드려주기 setTodos([...todos, todo]); }; const onClickDeleteButtonHandler = (todoId) => { axios.delete(`http://localhost:3001/todos/${todoId}`); }; // 생성한 함수를 컴포넌트가 mount 됐을 떄 실행하기 위해 useEffect를 사용합니다. useEffect(() => { // effect 구문에 생성한 함수를 넣어 실행합니다. fetchTodos(); }, []); // data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다. console.log(todos); // App.js:16 return ( <> <form onSubmit={(e) => { // 👇 submit했을 때 브라우저의 새로고침을 방지합니다. e.preventDefault(); onSubmitHandler(todo); }} > <input type="text" onChange={(ev) => { const { value } = ev.target; setTodo({ ...todo, title: value, }); }} /> <button>추가하기</button> </form> <div> {todos?.map((todo) => ( <div key={todo.id}> {todo.title} <button type="button" onClick={() => { onClickDeleteButtonHandler(todo.id); }} > 삭제하기 </button> </div> ))} </div> </> ); }; export default App;
PATCH 코드스니펫 전체 복사한 것
// src/App.jsx import React, { useEffect, useState } from "react"; import axios from "axios"; const App = () => { const [todo, setTodo] = useState({ title: "", }); const [todos, setTodos] = useState(null); // patch에서 사용할 id, 수정값의 state를 추가 const [targetId, setTargetId] = useState(null); const [editTodo, setEditTodo] = useState({ title: "", }); const fetchTodos = async () => { const { data } = await axios.get("http://localhost:3001/todos"); setTodos(data); }; const onSubmitHandler = (todo) => { axios.post("http://localhost:3001/todos", todo); }; const onClickDeleteButtonHandler = (todoId) => { axios.delete(`http://localhost:3001/todos/${todoId}`); }; // 수정버튼 이벤트 핸들러 추가 👇 const onClickEditButtonHandler = (todoId, edit) => { axios.patch(`http://localhost:3001/todos/${todoId}`, edit); }; useEffect(() => { fetchTodos(); }, []); return ( <> <form onSubmit={(e) => { e.preventDefault(); onSubmitHandler(todo); }} > {/* 👇 수정기능에 필요한 id, 수정값 input2개와 수정하기 버튼을 추가 */} <div> <input type="text" placeholder="수정하고싶은 Todo ID" onChange={(ev) => { setTargetId(ev.target.value); }} /> <input type="text" placeholder="수정값 입력" onChange={(ev) => { setEditTodo({ ...editTodo, title: ev.target.value, }); }} /> <button // type='button' 을 추가해야 form의 영향에서 벗어남 type="button" onClick={() => onClickEditButtonHandler(targetId, editTodo)} > 수정하기 </button> </div> <input type="text" onChange={(ev) => { const { value } = ev.target; setTodo({ ...todo, title: value, }); }} /> <button>추가하기</button> </form> <div> {todos?.map((todo) => ( <div key={todo.id}> {/* todo의 아이디를 화면에 표시 */} {todo.id} :{todo.title} <button type="button" onClick={() => onClickDeleteButtonHandler(todo.id)} > 삭제하기 </button> </div> ))} </div> </> ); }; export default App;
>> 리덕스-미들웨어
— 미들웨어는 비동기 처리하려고 많이 씀
리덕스-미들웨어에서 가장 많이 쓰는게 thunk다.
>> thunk를 쓰면, dispatcher를 보낼때 우리는 ‘객체’를 보냈었는데, ‘함수’를 보낼 수 있게 해준다.
우리는 그 함수를 통해, 우리가 끼워넣고 싶은걸 중간에 끼워넣는 것,
그 함수안에서 다시 dispatcher가 실행될 수 있도록 만드는 것
⇒ thunk를 사용하려면, createAsyncThunk를 사용한다.
export const __addNumber = createAsyncThunk(
// 첫번째 인자 : action value
"addNumber",
// 두번째 인자 : 콜백함수
(payload, thunkAPI) => {
setTimeout(() => {
thunkAPI.dispatch(addNumber(payload));
}, 3000);
}
);
createAsyncThunk 의 첫번째 인자에는 액션벨류, 두번째 인자에는 함수를 넣어주면 된다.
→ 이 모듈안에 있는 thunk 함수를 컴포넌트에서 import해서 사용할 것임
- - - - - - - - - - - - - - - - - - — - - - - - - - — - — — — - - - - — — - - - — - - - - - -
원래는 addNumber를 액션크리에이터로 해서 dispatch를 했다면,
const onClickAddNumberHandler = () => {
dispatch(addNumber(number));
};
이제는 미들웨어인 thunk를 거치게 하는 것
// thunk 함수를 디스패치한다. payload는 thunk함수에 넣어주면,
// 리덕스 모듈에서 payload로 받을 수 있다.
const onClickAddNumberHandler = () => {
dispatch(__addNumber(number));
};
그 thunk는 (counterSlice.js에 있음)
export const __addNumber = createAsyncThunk(
// 첫번째 인자 : action value
"addNumber",
// 두번째 인자 : 콜백함수
(payload, thunkAPI) => {
setTimeout(() => {
thunkAPI.dispatch(addNumber(payload));
}, 3000);
}
);
이런 형식으로, 함수를 인자로 받는거지
- - - - - - - - - - - - - - - - - — - - - - - — -
>> Thunk 이어서
>> fulfillWithValue , rejectWithValue
리덕스-툴킷 설치
yarn add react-redux @reduxjs/toolkit
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
export const __getTodos = createAsyncThunk(
"todos/getTodos",
async (payload, thunkAPI) => {
try {
const data = await axios.get("http://localhost:3001/todos");
// fulfillWithValue : 서버에서 데이터 가져오는게 성공한 경우 dispatch해주는 것
// 인자로는 payload를 넣어줄 수 있다.
// 어디로 얘네가 dispatch해준다는 걸까?
// --> dispatch란 ? 리듀서에 action과 payload를 전달해주는 과정임
return thunkAPI.fulfillWithValue(data.data);
} catch (error) {
// rejectWithValue : 서버에서 데이터 가져오는게 실패한 경우 dispatch해주는 것
return thunkAPI.rejectWithValue(error);
}
}
);
이제, 리듀서를 작성할건데
thunk함수는 리듀서에 직접 넣어주는 것이 아니기 때문에, 해당 메소드를 사용해야되는데
진행중 상태,성공,실패일때는 어떻게 동작할 지 구현을 해주어야 한다.
pending ,fulfilled , reject
export const todosSlice = createSlice({
name: "todos",
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;
},
},
});
'🌼 리액트 공부' 카테고리의 다른 글
리액트 네이티브 첫 수업 (실시간 강의) 정리 #1 (0) | 2022.12.29 |
---|---|
[리액트] Carousel (이미지 자동 슬라이드) 기능 구현하기 (0) | 2022.12.26 |
[리액트] 리액트 심화 필기노트 (1) | 2022.12.08 |
[리액트] 리액트 입문 강의 정리노트 (0) | 2022.12.05 |
[리액트] 리액트 입문강의 듣는중 / 리액트의 'state'란? (0) | 2022.12.04 |
댓글