React 8분 읽기

리덕스 사용해보기

이전 아고라스테이츠 리액트 프로젝트에서 문제가 되었던 것은 상태 끌어올리기로 인한 props drilling이었습니다. 제일 먼저 손을 봐야 하는 것이 현재 로그인 된 유저를 저장한 상태였는데요. 이것을 리덕스를 이용해서 바꿔보도록 하겠습니다.

이전 아고라스테이츠 리액트 프로젝트에서 문제가 되었던 것은 상태 끌어올리기로 인한 props drilling이었습니다. 제일 먼저 손을 봐야 하는 것이 현재 로그인 된 유저를 저장한 상태였는데요. 이것을 리덕스를 이용해서 바꿔보도록 하겠습니다.

리덕스 사용 전

로그인 된 유저 변수의 변화

초기 자바스크립트 아고라스테이츠

export const user = (() => {
  let currentUser = "";
  let observers = [];
  return {
 	getSubscribers() {
    	return observers;
   	},
   	subscribe(component) {
    	observers.push(component);
    },
    unSubscribe(component) {
      observers = observers.filter((a) => a !== component);
    },
  	notifyAll() {
  		observers.map((a) => a.setState({ userId: currentUser }));
    },
    getCurrentUser() {
      return currentUser;
    },
    setCurrentUser(userId) {
      currentUser = userId;
    },
  };
})();

자바스크립트로 아고라스테이츠를 구현했을 때 전역에서 user의 정보를 관리해야 그에 맞게 여러 컴포넌트가 리렌더링 될 수 있습니다. 컴포넌트가 많고 코드가 복잡해지다보니 이를 해결하기 위해서 observer pattern을 이용했는데, 리액트는 리렌더링 기능을 알아서 수행해주다보니 그 기능이 빠지고, user를 상태로 관리하게 되었습니다.

리액트

위의 이미지를 보면 유저가 로그인하는 순간 거의 모든 컴포넌트가 리렌더링 되는 것을 볼 수 있습니다. 그 이유는 현재 로그인 된 유저를 저장하는 user라는 상태가 최상위 컴포넌트인 App컴포넌트에서 props로 내려주기 때문이죠.

props를 계속 전달하기 때문에 리렌더링이 필요없는 컴포넌트도 다 리렌더링이 되는 문제가 있습니다. 실제로 user라는 상태를 사용하는 컴포넌트는 AsideHeader, MainDiscussionModal컴포넌트 정도인데, 위의 사진에서는 Main컴포넌트 자체도 렌더링이 되고 있습니다.

function App() {
  const [user, setUser] = useState("");

  useEffect(() => {
    storageAPI.setInitialStorage();
  }, []);
  return (
    <div className="App">
      <div id="wrapper">
        <div className="container">
          <Header user={user} setUser={setUser} />
          <Main user={user} />
        </div>
      </div>
    </div>
  );
}

위의 방식으로 user상태를 props를 계속 내려주고 있습니다. 이 상태를 리덕스로 관리해보겠습니다.

store

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "../features/userSlice";

export default configureStore({
  reducer: {
    user: userReducer,
  },
});

redux-toolkit을 이용해서 상태를 보관할 스토어를 만들어줍니다. 현재 리덕스로 관리할 상태는 user 하나기 때문에 위와 같이 작성했습니다.

reducer, actions

상태 변경에 필요한 reducer와 action함수를 정의합니다.

import { createSlice } from "@reduxjs/toolkit";

export const userSlice = createSlice({
  name: "user",
  initialState: "",
  reducers: {
    signIn(state, { payload }) {
      return payload;
    },
    logOut(state) {
      return "";
    },
  },
});

export const { signIn, logOut } = userSlice.actions;
export default userSlice.reducer;

이제 serUser의 역할을 signInlogOut을 이용해 구현한 상태가 되었습니다. 원하는 시점에 위 함수를 호출하면 user의 값이 그에 맞게 변하게 될 것입니다.

그렇다면 이제 user를 사용하지 않지만 하위 컴포넌트에게 props로 넘겨주기 위해서 짠 코드를 수정해보겠습니다.

리덕스 적용시키기

function App() {
  useEffect(() => {
    storageAPI.setInitialStorage();
  }, []);
  return (
    <div className="App">
      <div id="wrapper">
        <div className="container">
          <Header />
          <Main />
        </div>
      </div>
    </div>
  );
}

이제 최상위 컴포넌트에서는 user상태를 관리할 필요가 없기 때문에 상태를 지웠습니다. 이에 따라서 HeaderMain컴포넌트도 props를 전달받지 않게 되었죠.

export default function Header() {
  const user = useSelector((state) => state.user);
  const dispatch = useDispatch();
  const [show, setShow] = useState("");
  const onSignupClick = () => {
    setShow("SIGN UP");
  };
  const onSigninClick = () => {
    setShow("SIGN IN");
  };
  const onLogoutClick = () => {
    dispatch(logOut());
  };

해더 컴포넌트의 상태 정의부를 보게되면 위와 같습니다. 스토어에 있는 user를 가져다 쓰고 로그아웃이 필요한 경우 dispatch를 이용해서 기능을 수행합니다.

또한, Main 컴포넌트는 user상태를 사용하지 않고 자식에게 넘겨주기만 했기 때문에 아예 user와 관련된 부분을 지울 수 있었습니다.

Main 컴포넌트의 자식 중 user 상태가 필요한 곳에서 위의 코드처럼 스토어에서 가져와 코드를 처리할 수 있습니다.

결과

리덕스를 적용했으니 기능이 잘 동작하는 지, 렌더링은 어떻게 줄었는지 확인해봅시다.

로그인 로그아웃 기능도 잘 되고, 렌더링 되는 부분도 줄었습니다. 이전에 props의 변화로 인해 쓸데없이 렌더링 되던 DiscussionsSection컴포넌트가 아예 리렌더링되지 않고 있죠.

이렇게 리덕스를 사용하면 props drilling과 상태 끌어올리기로 인해 관리가 어렵던 상태를 쉽게 관리하면서 동시에 필요없는 렌더링도 줄일 수 있는 효과를 볼 수 있습니다.