728x90

 

로딩 관련 컴포넌트를 페이지에 추가할때 보통 위와 같이 추가하기 마련이다. 

그런데 위와 같이 넣을때 에러가 발생하는 경우가 있다. 그러면 습관성 삼항 연상자를 이용해서 로딩이 아닌경우  fragement(<></>) 를 리턴하게 함으로 타입 에러를 처리하고 턴을 마친다.

 

그런데 이 날따라 (혹은 이제서야) 의문이 들었다. 아니 분명 위와 같이 작성해도 문제가 되지 않는 부분도 있는데, 왜 타입 에러가 발생하는 케이스가 생기는걸까?


 

React.Element 와 React.Node 를 먼저 잠깐 살펴보자. 이를 보통 컴포넌트 props 를 넘겨줄때 children의 타입으로 지정하면서 본적이 있을 것이다. 각각의 의미를 살짝 보자면 아래와 같다.

 

React.Element

- 앱을 구성하는 작은 블록으로 일반 JavaScript 객체 형태이다.

- JSX 문법 (<MyComponent />, <div></div>)을 사용하면 Babel과 같은 트랜스파일러가 내부적으로 React.createElement() 함수 호출로 변환하여 이 객체를 생성한다.

 

React.Node

- React 컴포넌트가 렌더링 할 수 있는 모든것.

- React Node들을 담고 있는 배열 ([<li key="1">A</li>, <li key="2">B</li>]). React는 배열 내부의 노드들을 순서대로 렌더링한다.

 

쉽게 말해서 <div> 처럼 태그로 이루어진 컴포넌트 들은 Element 라고 할 수 있고,

이를 포함해서 단순한 number, string 등 화면에 그릴 수 있는 모든 값이  Node에 포함된다.

 

이제 다시 처음의 문제로 돌아가보자.

위 코드가 적혀있는 컴포넌트 children의 타입은 React.Element로 선언되어 있다. 

로딩중이라면 <LoadingScreen> 이라는 Element를 정상적으로 반환하겠지만 로딩중이 아니면 null 값이 리턴이 되는데 이는  Element범위에 속하지 않는다. 이에 타입에러가 발생했던 것이다. 

리턴되는 children의 타입을 React.Node 로 수정하면 null 이 리턴되더라도 문제가 없기에 타입에러는 사라지게된다!

 

추가적으로 React 자체 타입정의(@types/react)에서도 PropsWithChildren 유틸리티 타입을 사용하면 children의 타입이 ReactNode로 되어있는것을 확인할 수 있다. ReactNode를 사용하는게 표준으로 봐도 무방하지 않나 싶다. 

 

앞으로 children 의 타입은 ReactNode 를 사용하자~!

 

 

728x90
728x90

pm2 를 쓰고 있다면 최근에 빌드하면서 위와 같은 에러를 마주했을 수도 있다.

 RUN yarn && yarn global add pm2....

 

위와 같은 명령어로 실행시키면 저번주만해도 문제 없이 잘 진행되었지만 오늘 갑자기 아래와 같은 오류가 발생했다.

 

 

- error pm2@6.0.5: The engine "node" is incompatible with this module. Expected version ">=16.0.0". Got "14.21.3"

- error Found incompatible module

 

 

일단 코드쪽에서 버전을 변경한게 없어서  서버 관리자분께 혹시 버전 변경된게 있나 문의를 넣었지만 따로 변경된 부분은 없었다.

그러다가 pm2 가 최근에 새로운 버전이 나왔다는 것을 확인했다.

 

https://www.npmjs.com/package/pm2/v/2.0.15?activeTab=versions

 

pm2

Production process manager for Node.JS applications with a built-in load balancer.. Latest version: 6.0.5, last published: 9 days ago. Start using pm2 in your project by running `npm i pm2`. There are 1737 other projects in the npm registry using pm2.

www.npmjs.com

 

기존에는 5.4.3을 사용했는데 오늘 날짜 기준으로 9일전에 새로운 6.0.5 버전이 출시된것.. 하지만 이 6버전은 node가 14인 환경에서는 정상적으로 작동하지 않아서 해당 에러가 발생했던 것이다.

 

@근데 왜 내 버전은 갑자기 5.4.3에서 6.0.5 버전으로 변경되서 빌드가 시작된것일까?

 

그건 명령어를 yarn global add pm2 로 해두었기 때문이다.

 

global이 들어가 있기 때문에 최신 버전을 받아서 진행되는데, 이에 빌드할때 최신 버전인 6.0.5가 사용된것이다. 그렇다면 해당 명령어만 수정해준다면 해결될 것이다. 나의 경우 직전의 버전까지 정상작동을 확인했기 때문에 아래와 같이 pm2 설치시에 버전을 특정해주었다. 

 

$ yarn global add pm2@5.4.3

 

 

pm2가 아니더라도 global로 진행되는게 있다면 이런 문제가 발생할 수 있다는 것을 인지해두고 사용을 지양해야하지 않나 싶었다.

728x90
728x90

api의 결과물이 CORS라면, 프로 출신들은 다음의 대사가 머릿속에 떠오른다.

 

"백엔드에게 CORS 관련 설정을 추가해달라고해야지~ 해줘~"

 

하지만 그에 대한 대답이 다음과 같다면 어떨까?

 

"이미 cors 설정 다 되어있습니다."

 

"what?????????????"

 


 

 

특정 api 에서 cors가 발생하고, 관련 확인 요청을 드렸는데 위와 같은 대답이 돌아왔다.

생각해보니 같은 엔드 포인트를 가지고 있는 다른 api 들은 잘 사용하고 있었다.

그런데 왜 유독 이 api에서만 갑자기 cors 에러가 발생한것일까?

 

결론 부터 말하자면 보내는 데이터의 크기가 문제였다.

사용하는 Django의 POST요청 최대 크기 기본값은 2.5MB 였는데, 보내는 데이터가 사진으로 4MB를 넘어버렸다.

이런 상황에서 아래 두 원인중 하나로 인해서 에러가 발생했던 것이다.

 

1.  이에 서버는 400이나 413 을 응답했으나 브라우저에서 구분을 못해서 해당 CORS오류가 발생 가능성

2.  크기가 너무 커서 OPTION요청을 무시해버렸고 이 때문에 CORS 오류 처럼 보이는 메세지가 나왔을 가능성

 

해결 방법은 행복(?)하게도 백엔드에게 다시 요청드리면 된다.

 

"요청 허용 용량을 늘려주세요~"

 

그러면 백엔드에서 아래와 같은 설정을 통해 허용 용량을 늘려줄것이다.

그러고 나면 문제 해결!

 

DATA_UPLOAD_MAX_MEMORY_SIZE = {허용 사이즈}

 

 

CORS에러가 발생했을때 원인이 크기에도 있는지 상상도 못했는데,

보내는 파일이 크다면 한번 쯤 생각해보는것으로!

728x90
728x90

해프닝

프런트엔드의 재미난(?) 고충 중의 하나. 특정 기기에서 이상하게 보인다는 것이 qa를 통해서 들어왔다. 분명 내 화면에서는 문제가 없었는데 왜 그럴까... 하아~ 내가 겪은 문제는 아래와 같았다.

 

아래는 내가 구현해둔 로딩 페이지이다. 로딩 중이라는 텍스트를 inset-0이라는 속성을 이용해서 화면 가운대 배치를 했다.

inset: 0 속성은 fixed나 absolute의 요소를 중앙 배치 하기 위해서 쓰기 아주 좋은 속성이다.

inset: 0은 top: 0, bottom: 0, right: 0 , left: 0의 기능을 하기에 4개나 써야하는 속성을 하나의 값으로 해결할 수 있다.

 

그런데 구형 ios 버전을 가지고 있는 기기에서는 중앙정렬이 되지 않고,

아래와 같이 구석에 콕 박혀있게 디스플레이되게끔 나오는것이다... 

 

 

중앙정렬 관련해서는 inset 속성을 사용했기에 해당 css를 검색해보기로 했다.

 

다큐먼트를 보면서 최하단에 이렇게 호환가능한 버전이 써있는건 여러번 봤을 것이다.  (존재는 알았지만 눈여겨본적이 없느...)

여기서 ios 14.5 버전 이후로 지원한다고 되어있는데, 테스트한 기기는 14.5 보다 낮은 버전의 기기였다. 그래서 inset 속성이 정상적으로 지원하지 않는것 같다.

 

해결

해결 방법은 간단하다. inset의 무엇을 의미한다고 했던가? 4가지 속성을 한번에 표현한다고 했는데 그 속성을 각각 모두 써주면 된다. 고로 아래와 같이 모두 풀어서 써주면 원하는 css를 다시 입힐 수 있다.

// 구버전 ios 에서 지원 X
inset: 0

// 수정 코드
top: 0
bottom: 0
right: 0
left: 0

 

 


 

이와 비슷하게  display: flex 의 속성을 사용했을때 gap의 속성도 구버전에서는 정상적으로 작동하지 않는 경우가 있다.

이럴때는 flex의 설정을 grid로 변경하거나 (grid의 gap은 정상적으로 작동한다..!) gap 대신 margin 속성을 이용해서 원하는 디자인을 구현해야한다.

 

 

예상치 못한 구현버전의 위협....멈춰...! 

728x90
728x90

엑셀 파일을 다운로드 기능을 구현할때가 있다.

오늘은 해당 기능에 대해서 살펴보려한다.

보통 엑셀 다운로드용 api를 받아서 사용하는데, GET과 POST 2가지 방법이있다. 

값을 받아서 구현하는 순서는  아래와 같다.

 

 

try{
 const res = await excelFile(); // 엑셀 데이터 넘겨주는 함수
 const url = window.URL.createObjectURL(
  new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
 );
}....

 

여기서 res.data, 즉 엑셀데이터의 값은 우리가 알아들을 수 없이 'PK\x03\x04\x14\x00\x00\x0..' 이런식으로 데이터가 반환된다. 이런 데이터가 넘어온다면 당황하지 말고 잘하고 있다고 판단하면 된다 :)

api 를 통해 받아온 값을 url 화 한다. URL.createObjectURL() 함수는 객체가 가르키는 URL을 반환하는 역할을 한다.

해당 URL은 이걸 생성한 document가 사라지면 사라진다!

{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } 이 값은 엑셀포멧에 맞는 값이다. 우린 엑셀을 다운로드 할 것이기에 해당 타입을 사용해준다.

 

try{
 const res = await excelFile(); // 엑셀 데이터 넘겨주는 함수
 const url = window.URL.createObjectURL(
  new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
 );
 
 const link = document.createElement('a');
 link.href = url;
 link.setAttribute('download', fileName);
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);
}....

 

준비는 끝났고 다운로드만 해주면된다!

위에서 만든 url을 가상으로 만든 a 태그에 연결해주고

click()을 강제로 실행하여 다운로드 절차를 밟을 수 있다. 여기에 fileName을 추가하여 원하는 파일로 다운로드 받을 수 있게끔 할 수 있다.


 

이렇게 진행되고 엑셀 파일이 정상적으로 다운되더라도 파일이 열리지 않는 경우도 있다.

그건.. api 요청시에 타입을 제대로 지정해주지 않았을 확률이 있다.

아래 처럼 GET 인지, POST인지에 따라서 responseType을 꼬옥 추가or 확인해 보기를 추천한다.

 

// GET
export excelGETDownload = () => {
 const res = await api.get(
  '/execel/download',
  {responseType: 'blob'}
 )
}

// POST
export const excelPOSTDownload = (payload) => {
 const res = await api.post(
   '/execel/download',
   {...payload},
   {responseType: 'blob'}
 )
}

 

728x90
728x90

 

Nextjs로 

평소와 같은 작업을 하고 머지 후 빌드를 돌렸다.

그런데 로컬에서 나지 않던 문제가 갑자기 발생했다.

 

"Expected positive integer for height but received 0 of type number"

 

로컬에서는 잘만 나왔는데 갑자기 빌드에서 안된다라?  이 사실 자체가 두근거리게 한다 ^^; 

yarn run dev로 돌릴때는 문제 없었는데 로컬에서 빌드를 돌려도 위와같은 에러가 똑같이 발생했다.

 

에러문구는 새롭게 추가된 특정 이미지와 sharp 관련된 부분을 에러로 찍어내고 있었다.

뭔가 높이가 0이라고 하는것도 같은데...

 

 

이슈 : https://github.com/vercel/next.js/issues/40602

 

Error in next/image with high aspect ratio images · Issue #40602 · vercel/next.js

Verify canary release I verified that the issue exists in the latest Next.js canary release Provide environment information Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Ver...

github.com

 

 

원인

next js의 next-image-loader.js 의 에러 때문이었다.

특정 값(blurHeight) 계산할때 미세한 높이가 0으로 반올림이되는데,

이에 해당 이미지를 image/next 의 Image 태그로 사용하면 에러가 발생하게 됬던것이다.

(문제가된 나의 이미지 파일도 가로 길이는 400px인 반면에 높이는 9px도 되지 않았고, next의 Image 태그를 사용했다)

 

해결

현재 Next 특정 버전에서 문제가 발생하여 새로운 버전들에서는 문제가 되지 않는것 처럼 보인다.

다만 버전을 올릴수 없거나 하는 상황이면 어떻게 해야할까? (회사에선 거의 그러하다)

next-image-loader.js는 nextjs의 내부 파일이기 때문에 수정해서 관리하기는 껄끄럽다.

 

이럴땐,

넓이에 비해 너무 작은 이미지 사용을 피하거나, Image 태그 대신에 next-image-loader를 사용하지 않는 html 기본 img 태그를 사용하는것이다.

 

후자의 방법으로 수정하고나니 빌드에러에서 해방될 수 있었다!

디자이너님 이제 너무 얇고 긴 이미지는 안돼요!!!!!!!!!!!

 

 

 

728x90
728x90

 

Nextjs에서 사용되는 redirect는 서버에서 페이지 이동을 시켜줄때 매우 듬직한 역할을 한다.

클라이언트 단에서는 useRouter를 이용해서 쉽게 이동 가능하지만 훅이다 보니 서버단에서는 해당 방식을 사용하기 힘들다. 그래서 redirect 함수를 이용해서 특정 상황에 다른 페이지로 보내줄 수 있다. 그런데 이 redirect의 위치에 따라서 작동하지 않을 수 있다는 사실을 알고 있는가? 나는 이번에 처음알았다.

 

Nextjs 14버전 서버사이드 쪽에서 api를 호출하고 그 값에 따라서 redirect를 사용하는 함수를 간단하게 만들었다. 값이 있으면 해당 redirect를 사용한다. 아래 함수는 원하는대로 작동할 수 있을까?

try{
  const res = await getName();
  if(res){
    redirect('/name')
  }
}catch(e){
  // 에러 처리
  console.log(e)
}

 

위 함수는 아쉽게도 res 값이 있더라고 원하는 방향으로 작동하지 않는다. 단 redirect는 실행되었다. 

음? 페이지 전환도 안됐는데 redirect는 실행이 되었다는게 무슨 소리인가? 서버 쪽 콘솔을 확인하면 그 답을 알 수 있다.

 

Error: NEXT_REDIRECT 라는 에러가 찍히는 것을 알 수 있다. redirect가 작동하는 키는 에러를 발생시키는 것에 있다. 에러를 발생시키면서 현재 서버쪽에서 작동하고 있는 플로우를 멈추고 따로 지정한 페이지로 이동하게끔 하는것이다. 때문에 에러를 발생시키는 redirect를 try~catch 문에 넣으면 redirect가 발생시킨 에러를 catch가 잡아버리기 현상이 발생하게 되고, 원하는 페이지 이동이 발생하지 않게되는 것이다. 따라서 redirect 함수는 try~catch문 안에 사용하는 것을 삼가해야 할 것!!

 

https://nextjs.org/docs/app/api-reference/functions/redirect

 

Functions: redirect | Next.js

API Reference for the redirect function.

nextjs.org

 

 

 

이번 기회에 찾아보다보니... 친절하게 공식문서에 써..있다ㅎ;;

  • In Server Actions and Route Handlers, redirect should be called after the try/catch block.

옵션을 통해서 replace 와 push를 고를 수 있다는 점까지 챙겨가면서.. 이 참에 위 부분 공식문서 살짝 정독해보는 시간을 가져보는 것도 좋겠다!

 

 

결론 : try catch 안에서 redirect 사용하지 않기!

 

728x90
728x90

위와 같은 에러가 발생했다. Warning: Extra attributes from the server: cz-shortcut-listen

 

이는 크롬 확장자 중 colorZilla 때문에 발생한다고 한다. colorzilla여서 cz?

위 확장자는 화면에서 원하는 색을 따올 수 있게 도와주는 확장자이다.

SSR환경에서 발생하는데

서버에서 만들어진 HTML에 브라우저 확장자가 추가 속성을 넣게 되면서
잉 서버랑 클라이언트 브라우저가 다른데? 하는 상황이다. colorzilla 외에도 간혹 이런 해프닝을 일으키는 확장자들이 있다고 한다.

 

해결방법은 해당 확장자를 비-활성화 해버리면 일단 깔끔하게 해결가능하다.

 

그럼 colorzilla.. 잘쓰고 있었다면 

나의 경우 크롬 eyedropper 를 쓰고있는데 추천할 만한것 같다!

 

https://chromewebstore.google.com/detail/eye-dropper/hmdcmlfkchdmnmnmheododdhjedfccka

 

Eye Dropper - Chrome 웹 스토어

Eye Dropper allows you to easily pick a color from any web page or an advanced color picker. It's great tool for web developers and…

chromewebstore.google.com

 

 

 

728x90
728x90

 

Form 관리를 리엑트에서 관리하기는 생각보다 귀찮다.

각각의 Input에서 발생될 onChange 함수를 달아주어야 하기도 하고 제출할때 validation 체크에서 부터 에러까지, 하나씩 다 챙겨줘야 한다. 이때 보통 useState를 사용하는데 이는 input 값이 매순간 해당 범위가 리렌더링되게끔 만든다. 

// useState를 사용한 form 관리
const [value, setValue] = useState<string>();

const onClickHandler = (e) => {
	setState(e.target.value)
}
...
<input value={value} onChange={(e) => onClickHandler(e)} />
..

 

물론 해당 리렌더링이 영향에 엄청난 영향을 미치고  코드가 엄청 길어지는건 아니지만, 짧게 관리할 수 있다면 가독성도 늘어나고 효율성도 늘어나지 않을까 한다. 여기에 적합한 라이브러리 react-hook-form 이다. 이 라이브러리는 폼 관리를 단순하게 만들어줄 뿐만 아니라 퍼포먼스 부분에서도 이점을 가져다 준다. useState를 이용해서 관리시에 연결된 여러 값에서 리랜더링이 발생한다. 하지만 이걸 사용하면 현재 변동되고 있는 값에 관련된 부분만 리랜더링이 진행된다. 

 

 

설치

$ npm install react-hook-form

 

사용

useForm

useForm 훅을 선언해주면 바로 사용가능하다. 값을 업데이트 해주는 register 함수를 불러오고, defaultValues로 기본값을 선언해주면 준비 끝이다. 그 후에는 input 태그에 아래와 같이 {...register('원하는 키값')} 을 추가해주면 input 안에 값이 변경될 때마다 qwer 값을 맞춰서 수정해준다.

import { useForm, SubmitHandler } from "react-hook-form"

const { register } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
  
 <input {...register('qwer')} />

 

watch

"그럼 값이 잘 바뀌는지 어떻게 확인하죠? 보통 콘솔로 값찍는데.. 주어진 변수가 없는데..?"

우리에겐 watch라는 함수가 있다. 이 또한 useForm 훅과 함께한다. watch 함수를 선언하고 watch('지켜보고자 하는 키값')을 콘솔 등에 찍어주면 해당 값이 변경될때마다 해당 값을 체크해준다. 값을 확인해보고 싶거나 하나하나 들어오는 인풋을 체크해야 할때 유용하게 쓰일 친구다.

import { useForm, SubmitHandler } from "react-hook-form"

const { register, watch } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
 
 console.log(watch('qwer'))
  
 <input {...register('qwer')} />

 

setValue

하나의 변경으로 여러 값을 수정하고 싶을때는 어떻게 할까? 그럴땐 setValue를 사용해준다. (useState 사용할때 많이 선언했던 기억이..)

setValue('키값', 바뀌는 값)로 함수를 사용해서 여러 값을 변경시킬 수 있다.

참고로 {...register("-- ")} 를 여러개 사용하면 가장 마지막 키값에 관련된 값과만 연결되는것에 주의!! 하나씩만 사용해야한다.

import { useForm, SubmitHandler } from "react-hook-form"

const { register, watch, setValue } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
 
 console.log(watch('qwer'))
  
 <input onChange={(e) => {
   setValue('qwer', e.targe.value)}; 
   setValue('rewq', e.targe.value)};
 }
 />
 
 <input 
   {...register('qwer')}  // 기능 X
   {...register('rewq')}  // 기능 ㅇ
 />

 

handleSubmit

이제 정해진 값을 제출할 차례이다. 이는 함수 이름부터 제출인 handleSubmit을 사용한다. 해당 함수에 전체 data 값을 다 가지고 있으니  바로 아래와같이 사용해주면 된다. console.log 대신에 원하는 api 호출 등의 액션을 추가해주면 끝!

import { useForm, SubmitHandler } from "react-hook-form"

const { register, watch, setValue } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
 
 const check = (data: any) => {
    console.log(data);
  };
  
 <input onChange={(e) => {
   setValue('qwer', e.targe.value)}; 
   setValue('rewq', e.targe.value)};
 }
 />
 
 <input 
   {...register('qwer')} 
 />
 
 <button onClick={handleSubmit(check)}>제출</button>

 

 

간단히 필요한 함수들을 알아보았다. 여기에 더 세부적인 기능은 아래 공홈을 한번 읽어보는걸 추천한다

https://react-hook-form.com/ 

 

React Hook Form - performant, flexible and extensible form library

Performant, flexible and extensible forms with easy-to-use validation.

react-hook-form.com

 

728x90
728x90
<span>{'안녕하세요\n나는\n개발하는 리트리버 입니다.'}</span>

 

위 코드를 돌리면 어떤 결과가 나올까? 줄바꿈 되지 않은 채로 출력된다.

"안녕하세요 나는 개발하는 리트리버 입니다."

개발중에 console 이나 alert 등에 사용하면 원하는 줄바꿈 값을 얻을 수 있지만, DOM에서 렌더링 될때는 우리가 원하는 기능을 하지 못한다. HTML이 '\n'을 줄바꿈으로 해석하지 않기 때문에 단순하게 띄어쓰기로 대체된다. (혹은 무시로..)

물론 \n을 사용하지 않고 줄바꿈을 할 수 있다면 좋다. 태그를 아예 다르게 쓰거나 해서 아예 다른 컴포넌트로 렌더링 되게끔 한다면 간단하다. 하지만 가끔 그렇게 사용하기 난감한 상황들이 좀 있다. map 함수로 배열값을 <li> 로 렌더링 되게끔 구성된 컴포넌트가 있다고 생각해보자.

const =['안녕', '이런\n저런']

LIST.map((item) => <li>{item}<.li>)

 

이렇게 되면 이런과 저런 사이에 줄바꿈을 넣기위해서 컴포넌트를 분리한다던가 하는 방법은 애매해져 버린다.

이럴때 두 가지 방법을 사용할 수 있다.

 

<pre> 사용하기

pre 태그는 preformatted text 태그로, HTML에 표시된 텍스트 그대로를 표시하고자 할때 사용되는 태그이다. 따라서 줄바꿈 뿐만 아니라 탭, 스페이스 등의 텍스트 상태를 있는 그대로 보여준다. 

LIST.map((item) => <li><pre>{item}</pre><.li>)

 

Input (pre 태그 예시)

<pre>
  This is a line of text.
    This line is indented with spaces.
  This line
  has line breaks.
</pre>

 

Output

This is a line of text.
    This line is indented with spaces.
 This line
 has line breaks.

 

white-space: pre-wrap 사용하기

css를 이용해서도 처리할 수 있다. white-space는 여백을 어떻게 처리하는가에 대해 다룬다. 여기서 pre-wrap 설정을 해주면

pre 태그와 같은 기능을 한다고 볼 수 있다. pre 라고만 설정해도 된다. pre-wrap과 pre 의 차이는, 글이 부모 사이즈를 넘어갔을때 줄을 바꿀지 (pre-wrap) 아니면 넘어갈지(pre)를 정해준다. 보통.. pre-wrap이겠죠?

 

728x90

+ Recent posts