배움 정리하기

그날그날 배운것들을 기록하자.

ref관련, 개발모드 상용모드 체크, 이미지태그와 백그라운드 이미지, 버튼 클릭 시 애니메이션, 인풋의 타입 관련, 메모리 누수 관련, 현재 route경로 찾기, history객체 타입 지정 관련, react-infinite-scroll, scroll restoration, iframe 관련 로딩 시 처리,

ref관련

리액트에서 dom에 직접접근을 하려고 할땐 ref를 사용해서 접근을 한다. map으로 요소들을 배열로 반환하면서 ref를 줄 상황이 생겨서 ref를 적용해보았다.

const Component = () => {
  const [data, setData] = useState([])
  const ref = useRef()

  return data.map(el => (
    <div>
      <input ref={ref} />
    </div>
  ))
}

처음에는 위와 같이 적용을 했었는데 위와같이 적용을 하게되면 결국에 하나의 요소만 ref에 담기더라 지금 생각해보면 당연한것같기도 하다.
ref는 다뤄볼 일이 없어서 익숙치도 않고, 이것저것 시도해보려고 알아보며 다른 방법도 시도해보았다.

const Component = () => {
  const [data, setData] = useState([])
  const [target, setTarget] = useState(null)

  return data.map(el => (
    <div>
      <input ref={setTarget} />
    </div>
  ))
}

이렇게 셋상태함수를 붙어주면 ref 요소를 callback으로서 사용한다고 하는데 Ref가 붙었을 때, 떼어졌을 때 무언가 업데이트 할 수 있는 방법을 리액트에서 제시하려고 만들었다고 한다. 적용 해보니 역시나 결과는 같았다. 여기에서 포기하지 않고 마법을 부려본다면 다음과 같다.

const Componenet = () => {
  const [data, setData] = useState([1, 2])
  let refs = useRef([React.createRef(), React.createRef()])

  return data.map((el, index) => (
    <div>
      <input ref={refs.current[index]} />
    </div>
  ))
}

기가막힌 코드가 완성되었다 refs값을 확인해보면 ref값들이 배열에 담겨져 있다. 이것을 조금 더 활용해본다면 다음과 같은 방법이 있다.

const Componenet = () => {
  const [data, setData] = useState([])

  useEffect(() => {
    // data 요청
  }, [])

  let refs = useRef(data.map(React.createRef))

  return data.map((el, index) => (
    <div>
      <input ref={refs.current[index]} />
    </div>
  ))
}

map함수는 배열을 반환하기에 데이타 수만큼 미리 ref가 담긴 배열을 만들어준다. 이렇게 활용할 수 있다. 멋진 코드가 완성이 되었다. 이러한 방법을 이곳저곳에 잘 활용해야겠다.

타입스크립트 모듈 import 관련

항상 ts파일에서 모듈을 불러오면 에러가 몇가지가 있었는데 왠만한건 다 해결했나 싶더니 아직도 에러를 불러오는 모듈이 있었다. 라이브러리 사용중

import library from 'library'

임포트헤서 불러오는데 모듈을 찾을 수 없다고 나온다. 그래서 @types 타입스크립트 버전이 있는지 봤는데 없는 라이브러리 였다. 일단 require로 땡겨와서 해결하는 방법도 있다. 그리고 새롭게 찾은 방법은 다음과 같다.
declaration.d.ts

declare module 'library-name'

이렇게 타입스크립트 선언파일에서 선언을 해주면 모듈을 찾지 못한다는 에러를 잡을 수 있다.

개발모드인지 상용모드인지 체크

특정 컴포넌트에서 현재 개발모드인지 상용모드인지 체크하려면 어떻게 해야할까? 궁금증이 생겨서 알아보게 되었다. 뭔가 적용을 해야할거같아서 알아보기 시작했는데, 결국 직접 적용해 보진 않고 다른 방법을 시도했기에 나중에 원활히 작동이 되는지 확인은 하지 못하였지만. 값체크는 했기에 정리를 해본다. 특정 컴포넌트에서

const Componenet = () => {
  function ReactIsInDevelomentMode() {
    return '_self' in React.createElement('div')
  }

  console.log(ReactIsInDevelomentMode())
}

위와 같은 함수를 작성했을때 development모드에서 React element는 _self라는 속성을 가지고 정의한다고 한다. production모드에서는 해당 속성이 정의되지 않는다고한다. 그래서 콘솔로 확인을 해보면 개발모드일때 true값이 나오는것을 확인할 수 있다. 따라서 그 값을 체크해서 활용하면 될 것 같다. 적용할 일이 생길때 한번 적용을 해보아야겠다.

이미지 태그와 백그라운드 이미지

이미지태그를 사용하면 이미지가 비율과 크기에 맞게 맞추어서 나온다. 넓이값을 조절하게되면 높이값도 비율을 맞추기 위해 조정된다. 물론 높이값을 정해주지 않을 경우이다. 백그라운드 이미지를 사용하게 되면 이미지가 정해준 넓이 높이 값에 따라 채워지게 되는데, 백그라운드 포지션 속성이나 백그라운드 사이즈 속성을 활용해 맞춰줄 수 있다. 이미지를 특정 박스에 맞추어 채우는데 가운데를 중심으로 박스에 오버되는 부분은 잘라내고자 하였다. 그렇게 하려면 img 태그에 src속성을 넣어주는 방법으로는 안될것 같았다. 그래서 백그라운드 속성을 활용하기로 하였는데.

.box {
background: url('url');
background-position: center;
background-repeat: no repeat;
background-size: cover;
...
}
}

이미지를 백그라운드로 채워주고 넓이 높이값을 맟춰 준 뒤 포지션 속성을 센터로 주어 센터를 중심으로 잡게끔 하였고, 작은 이미지들의 반복을 피하기위해 no repeat 속성을 주었으며, background-size : cover 속성으로 채워주었다. 이 과정에서 몇가지 알아낸 점들이 있다.

  1. 이미지 태그에 백그라운드 속성을 줄 경우 src가 비어있고 alt속성이 세팅되어있으면 엑박 이미지가 씌워진다. 어떻게 생각해보면 당연한 이치이다. src는 없으니 alt를 뱉어내는것.
  2. alt를 지운 뒤 이미지를 확인해보면 자동으로 보더가 씌워지며 css속성으로 없앨 수 없다. 이미지 태그자체에서 src가 없으니 이미지가 안나와서 꺠졌다는 UI를 보여주려고 자동으로 보더가 들어간다. img 태그에 백그라운드를 주어 사용했는데 div 태그로 바꿔주었다.

버튼 클릭 시 애니메이션

버튼 클릭 시 작아졋다가 커지고 살짝 흔들리며 이미지가 바뀌는 애니메이션을 구현하려고 하였다. 키프레임 애니메이션을 달아서 등록을 해야하는데 그냥 실행되는것이 아닌 클릭 시 실행되는것. 고민을 좀 해보다가 버튼에 ref로 접근을 하고 클릭 시 애니메이션 클래스네임을 정의한것을 달아줘보자고 하였다.

import React, { useRef } from 'react'

const Btn = () => {
  const btn = useRef(null)

  const handleClick = () => {
    btn.current.classList.remove('animation')

    btn.current.classList.add('animation')
  }

  return <button ref={btn} onClick={handleClick} />
}

export default Btn

위와같은 코드를 작성하고 적용해주니 클릭 시 애니메이션은 들어가나 재클릭 하였을때 다시 애니메이션이 발동되지 않았다. 정확한 이유는 찾지 못했다. 그러다가 찾게된 방법이 특성선택자를 활용하는 방법이었다. 그리고 onAnimationEnd속성을 활용한다. 특성 선택자를 활용한 바꾼 코드

import React, { useRef, useState } from 'react'

const Btn = () => {
  const btn = useRef(null)
  const [trigger, setTrigger] = useState(0)

  const handleClick = () => {
    setTrigger(1)
  }

  return (
    <button
      ref={btn}
      onAnimationEnd={() => {
        setTrigger(0)
      }}
      onClick={handleClick}
      trigger={trigger}
    />
  )
}

export default Btn

원리는 클릭했을때 트리거라는 상태를 0에서 1로 바꾼다. 그 값을 trigger속성에 담아서 element에 보내준다. 그 후 css 처리 방법

.btn {
  ... &[trigger='1'] {
    animation: ...animation;
  }
}

위와같이 정의해주었을떄 트리거값이 1일때 애니메이션이 요소에 달리게 된다. 유용하게 사용할 수 있을것 같지만 css특성선택자에 대해 잘 파악한것이 아니기에 파악 후 활용해야겠다.

인풋의 타입 관련

인풋의 타입에는 굉장히 많은 타입들이 있다. 무슨 차이가 있을까 궁금해서 얕게나마 알아보기로 하였다.

타입의 종류들

  • text 텍스트 입력 한 줄의 입력 필드를 정의 한다.
  • password password필드를 정의하며 별표나 문자가 동그라미 표시로 가려진다
  • submit 폼 핸들러에게 폼을 제출하는 버튼을 정의한다. 폼 핸들러는 일반적으로 입력 자료를 처리할 스크립트로 이루어진 서버페이지다.
  • radio 라디오 버튼을 정의한다.
  • checkbox 체크박스를 정의한다.
  • button 버튼을 정의한다.
  • number 숫자 값을 포함해야 하는 입력필드에 사용된다. 숫자의 한계도 설정할 수 있다.
  • color 색상을 포함해야 하는 입력 필드에 사용된다. 브라우저의 지원에 따라 색상 선택기가 입력 필드에 표시될 수 있다.
  • range 값이 범위안에 있어야 하는 입력 필드에 사용된다. 브라우저에 따라 입력 필드가 슬라이드 콘솔로 표시될 수 있다.
  • month 사용자가 월과 연도를 선택할 수 있게 해준다. 브라우저에 따라 날짜 선택기가 입력 필드로 표시될 수 있다.
  • week month와 동일하며 주와 연도를 선택
  • time 사용자가 시간을 선택할 수 있게 해준다.
  • datetime 서용자가 날짜와 시간을 선택할 수 있게 해준다.
  • email email주소를 포함해야 하는 입력 필드에 사용됨.
  • search 검색 필드에 사용된다. 일반 텍스트 필드처럼 동작함
  • tel 전화번호를 포함해야 하는 입력 릴드에 사용됨.
  • url URL주소를 포함하는 입력 필드에 사용된다. 폼이 제출될 때 필드의 값이 자동으로 유효성 검사가 될 수 있다.

종류가 굉장히 많다. 필요할때 적재적소에 찾아서 사용해야겠다.

매모리 누수 관련

개발을 하다보니

can`t perform a React state update on an unmounted component. ////

이런 쭉 나열된 에러가 뜨는데 대충 내용은 언마운트되었는데 왜 상태를 바꿔? 메모리가 누수가된다. 비동기 작업을 뒷정리 함수에서 처리해라! 이런 내용인것 같다. 뒷정리 함수? 스크롤 이벤트 적용애서 뒷정리 함수가 있어서 저 에러를 잡기 위해 계속 스크롤 이벤트만 만졌다. 안잡히더라. 그래서 멘탈이 상당히 나가있었다. 몇시간 동안 고민하다가 내린 결론.
저 에러는 라우팅을 추가 해준 다음 생긴 에러였다. 라우팅을 변경 시 콘솔에 떄때로 저 메시지가 찍혔는데 일정한 규칙없이 지멋대로 튀어나왔다. 계속 하단 네비게이션을 클릭하며 바꿧다 바꿧다 바꿧다 에러메시지만 확인했고 상태변경을 하나씩 주석처리하며 확인해본 결과 처음에 데이터를 가져오는 것에서 문제가 있다는 것을 알았다. 하지만 왜? 첫 마운트시 데이터를 요청하는건 아무 문제없었는데 왜 라우팅을 바꾸면 가끔 에러가 나올까 생각을 했다. 데이터 요청 함수는 대략 이러하다

try {
  const data = await axios.get('url')
  setData(data)
  const list = await axios.get('url')
  setList(list)
} catch (e) {
  console.log(e)
}

이 역시 아무 문제 없어보이는데 보이는 그대로 비동기api 요청 함수이다. 근데 계속 똑같은행동을 반복하다 찾은 규칙이 재빨리 왔다갔다 라우팅을 바꿀 때 에러메시지가 나왔다. 아마도 저 api비동기 요청이 끝나기 전에 라우팅을 바꾸면 없는상태를 바꾼다… 이런 에러인것 같다. 일단 이렇게 추측하고 넘어가기로 한다.

현재 route경로 찾기

네비게이션바 기능을 구현하면서 현재 경로가 어디인지 체크를 해야했다. 처음에는 단순히 window.pathname을 활용했을때 메인화면에서

/main
/main/

이렇게 두가지 종류로 잡힐때가 있어서 처리하기 번거로웠다 그래서 방법을 찾아보았다. react-router-dom 에 useLocation을 활용하였다.

import { withRouter, useLocation } from 'react-router-dom'

const nav = ({ history }) => {
  const location = useLocation()
}

이렇게 선언을 해주면 location에 히스토리 관련된 객체가 담기게 되며 현재 경로를 location.pathname로 접근할 수 있다.

import { withRouter, useLocation } from 'react-router-dom'

const nav = ({ history }) => {
  const location = useLocation()
  consolee.log(location)
  // url/main 이라면 /main
  // url/page 이라면 /page
}

이 값을 이용해 현재 경로를 추적하고 활용할 수 있따.

history객체 타입 지정 관련

history 를 인자로 받아오는 컴포넌트에서 자꾸 타입 문제가 생겼다. 그래서 찾아보다 적용한 방법. 일단 react-router-dom에서 RouteComponentProps를 불러온다. 가만 보면 그냥 타입용 인거같다.

import { withRouter, RouteComponentProps } from 'react-router-dom'

그 후 컴포넌트 선언시

const InterestItem: React.FunctionComponent<RouteComponentProps> = ({
  history,
    }) => {

  return ...
}

Rect.FunctionCOmponent에 제너릭으로 불러온것을 넣어준다. 히스토리 객체를이제 정상적으로 받는다.

react-infinite-scroll-component

리액트에서 무한 스크롤을 적용하는 과정중 선택지가 몇가지 있었다. 전에 기록했던 intersectionObserver를 사용하는 방법이 있고, 라이브러리를 사용할 수 있는데 일단 간단하게 라이브러리로 커버가 되면 라이브러리를 적용하려고 했다. 그러다 react-infinite-scroll-component라는 라이브러리를 발견했다. 사용법은 간단하다.

import InfiniteScroll from 'react-infinite-scro;;-component'

일단 모듈을 불러오고

<InfiniteScroll
...option // 각옵션들
>
{children} // 아이템요소들
</InfiniteScroll >

이러면 끝이다. 스크롤이 거의 바닥에 갈때쯤 새로 로딩을 해준다.

옵션들

const [list, setList] = useState([])
const [last, setLast] = useState(true)



...
<InfiniteScroll
dataLength={list.length} // 데이터의 길이
next={fetMore} // 바닥에 닿을 때 실행할 함수 즉 api 요청
hasMore={last} // 이 값이 false이면 더이상 요청을 하지 않는다.
loader= { jsx element} // 로딩 시; 보여줄 jsx요소
>
{
list.amp(...)

}
</InfiniteScroll>

옵션들은 이러하다 더 자세한 내용은 github에서 확인이 가능하다. fetMore 함수 관련해서는 api 요청을 하고 list를 관리하고 last값이 마지막 요청이면 값을 바꿔주고 오프셋 값을 관리해주었다.

const fechMore = async () =>
{
try {
const data = await axios.get(...)
setList([...list, ...data.])

if (data.length === 0) {
    setLast(false)
    //   더이상 데이터 응답이 없다면 last값을 바꿔 로딩을 제어
}
offset.current = offset.current + 1;
 // 오프셋 값은 ref를 사용하여 관리
 } catch (e) {
 console.log(e)
}

}
...
const offset = useRef(1)
// 렌더링과 관련없는 변수 ref로 관리

scroll restoration

라우팅을 왔다갔다 하며 스크롤 포지션을 기억하려고 하는데 찾아보니 몇가지 솔루션들이 있었다. react-router를 활용해서 작업하는 방법들과 라이브러리도 하나 보였고(데모페이지에서 작동을 안해서 그냥 넘어갔다) 이것저것 알아보던중 내용이 꽤 쉽지않고 어렵다는것을 알았다. 그러다가 정리가 잘된 글도 발견했는데 내용이 꽤 어려웠다.

https://godsenal.com/posts/React-router%EC%99%80-scroll-restoration-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0/

또 어떤 글을 찾아보니 그냥 header에 스크립트를 하나 추가하라고 해서 추가해보았다.

<script src="https://unpkg.com/delayed-scroll-restoration-polyfill@0.1.1/index.js"></script>

일단 추가하고 goBack으로 돌아가면 그위치 그대로 돌아간다. …
현재 작업하는 부분에서 라우팅 관련 문제가 있어서 확인 후 재 정리해야겠다.

iframe 관련 iframe 로딩 시 처리

iframe을 적용시키는데 해당 페이지 정보를 불러오는 시간이 꽤 긴것도 있었고, 로딩 시 내용물이 채워져있지 않아 보기 좋지 않았다. 관련해서 처리를하는데 iframe의 onLoad속성을 이용하였다.

...
  <div className={cx('loader')}>
    <Spinner height={10} margin={0} />
  </div>
)}
<iframe
  onLoad={onload}
  className={cx('frame_st')}
  /*
    // @ts-ignore  */
  src={history.location.state}
/>

onLoad 함수는 아이프레임의 로딩이 끝나고 실행되는 함수이다 그래서 onload함수를 정의하고 로딩이 끝나고 상태값을 하나 바꿔준다. 그에 따라 아이프레임을 렌더링을 해주면 로딩시에는 다른 화면이 보이고 로딩이 끝나면 onload함수가 불리어 끝났다고 알려주고 그에 따른 화면 렌더링을 해주는 방법이다.

replace는 ?

mdn에서 정의 내용으로는 replace() 메서드는 어떤 패턴에 일치하는 일부 또는 모든 부분이 교체된 새로운 문자열을 반환합니다. 그 패턴은 문자열이나 정규식(RegExp)이 될 수 있으며, 교체 문자열은 문자열이나 모든 매치에 대해서 호출된 함수일 수 있습니다. 라고 한다. 어쨌든 패턴을 넣어주고 그 부분을 다른 문자열로 교체한다라고 하는데, 왠지 딱 이거일것 같았다.

const newValue = value.replace( reg|substr, newSubStr|function);

기본적인 사용법은 위와 같았다.

const str = '{{username}} 안녕'

str.replace('{{username}}', '재영')
// '재영 안녕'

기가막힌 결과가 나왔다. 그런데, substr, 정규식이 아닌 글자 그대로의 문자열, 을 넣어주면 오직 첫 번째 일치되는 문자열만이 교체 된다고 한다. mustache가 2개라면?

const newStr = '{{username}} 안녕 {{username}} 반가워'

newStr.replace('{{username}}', '재영')
// "재영 안녕 {{username}} 반가워"

정말 하나만 변경되었다. 이러면 mustache가 2개인 상황에 적용할 수 없었다. 그래서 정규식을 조건으로 넣어줘 보았다.

// mustache 정규식
const reg = /{{\s*[\w\.]+\s*}}/g

const newStr = '{{username}} 안녕 {{username}} 반가워'

newStr.replace(reg, '재영')

//"재영 안녕 재영 반가워"

정규식을 사용하면 mustache가 2개 이상일 때도 대응이 된다. https://app.slack.com/client/TH0U6FBTN/CK15D3R2S/details/shared_files


Written by@jaeyoung-son
배운것을 기록하는 공간입니다.

GitHub