TIL : Thunk , Immer , Memo

hihyeon_cho·2022년 12월 21일
0

TIL

목록 보기
38/101
post-thumbnail

Thunk

: 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가지의 상태로 나뉘게 되는데,

  1. asyncUpFetch.pending ⇒ 비동기작업을 시작했을 때,
  2. asyncUpFetch.fulfilled ⇒ 비동기 작업이 끝났을 때,
  3. asyncUpFetch.rejected ⇒ 오류가 생겨서 비동기 작업이 중단됐을 때,

위 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’이라는 텍스트가 출력되게 된다.


동기적 creator와 비동기적 creator

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를 사용하게 된다.
});
  1. 동기적인 액션을 실행하게 되는 경우, toolkit이 action creator를 자동으로 생성해주므로 reducers를 사용한다.
  2. 비동기적이 액션을 실행하게 되는 경우 action creator가 자동으로 생성되지 못하므로, extraReducers 안에서 정의하여 사용하게 된다.


Immer

: 리액트에서 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를 변경할 수 있다.


memo()

: 컴포넌트가 불필요한 리랜더링을 하지 않도록 해주는 함수로, 불필요한 랜더링을 줄임으로 프로젝트의 부하를 줄이고, 퍼포먼스 능력을 향상시킬수 있다.

랜더링이 되는 시점

  1. 부모 컴포넌트가 랜더링 된
  2. component의 state가 변경된 경우
  3. 부모로부터 전달받은 props값이 변경된 경우
//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);


useCallback

: 컴포넌트가 리랜더링 되더라도 생성된 함수를 새로 만들지 않고 재사용하고자 할 때 사용하는 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로 전달하는 함수를 매번 재생성 하고 있기 때문인데, 리랜더링되는 과정은 아래와 같다.

  1. input에 값을 입력
  2. onChangeHandler가 실행되고, setValue가 실행 → value라는 state가 변경됨
  3. state 가 변경됨에 따라 App.js가 리렌더링 됨
  4. App.js 가 리렌더링되면, App.js 안에 있는 useState함수들이 다시 생성됨
    • 함수란? → onChangeHandler, onClickHandler
  5. onClickHandler 이 재선언되었기 때문에 Button.js 입장에서는 새로운 값으로 판단함
  6. Button.js은 onClickHandler 함수를 새로운 props 로 인식하고 리렌더링 함
  7. Button.js이 리렌더링되면서 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;
  1. useCallback import하기
  2. usecallback을 이용해서 onClickHandler가 재생성 되는 것 막기.



useMemo()

: 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 를 사용하는 것은 리액트에게 “렌더링 이후의 값과 전의 값을 비교해서 같으면 재생성하지마!” 라고 주문을 하는 것과 마찬가지 이므로 이들의 무분별한 사용은 오히려 퍼포먼스 성능에 악영향이 될 수 있다. 하지만 반드시 바뀌어야하는 값에 저 기능을 사용하면 굳이 비교하지 않아도 될 것들에도 리액트가 비교를 하기 때문에 저 기능을 사용하기에 앞서, 불필요한 리렌더링이 맞는지, 개선할 수 있는 부분인지 확인해야한다.
  • setState로 생성하지 않고 let으로 선언하는 원시타입데이터는 리랜더링 되지 않기 때문에 useMemo를 쓰지 않아도 된다.

오늘은 어제 들었던 심화강의를 이해해보는 시간을 가졌다. 생활코딩 강의를 참고하여 이해해보니, 어떤 개념인지 이해가 되었다. 어제까지만 해도 무슨말인지 잘 몰랐던 것도 계속 반복하다보니 무슨말인지 이해가 되는 것이 신기하다. 물론 예시코드로 공부한 것이어서 복잡한 코드를 보게되면 다시 헷갈릴지 모르지만 그러면서 정확하게 이해가 되는 거니까 더 많은 예시로 공부해봐야 겠다.
내일부터 프로젝트가 시작된다. 오늘까지 공부한 내용들을 잘 활용해서 프로젝트에 적용해보고 싶다.

profile
코딩은 짜릿해 늘 새로워 ✨

1개의 댓글

comment-user-thumbnail
2022년 12월 22일

이제 진짜 개발자가 되신것같아요
정리도, 생각도, 학습능력도 다 올라가고계신것같아 너무 보기좋네요
플젝도 화이팅입니다!!

답글 달기