Updated upstream
scss 에서 주로 부모 태그 id를 #root로 지정한다.
단순 리액트에서는 html의 부모 id가 #root 이지만
next.js 에서는 부모 id가 #__next 이다
추가설정없이는 전역 css와
_app에 다른페이지의 css를 간편하게 모으는것도 방법이지만
이경우 각자의 페이지마다 className을 구체적으로 떨어뜨려 중복을 방지하는것이 좋다.
HYDRATE 는 conbineReducer에서 리듀서를 합칠때
서버사이드 렌더링을 위해서 action.tpye의 case부분에 작성한다
컴바인리듀서에서 초기상태값도 자동으로 다른객체로 합쳐서부른다
useSelector에서 중앙상태값을 받아쓰고
useCallback안의 dispatch(액션함수({인자1,인자2})) 로 해당 액션 사용
구조분해할당할때
user에서 age를 받는다고 할때
const age = useSelector((state)=> state.user.age); 로 부르는데
const {age} = useSelector((state)=>state.user); 로도 부를수있다
객체 깊이가 한단계 낮아지니 유의
옵셔널 체이닝(optional chaining)
const id = useSelector((state)=> state.user.login && state.user.login.id);
const id = useSelector((state)=> state.user.login?.id);
있나 없나 의심 되는부분에는 ? 로 대체가능
export default function* rootSaga() {
yield all([
fork(feedSaga),
fork(userSaga),
]);
all은 배열은 받는다
fork에서 feedSaga는
function* feedSaga() {
yield take('액션타입')
}
을 사용한다
redux
const reducer = (state = initialState, action ) => {
switch (action.type) {
casse 'LOG_IN_REQUSET';
return {
...state,
isLoggingIn: true,
}
case 'LOG_IN_SUCCESS';
return{
export const feedReducer = feedSlice.reducer;
export const feedAction = feedSlice.actions; //리듀서 안에 정의된 함수 (state,action)
----
========================================================================================
import { createSlice } from '@reduxjs/toolkit'
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo(state, action) {
const { id, text } = action.payload
state.push({ id, text, completed: false })
},
toggleTodo(state, action) {
const todo = state.find(todo => todo.id === action.payload)
if (todo) {
todo.completed = !todo.completed
}
}
}
})
export const { addTodo, toggleTodo } = todosSlice.actions
export default todosSlice.reducer
========================================================================================
createSlice는 다음 옵션과 함께 option 객체를 인자로 사용합니다.
takes an options object as its argument, with these options:
- name: 생성 된 action types를 생성하기 위해 사용되는 prefix
- initialState: reducer의 초기 상태
- reducers: key는 action type문자열이 되고 함수는 해당 액션이 dispatch될때 실행될 reducer입니다.
(switch-case문과 비슷해서 "case reducers"라고도 합니다.)
따라서, "todos/addTodo"액션이 dispatch될 때 addTodoreducer가 수행됩니다.
default핸들러는 없습니다. createSlice에 의해 생성된 리듀서는
현재 dispatch된 액션이 아닌 다른 액션들에 대해 자동으로 현재 상태를 반환하도록 처리되어 있기 때문에,
직접 핸들링해주지 않아도 됩니다
redux-saga/effects
redux-saga/effects 에서
yield의 종류 (항상 앞에 붙는다) yield call은 await과 비슷하다
yeild 쓰는 이유는 단순 동작을 넘어서 동작이 잘 되는지 보장 받을 필요가 있다.
test할때 끊어서 실행되니 문제점을 파악하기 쉽다
ex) const l = logIn({type: "LOG_IN_REQUEST', data: {id : 'zerocho@gamil.com'}})
l.next();
next할때마다 yield 부분까지만 실행
all : 배열안에 값을 모두 실행
fork : function* 제너레이터 비동기 함수 실행 (결과를 기다리지않고 다음 줄 실행) (논블록킹)
axios.post('/api/login')
yield put({
type: 'LOG_IN_SUCCESS',
data: result.data
})
★ call : function* 제너레이터 동기 함수 실행 (결과를 기다리고 다음 줄 실행)
axios.post('/api/login')
.then(() =>{
yield put({
type: 'LOG_IN_SUCCESS',
data: result.data
})
})
take : 동기적으로 동작 1회용, 딱 한번만 사용되고 사용불가
while(ture) {
yield take('LOG_IN_REQUEST', login)
}
와일로 테이크를 감싸주면 여러번사용가능
takeEvery : 비동기적으로 동작 와일 테이크와 기능 같음
★ takeLatest : 마지막으로 호출된것만 실행 (실수로 두번 호출해서 둘 다 로딩중일때 마지막으로 로딩된 것만 호출, 이미 완료된 호출은 해당사항 없음)
요청 2번일경우 마지막 요청에대해서만 응답, 요청은 2번 무조건 다 감, 응답을 취소
takeLeading : 가장 먼저 호출된것만 실행
throttle : 일정시간후 실행 yield throttle('ADD_POST_REQUST', addPost, 2000); 스크롤을 내리거나 올릴때 사용
debouncing : 연이어 호출되는 함수들중 마지막함수 (또는 제일 처음)만 호출하게 한다. ajax에서 주로사용
,검색창에 한글자한글자 할때마다 호출하기보다 어떤 단어가 완성됬을때만 요청보낼떄
put : dispatch (액션함수 실행)
전체적인 redux-saga 흐름 정의
function* watchLogin() {
yield take('LOG_IN_REQUEST', logIn);
'LOG_IN_REQUEST'이라는 액션이 실행(take)될때까지 기다리다가(yield)
logIn이라는 제너레이터함수(function* login)을 실행한다.
즉 'LOG_IN_REQUEST'이라는 액션이 들어오면 logIn 제너레이터 함수(function*) 실행
function* logIn() {
yield call(logInAPI) /
}
function loginAPI( 전달할 로그인 데이터) { //여기서는 제너레이터함수(*)가 아니다
return axios.post('/api/login', 전달할 로그인 데이터 )
}
전체적인 redux-saga 흐름
//이벤트 리스너의 역할
function* watchLogin() {
yield take('LOG_IN_REQUEST', logIn);
function* logIn(action) {
const result = yield call(logInAPI, action.data, 'a', 'b') // logInAPI(action.data) == yeild call(logInAPI, action.data) 첫번째 자리가 함수 그 다음이 매개변수
yield put({
type: 'LOG_IN_SUCCESS',
data: result.data
})
}
function loginAPI( data a, b) { //여기서는 제너레이터함수(*)가 아니다
return axios.post('/api/login', data )
}
saga 상세 흐름
function* watchLogin() {
yield take('LOG_IN_REQUEST', logIn);
ex) 이벤트 리스너 역할
function* logIn() {
const result = yield call(logInAPI)
}
서버로 로그인하는 요청의 결과를 받는다
function* logIn() {
const result = yield call(logInAPI)
yield put({
type: 'LOG_IN_SUCCESS',
data: result.data
})
}
받은결과 함수실행(put)
put은 dispatch
function* logIn() {
try{
const result = yield call(logInAPI)
yield put({
type: 'LOG_IN_SUCCESS',
data: result.data
})
} catch (err) {
yield put({
type : 'LOG_IN_FAILURE',
data : err.response.data,
})
}
}
성공결과는 result.data,
실패 결과는 err.response.data
비동기요청함수의 액션
REQUEST
SUCCESS
FAILURE 나눈다
axios.get('http://placeholder.com')
.then((result)=>{
result.data
}).catch((err)=>
err)
axios의 result에는 {data (요청한값) , status:200,404 ,statusText:"", headers:{...}, config:{...}}
등등의 데이터가 오는데 원하는 요청값은 data로 지정되어있다.
dispatch쓰는이유
단순히 해당 page에서 axios.get으로 데이터를 불러들여오면 편한데 왜 dispatch와 redux-saga를 쓰는 이유는
saga가 redux의 middleware로서 redux로 중앙상태값을 변경시킬때 동시에 saga도 실행되어서
saga 이벤트리스너 역할을 하는 function* (리덕스타입 or 리덕스툴킷일때는 리덕스의 함수값 , 사가 비동기통신 함수)
를 호출할수있어서 동시에 전역의 중앙상태값을 axios통신과함께 바꿀수 있기 때문에 dispatch를 쓴다.
해당 saga는 처음 request에서 api함수를 호출하고 그 호출값에따라 success일 경우
성공한 데이터값을 리덕스타입 or 리덕스툴일때는 리덕스의 함수값을 불러 해당 함수의 action.payload 로 인자값을 넘겨준다.
넘겨진 인자값은 해당 리덕스 함수의 파라미터로 가서 리덕스 로직에 따라 중앙상태값을 변경시킨다.
해당 변경된 중앙상태값이 store에 저장되고 그 저장된 store를 _app.tsx 전역 페이지에 넘김으로써 모든 컴포넌트에게 공유시킨다.
dispatch는 보통
페이지가 처음 렌더링될때 useEffect로 부르거나
useCallback 으로 어떤 상태값이 변경되었을때 사용한다.
useSelector는 리덕스에서 상태값을 가져올때 사용한다.
reducer 흐름도
이니셜스테이트 -> 리듀서로 상태변환 -> 디스패치로 리듀서 사용 , 리듀서를 쓸때 사가 사용 -> 사가에서 API 호출 -> 호출값을 리덕스에 삽입 - 중앙 상태 변화
사가로 미들웨어가 걸려져 있어도 사가 안쓰고 단순히 리덕스만 사용가능!
axios 비동기값 확인
const __GetFeedState = (token: string | null) => {
return axios({
method: "GET",
url: "http://localhost:8080/feed",
// url: "https://jsonplaceholder.typicode.com/comments",
headers: { Authorization: "Bearer " + token },
})
.then((res) => {
console.log(res);
return res.data;
})
.catch((err) => {
return err;
});
};
useEffect(() => {
const token = localStorage.getItem("Token");
GetFeedState(token);
}, [GetFeedState]);