DogKaeBi

[Next.js 블로그] 한자 페이지. 동적 라우팅의 데이터

동적 라우팅으로 이동한 페이지에서 데이터를 사용하고 매치하는 것에 대해서 많이 고민했다.

[Next.js 블로그] 한자 페이지. 동적 라우팅의 데이터

참고 내용

아직 DB를 사용하지 않아서
임시 테스트 데이터를 사용하고 있다.

const tempTc = [
  { sortId: "0", tc: "零", jyutJam: "ling4 lin4", title: "숫자 0" },
  ... ,
  { sortId: "10", tc: "十", jyutJam: "sap6", title: "숫자 10" }];

const tempWord = [
  { sortId: "0", tc: "你好", jyutJam: "nei5 hou2", title: "안녕하세요" },
  ... ,
  { sortId: "10", tc: "再見", jyutJam: "zoi3 gin3", title: "또 만나요" },
];

현재 한자 카드의 링크는

<Link href={`/cantonese/${data.sortId}`}>...</Link>


한자 사전 페이지

동적 라우팅 page.js 파일

[Dynamic Routes 편]
[Next.js 정식 설명]

전에 이미 만들었지만 다시 설명.

.\app\cantonese\\[id]\page.js 를 만들었다.

/cantonese 는 리스트 페이지이고
/cantonese/id 는 상세 페이지이다.

현재 cantonese 폴더 내용 :

app
└ cantonese
  └ word
  |  └ page.js
  └ [id]
  |  └ page.js
  └ page.js

동적 라우팅은 대괄호으로 사용한다.
대괄호 안의 내용을 변수로 받고,
페이지 UI는 폴더 안의 page.js를 보여준다.


Dictionary 컴포넌트 기본틀

// ..src\app\cantonese\[id]\page.js

export default function Dictionary(props) {
  return <> {props.params.id} </>;
}

기본틀도 이전에 만들었다.
이제 사전에서 보여줄 데이터를 정리하자...

현재는
소팅, 한자, 발음, 타이틀이 있다.
추가로 설명분류가 필요하다.

설명의 경우, 발음에 따라서 뜻이 다르고
같은 발음도 여러 뜻이 있을 수 있다.

{
  sortId: "0",
  tc: "零",
  jyutJam: "ling4 lin4",
  title: "숫자 0",
  category: "숫자",
  mean: {
    ling4: ["숫자 0", "떨어지다", "적은 수량"]
    lin4: ["부족 이름"]
  }
}

데이터 읽기

문제는 여기다.

  1. 어떻게 여기서 데이터를 사용하나?
  2. 현재 페이지의 데이터를 어떻게 찾을 것인가?

이전 페이지에서 이미
getCardData를 사용해서
모든 데이터를 읽었다.

하지만 지금 다른 페이지로
해당 데이터를 가지고 있지 않다.

생각나는 방법은

  1. 다시 데이터를 읽는다
  2. 데이터를 부모에서 전달한다

2번의 방법은

  1. local storage 혹 session을 사용
  2. 주소에 쿼리의 형태로 데이터를 입력

문제는 1번의 경우...
반드시 이전 페이지에서 들어와야 한다.

2번의 경우 url이 너무 길어지고
해당 내용과 관련이 없어질 수 있다.
(애초 sortId를 사용해서 관련성은 없지만)


결국은...
임시 데이터이기 때문에
다시 읽는 방식을 택했다.

하지만 실제로 사용할 때는 서버 비용의 물 먹는 하마가 될 수 있을 것 같다.


데이터 매치

지금 tempTc 임시 데이터는 배열이다.
배열에서 같은 sortId를 찾는 방법을 사용할 수 있다.

find()함수를 사용해봤다.

const data = tempTc.find((tc) => tc.sortId == props.params.id);

반복문이어서
요소를 찾을 때까지 반복한다.

...부하가되는 작업은 아니다.
애초 변수도 tempTc 하나을 읽는 것이다.
단 찾는 번호가 마지막이면..
처음부터 마지막까지 반복으로 비교를 한다.

그래서 생각한 방법으로
tc (한자)를 주소로 사용하고
tempTc를 객체로 만들어서
tc를 key로 사용하는 방법을 생각했다.

추가로
데이터를 별도의 파일로 만들었다.
Json파일으로 만드는 것이 더 적합할 수 있지만.. 우선 js파일로 만들었다.

// .\public\data.js

export const tempTc = {
  "零" : {
    sortId: "0",
    tc: "零",
    jyutJam: "ling4 lin4",
    title: "숫자 0",
    category: "숫자",
    mean: {
      ling4: ["숫자 0", "떨어지다", "적은 수량"]
      lin4: ["부족 이름"]
    }
  },
  ...
}

데이터 매치는 많이 간단해진다.

const data = tempTc[props.params.id];

tempTc 전체를 사용하는 것은 차이가 없다.
그저... json으로 받는 다고 가정하면
해당 데이터만 반환하기 때문에 조금(?아주 조금) 효율적이지 않을 까 싶었다.

데이터를 변경했기 때문에
해당 링크를 사용하는 페이지도 변경해야 한다.

// .\app\component\CnCard.js - Link 부분

<Link href={`/cantonese/${data.tc}`}>...</Link>

페이지 코드

// ..src\app\cantonese\[id]\page.js

import { tempTc } from "./data";

export default function Dictionary(props) {
  const data = tempTc[props.params.id];
  if (data == undefined || data == null ) {
    return <>{props.params.id}의 데이터가 없습니다.</>
  }

  const jyutJamArr = data.jyutJam?.split(" ");
  let meanCount = 0;

  return (
    <>
      <h1>{data.tc}</h1>
      <p>월음 : {data.jyutJam}</p>
      <p>분류 : {data.category}</p>
      <p>뜻</p>
      {jyutJamArr.map((jyutJam)=> {
        return (
          <p key={"jyut-", jyutJam}>{jyutJam}</p>
          {data.mean[jyutJam].map((mean) => {
              meanCount++;
              return <p key={meanCount}>{mean}</p>
          })}
        )
      })}
    </>
  );
}

데이터를 매치한다

const data = tempTc[props.params.id];

매치된 데이터가 없으면 오류 문자를 출력한다.

if (data == undefined || data == null) {
  return <>{props.params.id}의 데이터가 없습니다.</>;
}

월음을 분리한다

const jyutJamArr = data.jyutJam?.split(" ");

임시 뜻 번호 변수를 선언한다.

let meanCount = 0;

순서대로 데이터를 출력한다

return (
  <>
    <h1>{data.tc}</h1>
    <p>월음 : {data.jyutJam}</p>
    <p>분류 : {data.category}</p>
    ...
  </>
);

풀이 부분은 월음을 반복해서 월음을 표기한다

<p>뜻</p>;
{
  jyutJamArr.map((jyutJam) => {
    return (
      <p key={("jyut-", jyutJam)}>{jyutJam}</p>
      ...
    )
  });
}

data에서 같은 월음을 찾아서 반복한다.
반복할 때 meanCount을 추가해서 key값으로 전달한다.
반복한 요소(뜻)을 출력한다.
( jyutJam은 이전 반복문의 요소이다 )

{
  data.mean[jyutJam].map((mean) => {
    meanCount++;
    return <p key={meanCount}>{mean}</p>;
  });
}

단어

단어와 한자는
key값이 다르기 때문에
같은 페이지를 사용할 수 있을 것 같다.

데이터 매치를 할 때,
한자와 단어 데이터로 매치해야 한다.

const data = tempTc[props.params.id] ?? tempWord[props.params.id];

단어의 경우 한자와 다르게
발음을 분리할 필요가 없다.

그래서 밑 "뜻" 부분을
Dictionary 컴포넌트 안에
TcContent 컴포넌트와
WordContent 컴포넌트를 만들었다

function TcContent(){
  return
  { jyutJamArr.map((jyutJam)=> {
    return (
      <p key={"jyut-", jyutJam}>{jyutJam}</p>
      { data.mean[jyutJam].map((mean) => {
        meanCount++;
        return <p key={meanCount}>{mean}</p>
      })}
    )
  })}
}

function WordContent(){
  {data.mean[jyutJam].map((mean) => {
    meanCount++;
    return <p key={meanCount}>{mean}</p>
  })}
}

한자 vs 단어 구분 변수도 추가했다.

const isTc = tempTc[props.params.id] != null;


결과

// ..src\app\cantonese\[id]\page.js

import { tempTc } from "./data";

export default function Dictionary(props) {
  const id = props.params.id;
  const data = tempTc[id] ?? tempWord[id];
  if (data == undefined || data == null ) {
    return <>{props.params.id} 데이터 없음</>
  }
  let meanCount = 0;

  return (
    <>
      <h1>{data.tc}</h1>
      <p>월음 : {data.jyutJam}</p>
      <p>분류 : {data.category}</p>
      <p>뜻</p>
      { isTc ? <TcContent/> : <WordContent/> }
    </>
  );


  function TcContent(){
    const jyutJamArr = data.jyutJam?.split(" ");
    return
    { jyutJamArr.map((jyutJam)=> {
      return (
        <p key={"jyut-", jyutJam}>{jyutJam}</p>
        { data.mean[jyutJam].map((mean) => {
          meanCount++;
          return <p key={meanCount}>{mean}</p>
        })}
      )
    })}
  }

  function WordContent(){
    {data.mean[jyutJam].map((mean) => {
      meanCount++;
      return <p key={meanCount}>{mean}</p>
    })}
  }
}

결과적으로는
원하는 데이터가 출력된다.

하지만 데이터 사용 효율은
데이터 베이스를 선택할 때,
더 많은 고민을 해야할 것 같다.