: createAsyncThunk는 비동기작업을 처리하는 Action을 만들어준다.(=createAsyncThunk를 통해 action creator가 만들어짐)
const asyncUpFetch = createAsyncThunk(
'counterSlice/asyncUpFetch', //action type ( 1번째 파라미터 )
async () => {
const response = await fetch('https://~') //서버에 접속하고
const data = await response.json(); //결과를 가져오고
return data.value;//결과를 반환하는 함수
} //action이 실행되었을 때 처리되어야 하는 함수 ( 2번째 파라미터 )
비동기작업을 했을 때, 크게 3가지의 상태로 나뉘게 되는데,
위 pending,fulfilled, rejected는 약속된 명칭으로 createAsyncThunk를 사용하면 자동으로 생성되는 상수라고 생각하면 된다.
이렇게 3가지 상태별로 리듀서가 생성되게 된다.
const counterSlice = createSlice({
name : 'counterSlice',
initialState:{
value:0,
status : 'Welcome'
},
extraReducers:(builder)=>{
builder.addCase(asyncUpFetch.pending,(state,action)=>{
state.status = 'Loading';
})
builder.addCase(asyncUpFetch.fulfilled,(state,action)=>{
state.value = action.payload
state.status = 'complete';
})
builder.addCase(asyncUpFetch.rejected,(state,action)=>{
state.status = 'fail';
})
}
});
const status = useSelector(state => {
return state.counter.status;
});
const count = useSelector(state => {
return state.counter.value;
});
.
.
.
return(
<button
onClick = {()=>{dispatch(asyncUpFetch());}}
> + async thunk</button>
<div> {count} | {status} </div>
)
버튼을 눌렀을 때, click이벤트가 발생하면서 asyncUpFetch 리듀서가 실행이 된다.
pending 상태일 때, reducer의 state.status
가 useSelector의 status
로 전달이 되고, 그 내용이 div태그 안의 {status}
에 ‘Loading’이라는 텍스트가 출력되게 된다.
그리고 asyncUpFetch의 ajax작업이 끝나면 fulfilled라는 리듀서가 자동으로 호출된다. 이때 data.value로 반환된 값이 reducerdml state.value
에 action.payload라는 이름으로 들어가게 된다. 그 내용이 useSelector의 count
로 전달이 되어 div태그 안의 {count}
에 value값이 출력되게 되며, reducer의 state.status
가 useSelector의 status
로 전달이 되어 div태그 안의 {status}
에 ‘complete’라는 텍스트가 출력되게 된다.
reject 상태일 때, reducer의 state.status
가 useSelector의 status
로 전달이 되고, 그 내용이 div태그 안의 {status}
에 ‘fail’이라는 텍스트가 출력되게 된다.
const counterSlice = createSlice({
name : 'counterSlice',
initialState:{
value:0,
status : 'Welcome'
},
reducers:{
up:(state,action) => {
state.value = state.value + action.payload
} // 1. 동기적인 액션은 reducers를 사용
},
extraReducers:(builder)=>{
builder.addCase(asyncUpFetch.fulfilled,(state,action)=>{
state.value = action.payload
state.status = 'complete';
})
}//2. 비동기적인 액션은 extraReducers를 사용하게 된다.
});
: 리액트에서 state안에 있는 내용을 변경할 때, 불변함을 유지하기 위해서 원본을 복제하고, 그 안에있는 원소도 다시 복제하여 밸류를 변경하는 식으로 변경하였다. 그래서 전개구문을 많이 쓰게 됐었는데 이 불편함을 해소시켜주는 것이 Immer이다.
import produce from "immer"
const copyTodos = produce(todos, draft =>{
draft[1].done = true
draft.push({title:"운동", done:false})
})
setTodos(copyTodos);
immer 사용하기 위해 produce함수에 todos와 콜백함수를 인자로 넣어준다.
함수의 인자인 draftsms todos의 임시복제본으로 지금까지 썼던 전개구문의 자리에 draft가 들어가서 복제본에 접근하여 state를 변경할 수 있다.
: 컴포넌트가 불필요한 리랜더링을 하지 않도록 해주는 함수로, 불필요한 랜더링을 줄임으로 프로젝트의 부하를 줄이고, 퍼포먼스 능력을 향상시킬수 있다.
//App.jsx
import React, { useState } from "react";
import Button from "./components/Button";
const App = () => {
const [value, setValue] = useState("");
const onChangeHandler = (e) => {
setValue(e.target.value);
};
return (
<div>
<input type="text" value={value} onChange={onChangeHandler} />
<Button />
</div>
);
};
export default App;
// components/Button.js
import React from "react";
const Button = () => {
console.log("리렌더링되고 있어요.");
return <button>버튼</button>;
};
export default Button;
위 처럼 인풋의 value, 즉 state가 onChange될때마다 setState로 state가 변경되고 있기 때문에, App.js는 리랜더링됩니다. 여기서 불필요한 리랜더링은 App 컴포넌트 안에있는 Button 컴포넌트 에서 발생하고 있습니다. input의 value와 아무 상관이 없음에도 불구하고 부모컴포넌트가 랜더링 되었다는 이유로 리랜더링이 되고 있습니다 이때, memo()를 사용해서 리랜더링 되지 않도록 해주는 것 입니다.
import React, { memo } from "react";
const Button = () => {
console.log("리렌더링되고 있어요.");
return <button>버튼</button>;
};
export default memo(Button);
: 컴포넌트가 리랜더링 되더라도 생성된 함수를 새로 만들지 않고 재사용하고자 할 때 사용하는 hook.
// src/components/Button.js
import React, { memo } from "react";
const Button = ({ onClick }) => {
console.log("리렌더링되고 있어요.");
return <button onClick={onClick}>버튼</button>;
};
export default memo(Button);
위 코드 처럼 함수를 props로 전달받게 되면 memo()를 사용하지 않고도 리랜더링이 일어난다.
이는 부모로부터 전달받은 props값이 변경된 경우로, props로 전달하는 함수를 매번 재생성 하고 있기 때문인데, 리랜더링되는 과정은 아래와 같다.
onChangeHandler
가 실행되고, setValue
가 실행 → value라는 state가 변경됨useState
및 함수
들이 다시 생성됨onChangeHandler
, onClickHandler
onClickHandler
이 재선언되었기 때문에 Button.js 입장에서는 새로운 값으로 판단함onClickHandler
함수를 새로운 props 로 인식하고 리렌더링 함console.log("리렌더링되고 있어요.");
이 실행됨그래서 이를 막으려면 App.js가 리랜더링한다고 해도 함수를 매번 재생성 하지 않도록 만들면 되는데, 이 때 쓰는 Hook이 useCallback이다.
기본구조
useCallback( ()=>{} , [] )
첫번재 매개변수 자리 : 구현하고자하는 함수
두번째 매개변수 자리 : 의존성배열
이렇게 생성하면 함수가 생성되고 난 후 재생성 되지 않게 된다. 하지만, 의존성 배열에 값을 넣어주게 되면 해당 값이 변경되었을 때 함수도 같이 재생성 된다.
// src/App.jsx
import React, { useCallback, useState } from "react";
import Button from "./components/Button";
const App = () => {
const [value, setValue] = useState("");
const onChangeHandler = (e) => {
setValue(e.target.value);
};
const onClickHandler = useCallback(() => {
console.log("hello button!");
}, []);
return (
<div>
<input type="text" value={value} onChange={onChangeHandler} />
<Button onClick={onClickHandler} />
</div>
);
};
export default App;
: useCallback과 같은 기능을 하는 Hook! 대상이 함수일때는 useCallback을 사용하고, 대상이 배열이나 객체와 같은 값일 때에는 useMemo를 사용한다.
// src/App.jsx
import React, { useState } from "react";
import List from "./components/List";
const App = () => {
const [value, setValue] = useState("");
const onChangeHandler = (e) => {
setValue(e.target.value);
};
const data = [
{
id: 1,
title: "react",
},
];
return (
<div>
<input type="text" value={value} onChange={onChangeHandler} />
<List data={data} />
</div>
);
};
export default App;
// src/components/List.jsx
import React, { memo } from "react";
const List = ({ data }) => {
console.log("리렌더링되고 있어요.");
return (
<div>
{data.map((todo) => (
<div key={todo.id}>{todo.title}</div>
))}
</div>
);
};
export default memo(List);
위와 같은이유로 data를 props로 받는 List컴포넌트가 App.js가 리랜더링 될때마다 재생성 된다.
이때, data라는 배열을 재생성되지 않도록 하기 위해서 useMemo를 사용한다.
import React, { useState } from "react";
import { useMemo } from "react";
import List from "./components/List";
const App = () => {
const [value, setValue] = useState("");
const onChangeHandler = (e) => {
setValue(e.target.value);
};
const data = useMemo(() => {
return [
{
id: 1,
title: "react",
},
];
}, []);
return (
<div>
<input type="text" value={value} onChange={onChangeHandler} />
<List data={data} />
</div>
);
};
export default App;
주의!
memo()
, useCallback
, useMemo
를 사용하는 것은 리액트에게 “렌더링 이후의 값과 전의 값을 비교해서 같으면 재생성하지마!” 라고 주문을 하는 것과 마찬가지 이므로 이들의 무분별한 사용은 오히려 퍼포먼스 성능에 악영향이 될 수 있다. 하지만 반드시 바뀌어야하는 값에 저 기능을 사용하면 굳이 비교하지 않아도 될 것들에도 리액트가 비교를 하기 때문에 저 기능을 사용하기에 앞서, 불필요한 리렌더링이 맞는지, 개선할 수 있는 부분인지 확인해야한다.오늘은 어제 들었던 심화강의를 이해해보는 시간을 가졌다. 생활코딩 강의를 참고하여 이해해보니, 어떤 개념인지 이해가 되었다. 어제까지만 해도 무슨말인지 잘 몰랐던 것도 계속 반복하다보니 무슨말인지 이해가 되는 것이 신기하다. 물론 예시코드로 공부한 것이어서 복잡한 코드를 보게되면 다시 헷갈릴지 모르지만 그러면서 정확하게 이해가 되는 거니까 더 많은 예시로 공부해봐야 겠다.
내일부터 프로젝트가 시작된다. 오늘까지 공부한 내용들을 잘 활용해서 프로젝트에 적용해보고 싶다.
이제 진짜 개발자가 되신것같아요
정리도, 생각도, 학습능력도 다 올라가고계신것같아 너무 보기좋네요
플젝도 화이팅입니다!!