Today Sangmin Learned
article thumbnail
728x90

이번 모각코 시간에는 CRA + 타입스크립트로 만든 관련용어 기능을 Next.js로 옮기는 작업을 했다.

다른 팀원과 협업을 하면서 폴더 구조에 대한 얘기를 했는데, 내가 CRA에서 혼자 프로젝트를 진행할 때와는 좀 달랐다. 팀원이 현직자인데, 물론 각자의 스타일이 다 있겠지만 아무래도 실무에서 직접 쓰이는 부분이라고 하니 웬만하면 그대로 따라야겠다고 생각했다.

 

이전에 Next.js가 CRA에 비해 가진 장점을 포스팅했을 때, 라우팅 처리가 따로 필요가 없다고 했었다. pages 내부에 폴더 구조가 그대로 URL이 된다. 예를 들면 pages/Terms/New.tsx 를 만들었다고 해보자. 이제 localhost:3000/Terms/New 에 들어가면 그 New.tsx에 넣은 내용물들이 그대로 나온다.

 

우리 프로젝트의 경우에는, 우선 pages/AA/BB.tsx 이렇게 경로를 정한 뒤 이 BB.tsx에 페이지 전체의 내용을 띄워줄 스크린 페이지를 연결시킨다. 음 CRA로 따지자면 App.tsx에 라우팅 처리를 통해 페이지를 연결하고, 그 페이지에서 컴포넌트들을 띄워주는 것이 아닐까?

 

이런 형태라고 보면 된다. screen 폴더에는 각 페이지별로 스크린을 만든다. 예를 들면 나는 용어 생성 페이지를 담당했으므로 screen/TermScreen/TermScreen.tsx 파일을 만들고 그 안에 각종 컴포넌트들을 불러오는 느낌이다.

import React, { FC } from 'react'
import TermRelated from 'components/Terms/TermRelated'
import { css } from '@emotion/react'
import InputTitle from 'components/Terms/InputTitle'
// import { ControlledUsage } from 'components/Terms/ControlledUsage'

const termScreenStyle = css`
  padding: 0 21%;
`

const TermScreen: FC = () => {
  return (
    <div css={termScreenStyle}>
      <InputTitle />
      {/* <ControlledUsage /> */}
      <TermRelated />
    </div>
  )
}

export default TermScreen

여기에서 InputTitle과 TermRelated는 각각 컴포넌트들이다. TermRelated는 내가 오래전에 삽질하면서 조금씩 만들었지만 몇달이 지나고서 본격적으로 해보니까 하루이틀만에 끝낼 수 있었던 관련용어 기능을 구현한 컴포넌트이다.

 

import React, { useState } from 'react'
import { css } from '@emotion/react'

const listContainerStyle = css`
  width: 100%;
  overflow-x: scroll;
  padding-bottom: 30px;
`

const listTermsStyle = css`
  width: 100%;
  padding: 0;
`

const textInputStyle = css`
  background-color: #fcfcfc;
  border: 1px solid #efefef;
  padding: 0 16px;
  font-size: 14px;
  width: 364px;
  height: 52px;
`

const relatedTermsStyle = css`
  padding: 8px 12px;
  float: left;
  border-radius: 18px;
  font-weight: 500;
  min-width: fit-content;
  margin: 5px 5px 0px 0px;
  color: white;
  background-color: #7d68ff;
  list-style: none;
`

const buttonInRelatedTermsStyle = css`
  margin-left: 5px;
  background-color: transparent;
  color: yellow;
  border: 0;
  outline: 0;
`

const submitButtonStyle = css`
  float: right;
  background-color: #654bff;
  border: none;
  font-size: 18px;
  color: white;
  border-radius: 10px;
  padding: 18px 36.5px;
`

const TermRelated = () => {
  interface ITerms {
    id: number
    text: string
  }
  const [relatedTerms, setRelatedTerms] = useState<ITerms[]>([])
  const [inputTerm, setInputTerm] = useState('')
  const [id, setId] = useState(1)

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const lastIdx = e.target.value.length - 1
    if (e.target.value[lastIdx] === ',' || e.target.value[lastIdx] === ' ')
      return
    setInputTerm(e.target.value)
  }
  const onClick = () => {
    for (let i = 0; i < relatedTerms.length; i++) {
      if (relatedTerms[i].text === trimmedInputTerm) {
        setInputTerm('')
        alert('중복')
        return
      }
    }
    const termsArray = relatedTerms.concat({
      id: id,
      text: inputTerm.trim().replace(/[^ㄱ-힣a-zA-Z0-9+#]/gi, ''),
    })
    setRelatedTerms(termsArray)
    setId(id + 1)
    setInputTerm('')
  }

  const trimmedInputTerm = inputTerm.trim().replace(/[^ㄱ-힣a-zA-Z0-9+#]/gi, '')

  const onKeyUp = (e: React.KeyboardEvent<HTMLDivElement>): void => {
    if (
      (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 32) &&
      trimmedInputTerm
    ) {
      onClick()
    }
  }

  const onRemove = (id: number): void => {
    const termsArray = relatedTerms.filter(
      (relatedTerms: ITerms) => relatedTerms.id !== id,
    )
    setRelatedTerms(termsArray)
  }
  const relatedTermsList = relatedTerms.map((relatedTerms: ITerms) => (
    <li css={relatedTermsStyle} key={relatedTerms.id}>
      {relatedTerms.text}
      <button
        css={buttonInRelatedTermsStyle}
        onClick={() => onRemove(relatedTerms.id)}
      >
        X
      </button>
    </li>
  ))

  const onSubmit = (e: any) => {
    e.preventDefault()
  }
  return (
    <>
      <div id="container">
        <h2>관련 용어</h2>
        <input
          id="text_input"
          css={textInputStyle}
          placeholder="관련 있는 용어를 입력해주세요"
          value={inputTerm}
          onChange={onChange}
          onKeyUp={onKeyUp}
        />
        <div css={listContainerStyle} id="container2">
          <ul css={listTermsStyle} id="list_terms">
            {relatedTermsList}
          </ul>
        </div>
        <input
          css={submitButtonStyle}
          id="submit"
          type="submit"
          onSubmit={onSubmit}
          value="저장하기"
        />
      </div>
    </>
  )
}

export default TermRelated

스타일링은 styled-components와 비슷한 emotion을 사용하였고, 아래 형태와 같이 사용한다.

import React  from 'react'
import { css } from '@emotion/react'

const divStyle = css`
  display: block;
  margin: 0 auto;
`;

const textStyle = css`
  text-align: center;
`;

const Component: FC = () => {
return (
	<div css={divStyle}>
      <p css={textStyle}>Hello World!</p>
    </div>
  )
}

export default Component;

그래서, CRA + 타입스크립트로 만들었던 부분에서 CSS는 필요에 따라 가져왔다.

 

원래는 추가할 용어와 관련 용어 사이에 마크다운 에디터가 들어가 있어야 하는데, 현재 마크다운 에디터의 어느 부분에선가 오류가 생겨 추가를 하지 못하고 있다.

 

현재까지 한 부분은 우선 이렇고, 이제 관련용어 각각에 라우팅 처리를 하고, 마크다운 에디터 추가.. 그리고 REST API가 나오기 전에 axios와 SWR 처리를 해둬야한다. 갈 길이 멀다~

'today i learned' 카테고리의 다른 글

today i learned 8/7  (0) 2021.08.07
today i learned 8/6  (0) 2021.08.06
today i learned 8/5  (0) 2021.08.05
today i learned 8/4  (0) 2021.08.04
today i learned 8/3  (0) 2021.08.03
profile

Today Sangmin Learned

@steadily-worked

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!