React/게시판만들기 v1.

[React][CRUD] 리액트로 간단한 게시판 페이지 만들기 - 17. (2) createSelector를 이용하여 게시판 목록에 댓글 개수 띄우기, redux-toolkit, reselect

binaryJournalist 2020. 11. 5. 09:17
반응형

 

 

이전글:

2020/11/04 - [React] - [React][CRUD] 간단한 게시판 페이지 만들기 - 17.(1) 게시판 목록에 댓글 개수 띄우기, redux-saga, axios

 

[React][CRUD] 간단한 게시판 페이지 만들기 - 17.(1) 게시판 목록에 댓글 개수 띄우기, redux-saga, axios

목차 돌아가기: binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial [React][CRUD] create-board-tutorial code: github.com/jwlee-lnd/react-create-board jwlee-lnd/react-create-board Descrip..

binaryjourney.tistory.com

 

 

목차 돌아가기: 

binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial

 

[React][CRUD] create-board-tutorial

code: github.com/jwlee-lnd/react-create-board jwlee-lnd/react-create-board Description(korean) : https://binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial - jwlee-lnd/react-create-boar..

binaryjourney.tistory.com

 

 

 

 

 

redux-toolkit 에서 createSelector 메소드를 제공하는데 이는 reselect의 createSelector 메소드를 redux-toolkit 이 다시 export 해주는 것이다.

 

아래 API를 보면 알 수 있다.

 

redux-toolkit.js.org/api/createSelector

 

createSelector | Redux Toolkit

createSelector

redux-toolkit.js.org

 

 

 

createSelector는 state 내 지정된 값을 참조하여 새로운 값을 만든 후 변수에 넣어 저장하게 해주어 memoization 기능을 나름 제공한다.

 

kyounghwan01.github.io/blog/React/redux/redux-toolkit/#reselect

 

Redux Toolkit을 사용하여 간단하게 상태 관리하기

Redux Toolkit을 사용하여 간단하게 상태 관리하기, reselect, redux, react, immer, redux-action, FSA

kyounghwan01.github.io

 

velog.io/@ksh4820/react-redux-reselect-%ED%8C%A8%ED%82%A4%EC%A7%80

 

react-redux, reselect 패키지

npm install react-reduxProvider 컴포넌트Provider 컴포넌트 하위에 있는 컴포넌트는 리덕스의 상태 값이 변경되면 자동으로 컴포넌트 함수가 호출되도록 할 수 있다. store 객체를 Provider의 속성 값으로 전

velog.io

 

 

위 블로그는 createSelector를 사용하는 방법과 reducer에서 데이터를 가공하는 것보다 createSelector를 이용하여 데이터를 가공하는 것이 더 안전하다는 설명이다.

 

 

 

 

위 블로그 두 개가 createSelector에 대한 개념설명이 조금씩 차이가 있는데

 

 

첫째는 createSelector를 사용하지 않는다면 store가 업데이트 될 때마다 매번 댓글 개수를 불필요한 연샨을 하여 컴포넌트에 넘길텐데 createSelector 는 이전에 연산된 값을 캐시에 저장하여 비교해주어 값이 바뀌지 않았을 때는 값을 넘기지 않아서 낭비를 줄인다는 것이다. 

 

둘째는 createSelector가 연산에 사용되는 데이터가 변경되는 경우에만 연산을 수행하고, 변경되지 않은 경우에는 이전 결과 값을 그대로 사용한다는 것이다.

 

 

정확한 개념을 아는 사람이 있다면 꼭 댓글로 달아주길 바란다...

 

 

 

 

나 같은 경우 댓글 개수를 세는 createSelector를 만들기는 어려운 환경이었다. 테이블이 굉장히 한정되어 있고 서버쪽에서 필요한 데이터만 넘길 수가 없었기 때문이다. 그래서 일단은 아래같이 만들었다.

 

createSelector

위 코드는 게시글과 댓글을 참조하여 게시글 id와 같은 댓글 id의 개수를 세는 코드이다.

 

 

 

만약 첫번째 개념이라면 내 코드에서 board의 article에서 댓글을 달지 않는 한 수정하든 조회수가 변하든 불필요한 낭비가 없을 것이고

두번째 개념이라면 board의 state는 계속 바뀌고 있기 때문에 createSelector를 쓴 의미가 없게 된다.

 

 

 

 

stackoverflow.com/questions/52607573/how-does-redux-reselect-memoization-work

 

How does Redux Reselect memoization work?

I am trying to integrate reselect into my current app and as always , first i begin to read documentation and then if it needed , another recources.I couldn't understand one special part of documen...

stackoverflow.com

 

 

위 사이트의 글을 보면 두번째 개념에 더 가까운 것 같다.

 

 

const mapStateToProps = (state, props) => { return { todos: getVisibleTodos(state, props) } }


const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)



return ( <div> <VisibleTodoList listId="1" /> </div> )

 

 

If we render the component twice with the same props:

 

  1. the selector gets called and result gets memoized

  2. the selector gets called a second time, and it sees that { listId: 1 } is the same prop arguments as the first time, so it just returns the memoized value.

 

If we render the component twice with different props:

 

  1. the selector gets called and the result gets memoized

  2. the selector gets called a second time, and it sees that { listId: 2 } is not the same props args as the first time { listId: 1 }, so it recalculates and memoizes the new result (todo list 2 object) in memory (overwriting the previous memoization).

 

 

여기서 말하는 props는 결국 useSelector 로 잡아오는 state 내 항목들을 의미하므로..

 

 

 

 

 

 

 

createSelector는 바로 값을 주지 않는다. 함수를 만들어주는 메소드이다.

 

 

그래서 useSelector안에 쓰거나 꼭 만든 형식에 맞게 인자값을 넣어주어야 한다.

 

 

createSelector useSelector

 

 

기존 useSelector 안에 넣어주고 shallowEqual을 써도 상관은 없다.

 

createSelector useSelector

 

 

 

 

 

 

아래는 createSelector를 이용하기 위해 바꾼 코드들이다.

 

// boardSaga

import { put } from "redux-saga/effects";
import Axios from "axios";
import { boardActions } from "../slice/boardSlice";
import { commentActions } from "../slice/commentSlice";

export function* getBoardAsync() {
  try {
    const responseForBoard = yield Axios.get(`http://localhost:4000/board/`);
    const responseForComment = yield Axios.get(
      `http://localhost:4000/comment/`
    );

    const boardData = responseForBoard.data;

    yield put(boardActions.getBoardSuccessAsync(boardData));
    yield put(commentActions.getCommentsAsync(responseForComment.data));
  } catch (e) {
    yield put(boardActions.getBoardFailedAsync(e.message));
  }
}

 

 

// BoardPage

import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import BoardList from "./Sections/BoardList";
import { Button, Typography } from "antd";
import { useDispatch, useSelector } from "react-redux";
import { boardActions } from "../../../slice/boardSlice";
import { articleActions } from "../../../slice/articleSlice";
import { createSelector } from "@reduxjs/toolkit";

const { Title } = Typography;

function BoardPage() {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(boardActions.getBoard());
  }, [dispatch]);

  const { board, isLoading, isSuccess, error } = useSelector((state) => ({
    board: state.boardReducers.board,
    isLoading: state.boardReducers.isLoading,
    isSuccess: state.boardReducers.isSuccess,
    error: state.boardReducers.error,
  }));

  const createCommentLength = createSelector( // 추가
    (state) => state.boardReducers.board,
    (state) => state.commentReducers.comments,
    (articles, comments) => {
      const commentByArticle = {};
      for (var index in articles) {
        debugger;
        if (!comments) return commentByArticle;

        const filteredComments = comments.filter(
          (comment) => comment.articleId === articles[index].id
        );
        commentByArticle[articles[index].id] = filteredComments.length;
      }
      return commentByArticle;
    }
  );

  const commentLength = useSelector(createCommentLength); // 추가

  const onDeleteClick = (id) => {
    if (!window.confirm("삭제하시겠습니까?")) return false;
    dispatch(articleActions.deleteArticle(id));
  };

  return (
    <div style={{ width: "80%", margin: "3rem auto" }}>
      <div>
        <Link to="/register?isForEdit=false">
          <Button type="primary">New Post</Button>
        </Link>
      </div>
      <div style={{ textAlign: "center", marginBottom: "2rem" }}>
        <Title>게시판</Title>
      </div>
      <div>
        {error ? (
          <h2>에러 발생: {error}</h2>
        ) : isSuccess && board.length > 0 ? (
          <BoardList
            board={board}
            commentLength={commentLength} {/* 추가된 부분 */}
            handleDeleteClick={onDeleteClick}
          />
        ) : isSuccess && board.length <= 0 ? (
          <p> 조회할 내용이 없습니다. </p>
        ) : (
          <p> 목록을 불러오는 중입니다. </p>
        )}
      </div>
    </div>
  );
}

export default BoardPage;

 

 

// BoardList

import React from "react";
import { Link } from "react-router-dom";
import { Button } from "antd";

function BoardList(props) {
  // console.log(props.board);

  return (
    <div>
      <table style={{ width: "100%" }}>
        <colgroup>
          <col width="10%" />
          <col width="70%" />
          <col width="10%" />
          <col width="10%" />
        </colgroup>
        <tbody>
          <tr>
            <th>번호</th>
            <th>제목</th>
            <th>조회수</th>
            <th></th>
          </tr>
        </tbody>
        <tbody>
          {props.board.map((article) => (
            <tr key={article.id}>
              <td>{article.id}</td>
              <Link to={`/article/${article.id}`}>
                <td>
                  {article.title}
                  &nbsp; {/* 수정된 부분 */}
                  {props.commentLength[article.id] > 0 &&
                    `[${props.commentLength[article.id]}]`}
                </td>
              </Link>
              <td>{article.views}</td>
              <td>
                <Button onClick={() => props.handleDeleteClick(article.id)}>
                  X
                </Button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default BoardList;

 

 

 

 

 

 

 

 

 

 

createSelector 의 구조를 뜯어보면 다음과 같이 생겼다.

 

 

/* homogeneous selector parameter types */
/* one selector */
export function createSelector<S, R1, T>(
  selector: Selector<S, R1>,
  combiner: (res: R1) => T,
): OutputSelector<S, T, (res: R1) => T>;
export function createSelector<S, P, R1, T>(
  selector: ParametricSelector<S, P, R1>,
  combiner: (res: R1) => T,
): OutputParametricSelector<S, P, T, (res: R1) => T>;

/* two selectors */
export function createSelector<S, R1, R2, T>(
  selector1: Selector<S, R1>,
  selector2: Selector<S, R2>,
  combiner: (res1: R1, res2: R2) => T,
): OutputSelector<S, T, (res1: R1, res2: R2) => T>;
export function createSelector<S, P, R1, R2, T>(
  selector1: ParametricSelector<S, P, R1>,
  selector2: ParametricSelector<S, P, R2>,
  combiner: (res1: R1, res2: R2) => T,
): OutputParametricSelector<S, P, T, (res1: R1, res2: R2) => T>;

/* three selectors */
export function createSelector<S, R1, R2, R3, T>(
  selector1: Selector<S, R1>,
  selector2: Selector<S, R2>,
  selector3: Selector<S, R3>,
  combiner: (res1: R1, res2: R2, res3: R3) => T,
): OutputSelector<S, T, (res1: R1, res2: R2, res3: R3) => T>;
export function createSelector<S, P, R1, R2, R3, T>(
  selector1: ParametricSelector<S, P, R1>,
  selector2: ParametricSelector<S, P, R2>,
  selector3: ParametricSelector<S, P, R3>,
  combiner: (res1: R1, res2: R2, res3: R3) => T,
): OutputParametricSelector<S, P, T, (res1: R1, res2: R2, res3: R3) => T>;

//...(생략)

 

 

(이건 같은 parameter를 이용할 때를 말하는 것 같은데)

 

selector로는 현재 12개까지 가능한 것으로 보인다

보면 selector1, selector2, .. 이것은 combiner에 들어갈 값을 지정해주는 것이다.

combiner의  res1, res2, ... 로 이름이 지어져서 들어간다.

 

 

createSelector

 

내 코드의 경우 첫째줄 state => , 이 부분이 articles 로 들어가고 둘째줄 state => , 이 부분은 comments 로 들어간다.

 

 

 

 

 

 

 

*(참고로 parameter 값이 다른 경우 output 부분이 조금 다르다)

 

 

/* heterogeneous selector parameter types */

/* one selector */
export function createSelector<S1, R1, T>(
  selector1: Selector<S1, R1>,
  combiner: (res1: R1) => T,
): OutputSelector<S1, T, (res1: R1) => T>;
export function createSelector<S1, P1, R1, T>(
  selector1: ParametricSelector<S1, P1, R1>,
  combiner: (res1: R1) => T,
): OutputParametricSelector<S1, P1, T, (res1: R1) => T>;

/* two selector */
export function createSelector<S1, S2, R1, R2, T>(
  selector1: Selector<S1, R1>,
  selector2: Selector<S2, R2>,
  combiner: (res1: R1, res2: R2) => T,
): OutputSelector<S1 & S2, T, (res1: R1, res2: R2) => T>;
export function createSelector<S1, S2, P1, P2, R1, R2, T>(
  selector1: ParametricSelector<S1, P1, R1>,
  selector2: ParametricSelector<S2, P2, R2>,
  combiner: (res1: R1, res2: R2) => T,
): OutputParametricSelector<S1 & S2, P1 & P2, T, (res1: R1, res2: R2) => T>;

/* three selector */
export function createSelector<S1, S2, S3, R1, R2, R3, T>(
  selector1: Selector<S1, R1>,
  selector2: Selector<S2, R2>,
  selector3: Selector<S3, R3>,
  combiner: (res1: R1, res2: R2, res3: R3) => T,
): OutputSelector<S1 & S2 & S3, T, (res1: R1, res2: R2, res3: R3) => T>;
export function createSelector<S1, S2, S3, P1, P2, P3, R1, R2, R3, T>(
  selector1: ParametricSelector<S1, P1, R1>,
  selector2: ParametricSelector<S2, P2, R2>,
  selector3: ParametricSelector<S3, P3, R3>,
  combiner: (res1: R1, res2: R2, res3: R3) => T,
): OutputParametricSelector<S1 & S2 & S3, P1 & P2 & P3, T, (res1: R1, res2: R2, res3: R3) => T>;

 

 

 

 

 

 

 

 

그럴 일이 있을까 싶지만 만약 selector 개수가 12개가 넘어갈 때는 배열로 selector를 묶을 수도 있다

 

/* one selector */
export function createSelector<S, R1, T>(
  selectors: [Selector<S, R1>],
  combiner: (res: R1) => T,
): OutputSelector<S, T, (res: R1) => T>;
export function createSelector<S, P, R1, T>(
  selectors: [ParametricSelector<S, P, R1>],
  combiner: (res: R1) => T,
): OutputParametricSelector<S, P, T, (res: R1) => T>;

/* two selectors */
export function createSelector<S, R1, R2, T>(
  selectors: [Selector<S, R1>,
              Selector<S, R2>],
  combiner: (res1: R1, res2: R2) => T,
): OutputSelector<S, T, (res1: R1, res2: R2) => T>;
export function createSelector<S, P, R1, R2, T>(
  selectors: [ParametricSelector<S, P, R1>,
              ParametricSelector<S, P, R2>],
  combiner: (res1: R1, res2: R2) => T,
): OutputParametricSelector<S, P, T, (res1: R1, res2: R2) => T>;

/* three selectors */
export function createSelector<S, R1, R2, R3, T>(
  selectors: [Selector<S, R1>,
              Selector<S, R2>,
              Selector<S, R3>],
  combiner: (res1: R1, res2: R2, res3: R3) => T,
): OutputSelector<S, T, (res1: R1, res2: R2, res3: R3) => T>;
export function createSelector<S, P, R1, R2, R3, T>(
  selectors: [ParametricSelector<S, P, R1>,
              ParametricSelector<S, P, R2>,
              ParametricSelector<S, P, R3>],
  combiner: (res1: R1, res2: R2, res3: R3) => T,
): OutputParametricSelector<S, P, T, (res1: R1, res2: R2, res3: R3) => T>;

// ... 생략

 

 

 

 

나도  createSelector는 생소한 개념이라 다음주부터 본격적 개발 들어가고 더 알게 되면 포스팅을 새로 하겠다.

 

 

댓글 개수 나타내기는 이것으로 마치겠다.

 

 

 

 

목차 돌아가기: binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial

 

[React][CRUD] create-board-tutorial

code: github.com/jwlee-lnd/react-create-board jwlee-lnd/react-create-board Description(korean) : https://binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial - jwlee-lnd/react-create-boar..

binaryjourney.tistory.com

 

반응형