18Day

김하은·2023년 2월 9일
0

중간평가이후..
오늘 검색 부분이 마지막 -> 내일부터는 중고마켓 시작

acync /await
JS동작원리
.
.
.

이제 이 뒷부분이 상당히 중요.


중고마켓에서는 커스텀 훅 패턴을 이용해 사용.
(컨테이너 프레젠터 패턴아님.)


사진 업로드 어렵다..


검색 => 검색 알고리즘 사용?

X 라이브러리자체에서,또는 DB에서 겁색시 옵션값바꿀수 있는데 그쪽부분에서 사용하고, 웹서비스에서는 알고리즘은 아니고 이미 DB에 내장되어있는 기능을 사용하는것!!

검색버튼 있는버전 & 없는버전

브라우저에서 검색버튼클릭 => 검색이 들어가고, fetchBoards 가 한번 더 요청이됨.
해당 키워드에 맞는것을 DB에서 다 뒤짐 ==> 모든 데이터를 다 뒤져서 가져오기에 느림.
-> 검색결과를 받으면 map으로 그려줌.

이후 구글에서
->> 단어별로쪼갠다. => 쪼개서 각 테이블에 저장한다.
단어별로 해서 각 단어에 해당하는 해당 게시물을 넣어서...

=> 이 방식을 역 인덱스 방식이라고 한다.
인덱스 = 색인
역인덱스 => 책 뒷부분에 가 , 나 , 다 해서 나눠져있는것 => 이렇게쪼개진것을 토큰이라고함.

토큰

토큰 단위로 쪼갬 => 토크나이징(토큰화한다)
토크나이징해서 토큰들을 저장해 역인덱스형태로 만듬 ==> 번거로움

해당 테이블 생성을 대신 해주는 회사 등장

: 엘라스틱서치(데이터베이스 회사)
저장과 조회 => 디스크에 input, output = DISK I/O. ===> 디스크에 저장 = 느림
엘라스틱이 DISK I/O 발생하기에 좀 느림. (대신 컴퓨터 껐다켜도 기록 사라지지 않음)

-> 이후 Redis라는것 등장
=> 메모리(RAM)에 저장함 -> 키 ,vlaue 형태로 저장하고 .. 훨씬 빠름 . 다만 메모리 저장이기에 컴퓨터 껐다키면 삭제됨.
===> 모든 데이터를 저장하지않고, 자주검색되는것만 캐싱(임시저장)해놓음 -> 디스크 I/O 발생 안함.(빠름)

=> 자주 검색되는것은 Redis에 있으니 여기서 먼저 확인 -> 여기 없으면 엘라스틱 서치로 가고, 해당 검색결과를 후에 검색할 사람을 위해 Redis에 저장해놓고(캐싱해놓고) -> 가져옴.

==>>> 물론 서비스 규모가 커질경우에 엘라스틱 서치, 레디스 가 사용되는것이고,
규모작을경우에 사용할 시 오버엔지니어링 임. 필요없는데 사용되어 유지보수 어려움.(규모적을시에는 그냥 DB도는게 더 나음)


버튼 눌러서 검색하기

버튼 없이 실시간으로 검색하기

import { useQuery, gql } from "@apollo/client";
import { useState } from "react";
import type { ChangeEvent, MouseEvent } from "react";
import type {
  IQuery,
  IQueryFetchBoardsArgs,
} from "../../../src/commons/types/generated/types";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int, $search: String) {
    fetchBoards(page: $page, search: $search) {
      _id
      writer
      title
      contents
    }
  }
`;

export default function MapBoardPage(): JSX.Element {
  const [search, setSearch] = useState("");
  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
(FETCH_BOARDS);
  console.log(data);

  const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => {
    void refetch({ page: Number(event.currentTarget.id) }); 
  // 검색에서 refetch를 할때 search검색어가 refetch에 이미 저장되어 있는 상태기에
    // 여기서 추가로 search포함하지 않아도됨
  };

  const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
    // setSearch(event.currentTarget.value);
    void refetch({ search: event.currentTarget.value, page: 1 }); // 버튼 없이 검색하기  ==> 문제: 검색한글자 입력시마다 뮤테이션 계속 날라감 ==> 트래픽 많아짐. 비용, 성능적으로 좋지 않음.
  };

  return (
    <div>
      검색어입력:
      <input type="text" onChange={onChangeSearch} />
      {/* <button onClick={onClickSearch}>검색하기</button> */}
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.writer}</span>
        </div>
      ))}
      {new Array(10).fill(1).map((_, index) => (
        <span key={index + 1} id={String(index + 1)} onClick={onClickPage}>
          {index + 1}
        </span>
      ))}
    </div>
  );
}

디바운싱 : 특정시간 이내 입력이 없을시 마지막 1회만 실행.(바뀐다음 마지막것만 실행하게) => 연속적인 쿼리요청을 막기위함

쓰로틀링(throwtling?): 특정시간이내 추가입력이 있어도 처음 한번만 실행(스크롤내릴때 등, 한반내렸을때나, 두번이나 같은...)

디바운싱 라이브러리 사용

yarn add lodash

yarn add --dev @types/lodash

import { useQuery, gql } from "@apollo/client";
import type { ChangeEvent, MouseEvent } from "react";
import type {
  IQuery,
  IQueryFetchBoardsArgs,
} from "../../../src/commons/types/generated/types";
import _ from "lodash";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int, $search: String) {
    fetchBoards(page: $page, search: $search) {
      _id
      writer
      title
      contents
    }
  }
`;

export default function MapBoardPage(): JSX.Element {
  // const [search, setSearch] = useState("");
  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
(FETCH_BOARDS);
  console.log(data);

  const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => {
    void refetch({ page: Number(event.currentTarget.id) }); // 검색에서 refetch를 할때 search검색어가 refetch에 이미 저장되어 있는 상태기에
    // 여기서 추가로 search포함하지 않아도됨
  };
  const getDebounce = _.debounce((value) => {
    /// 매개변수 value라는 이름으로 인자를 받아 사용
    void refetch({ search: value, page: 1 });
  }, 500); // 0.5초 이 시간안에 추가입력 없을때 refetch api요청
  const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
    getDebounce(event.currentTarget.value); // 얘를 해당 함수의 인자로 내보내고
  };
  return (
    <div>
      검색어입력:
      <input type="text" onChange={onChangeSearch} />
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.title}</span>
          <span style={{ margin: "10px" }}>{el.writer}</span>
        </div>
      ))}
      {new Array(10).fill(1).map((_, index) => (
        <span key={index + 1} id={String(index + 1)} onClick={onClickPage}>
          {index + 1}
        </span>
      ))}
    </div>
  );
}

검색한것 색강조하기

=> 키워드를 기준으로 쪼개야함.

그런데, 키워드를 split의 중심으로 한다면 keyword가 안나옴.

따라서 replace하여 state에 담은 검색 키워드양옆에 시크릿코드를 붙이고 해당 시크릿코드 기준으로 split하기

replaceAll을 사용하여 해당 키워드양옆에 시크릿코드를 붙여줌 (키워드가 여러군데 있을수 있으니 replace가 아니라 replaceAll사용)

yarn add uuid
yarn add --dev @types/uuid
import {v4 as uuidv4 } from 'uuid'

<span style={{ margin: "10px" }}>
          {el.title
            .replaceAll(keyword, `@#$#@$@#${keyword}@#$#@$@#`)
            .split("@#$#@$@#")
            .map((el) => (
              <span
                key={uuidv4()}
                style={{ color: el === keyword ? "red" : "" }}

                {el}
              </span>
            ))}
        </span>

전체코드

import { useQuery, gql } from "@apollo/client";
import type { ChangeEvent, MouseEvent } from "react";
import { useState } from "react";
import type {
  IQuery,
  IQueryFetchBoardsArgs,
} from "../../../src/commons/types/generated/types";
import _ from "lodash";
import { v4 as uuidv4 } from "uuid";
const FETCH_BOARDS = gql`
  query fetchBoards($page: Int, $search: String) {
    fetchBoards(page: $page, search: $search) {
      _id
      writer
      title
      contents
    }
  }
`;

export default function MapBoardPage(): JSX.Element {
  const [keyword, setKeyword] = useState("");
  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
(FETCH_BOARDS);
  console.log(data);

  const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => {
    void refetch({ page: Number(event.currentTarget.id) }); // 검색에서 refetch를 할때 search검색어가 refetch에 이미 저장되어 있는 상태기에
    // 여기서 추가로 search포함하지 않아도됨
  };
  const getDebounce = _.debounce((value) => {
    /// 매개변수 value라는 이름으로 인자를 받아 사용
    void refetch({ search: value, page: 1 }); // 버튼 없이 검색하기 
    setKeyword(value); // 검색한 결과를 담음.
  }, 500); // 0.5초 이 시간안에 추가입력 없을때 refetch api요청
  const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
    getDebounce(event.currentTarget.value); // 얘를 해당 함수의 인자로 내보내고
  };
  return (
    <div>
      검색어입력:
      <input type="text" onChange={onChangeSearch} />
      {/* 검색한 키워드 색바꾸기 => 해당 키워드를 기준으로 문장을 쪼개고, 해당 문장을 배열에 담음. 그리고 map으로 그림. 해당 키워드는 색을 변경
        1. 키워드 중심으로 문장 쪼개기,
        2. 키워드 저장할 state만들기
        3. 분리된 문장 map으로 뿌리고 각각의 el이 해당 키워드와 같으면 색 바뀌게...(강조색)
      */}
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>
            {el.title
              .replaceAll(keyword, `@#$#@$@#${keyword}@#$#@$@#`)
              .split("@#$#@$@#")
              .map((el) => (
                <span
                  key={uuidv4()}
                  style={{ color: el === keyword ? "red" : "" }}

                  {el}
                </span>
              ))}
          </span>
          <span style={{ margin: "10px" }}>{el.writer}</span>
        </div>
      ))}
      {new Array(10).fill(1).map((_, index) => (
        <span key={index + 1} id={String(index + 1)} onClick={onClickPage}>
          {index + 1}
        </span>
      ))}
    </div>
  );
}

0개의 댓글