July 25, 2020
이미지 로딩 실패시 처리, 웹폰트 관련내용, webpack dotenv, object-fit속성, 옵저버 관찰 등록 후 끊기, 옵셔널체이닝, input spellcheck, ios기기에서 safari접속 에러 내용, 모바일과 데스크탑 css 분리, react app 에서 IE 지원하기
개발하다보면 이미지에 데이터를 불러와서 바인딩 해줄때, 데이터가 잘못되었거나 변경되었을 경우에는 이미지가 로드되지 않거나, alt속성에 따라 엑박 이미지가 나오게 된다. 그렇게되면 UI를 해칠 우려가 있다.
이미지 태그에는 onerror라는 속성이 있다. src에 해당하는 속성으로 이미지를 불러오고, 문제가 생기면 onerror에 이미지로 대체한다.
import React from 'react
import errImg from './'
import replaceImg from './'
const ImgReplace = () => {
return <img src={errImg} onError={replaceImg} />
}
위와 같이 적용해 주었을 경우에는 errImg를 불러오는데 실패하면 replaceImg로 대체하여 로드한다.
위의 경우에는 이미지 태그에서 적용이 가능하나 백그라운드 이미지로 적용할 경우 적용이 안된다. 그러나 백그라운드 이미지에서도 간단한 방법이 있다.
import React from 'react
import errImg from './'
import replaceImg from './'
const ImgReplace = () => {
return <div
style={{
backgroundImg: 'url(errImg), url(replaceImg)'
}}
/>
}
background-img 속성에 url(이미지)를 2개를 넣어준다 먼저 앞에 이미지를 불러오고 실패 했을때 뒤에있는 이미지를 불러온다. 이렇게 이미지 로드시 실패에 관한 상황에 대처할 수 있다.
웹폰트는 글라프로 구성된 모음이며, 각 글라프는 문자나 기호를 설명하는 벡터 모양이다. 그 결과, 두 가지 단순한 변수가 특정 글꼴 파일의 크기를 결정하며 Open Sans라는 인기있는 웹폰트에는 라틴어 그리스어 및 키릴어 문자를 포함하는 897개의 글라프가 들어있다.
오늘날 웹에서는 네 가지 글꼴 컨테이너 형식인 EOT,TTF,WOFF,WOFF2가 사용된다. 여러 옵션이 있지만, 이전 브라우저와 최신 브라우저 모두에서 작동하는 단일 범용 형식은 없다. EOT는 IE 전용이고, TTF는 부분적인 IE 지원 기능이 포함되어있고, WOFF는 지원 범위가 가장 넓지만 몇명 이전 브라우저에서는 사용할 수 없다. WOFF2.0지원은 많은 브라우저에서 현재 진행중이다.
단순하게 다운로드 시간만큼 렌더링이 느려진다. 특히 한글 폰트는 상대적으로 용량이 크다. 이점이 여려 문제를 야기한다.
body {
font-family: 'Nanim Gothic', sans-serif";
}
@font-face {
font-family: 'Nanum Gothic';
font-style: normal;
font-weight: 400;
src: url(/font/NanumGothic.eot), url(....woff2) format('woff2'), url(....woff)
format('woff'), url(....ttf) format('truetype') ...;
}
local 없이 선언하게 되면 폰트 존재 유무와 관계없이 무조건 다운받게된다. 따라서 불필요한 리소스를 요청하게 된다. local 문법을 선언해주면 시스템에설치 되어 있다면 리소스를 요청하지 않는다.
src: local('Nanum-Gothic'), url(/font/NanumGothic.eot),
url(....woff2) format('woff2'), url(....woff) format('woff'),
url(....ttf) format('truetype');
@font-face를 적용할때 unicode-range속성을 사용해서 지원가능한 unicode범위를 정해놓고 해당 속성에 일치하는 글자를 렌더링할 때 다운받는다.
@font-face {
... unicode-range: U+x xxx-xxxx, u+x xxx-xxxx;
}
다국어를 지원하는 사이트의 경우 유용하다.
폰트를 받는 시점이 텍스트를 렌더링 하는 시점과 경합이 일어나기에 그렇다. 두 가지 문제를 최소화 하는게 중요 FOIT을 방지하고 FOUT를 최소화 하는 방법으로!
@font-face에서 font-display라는 속성이 있다. 값들은 다음과 같다.
async function fontLoad() {
const font = new FontFace('font', 'url(font.woff)')
await font.load()
document.fonts.add(font)
document.body.classList.add('fonts-loaded')
}
위의 예처럼 className을 핸들링 할 수 도 있겠고, 다른 방법을 사용할 수 도 있겠다. 폰트 로딩 체크가 가능하다는점.
html 의 head 부분에 link 태그를 추가한다.
<link rel="preload" href="./fonts/..." as="font" ... />
preload를 이용해서 폰트를 받으면 리소스를 다른것보다 먼저 요청한다. 요소들이 그려지기 전에 로드를 하고 FOUT과 FOIT이 없어지는 대신, 그만큼 렌더링이 느려진다. 사용여부와 관계없이 무조간 리소스를 받는다.
내가 preload를 이번에 적용하려고 할때 잘 적용이 되지 않았다. 원인은 아직 찾지 못했고 더 학습해야할 부분이다.
대부분의 경우에 preload와 font-display를 활용해서 대처가 가능하고 좀더 세부적인 컨트롤이 필요할 때 JS를 사용해서 적용해주면 될 것 같다.
Noto Sans 관련 https://m.blog.naver.com/PostView.nhn?blogId=flyteen85&logNo=221330192231&proxyReferer=https:%2F%2Fwww.google.com%2F
환경변수를 관리할때 프로젝트의 루트 디렉토리에 .env 파일에 환경변수들을 관리하고 사용을 하며 그 내용을 간략하게 한번 정리했다. 신규 프로젝트를 생성해서 process.env에 접근을 했더니 접근이 되지 않았다. 해당 내용을 알아보니 webpack 세팅을 별도로 해주어야 했다. dotenv-webpack을 먼저 받는다.
$ npm i dotenv-webpack
그 후 webpack.config.js에서 셋팅을 해준다.
const Dotenv = require('dotenv-webpack')
//.. 그 후 플러그인 추가
module.export = {
...
plugins: [
new Dotenv({
path: '/.env',
// ... 나머지 옵션들
})
]
...
}
위와같이 웹팩 적용 후 다시 process.env에 접근하면 환경변수에 접근이 가능한것을 확인할 수 있다.
object-fit 속성은 대체되는 요소의 내용(img, video, object, svg)등 이 지정된 너비와 높이에 맞게 장착되는 방식을 지정한다. 따라서 이미지나 고정된 크기의 썸네일을 출력하는 다양한 경우처럼 제각각의 크기를 가진 오브젝트등을 넘겨받아 비율을 유지한 채로 일정한 크기를 재 가공하는 경우 유용하다. background-size속성과 매우 유사함.
img {
width: 100%;
height: 100%;
object-fit: cover;
}
object-fit 속성은 기본적으로 요소의 가운데로 이미지를 이동시키는데, 이 위치를 원하는 값으로 변경하는것이 object-position속성.
특정 페이지가 마운트되고 요소관찰을 시작하는데, 요소들이 특정 조건에 따라 변경이 된 후, 옵저버를 다시 등록 하는데 옵저버가 2번이 등록이 되서, 난감했다. 그래서 옵저버의 콜백또한 2번이 실행되는 문제가 생겼다. DOM요소 안의 내용물은 바뀌지만, DOM자체는 그대로였기에, 2번 등록되는듯 했다. 그래서 처음에 든 생각은 언마운트 할 때 연결을 끊는 unobserve를 사용해서 해당 요소들을 unobserve하려고 했다.
export const unObserver = refs => {
let observer: any
observer = new IntersectionObserver(checkIntersect, {
threshold: 0.5,
})
if (refs.current) {
refs.current.forEach(el => {
if (el) {
observer.unobserve(el)
}
})
}
}
unObserver 함수를 정의해서 export 해준 뒤
...
useEffect(() => {
//관찰 등록
registerObserver(
...
);
// 뒷정리에서 관찰 끊기
return () => {
unObserver(refs.current);
};
}, [list]);
...
뒷정리에서 해당 unobserver함수를 실행시켰다. 함수는 정상적으로 작동했으나 관찰이 끊기지 않았다. 해당 원인은 intersectionObserver 생성자에 있었다. 기존 생성자는 함수에서 생성을 했었는데,
export const registerObserver = (refs) => {
let observer: any;
observer = new IntersectionObserver(checkIntersect, {
threshold: 0.5,
});
if (refs.current) {
refs.current.forEach((el: any) => {
if (el) {
observer.observe(el);
}
});
}
...
};
위와 같았다. 함수가 실행될때마다 새로운 observer인스턴스를 생성해서 각각의 별개의 observer인스턴스를 가진 것 이었다. 그래서 해당 파일에서 공통으로 사용되게끔 함수 밖으로 뺴주기로 하였다.
let observer: any
observer = new IntersectionObserver(checkIntersect, {
threshold: 0.5,
})
export const registerObserver = refs => {
if (refs.current) {
refs.current.forEach(el => {
if (el) {
observer.observe(el)
}
})
}
}
export const unObserver = refs => {
if (refs.current) {
refs.current.forEach(el => {
if (el) {
observer.unobserve(el)
}
})
}
}
이제 정상적으로 언마운트할때 관찰을 끊고 새로 등록될떄 잘 등록된다.
옵셔널체이닝은 새롭게 추가된 문법이다. null이나 undefined인 값이 반환되면, 즉시 중단하고 undefined를 반환한다. 보통 null이나 undefined의 값에 접근하여 함수를 실행시키면 오류가 발생하나, 오류 없이 undefined를 반환한다. 추가된지 얼마 안 된 문법이기에 구형 브라우저는 폴리필이 필요하다. 옵셔널 체이닝을 사용하면 프로퍼티가 없는 중첩 객체에도 에러 없이 안전하게 접근할 수 있다.
예제를 보자. 여러 유저가 있는데 주소 정보를 가지고 있지 않은 유저가 몇몇이 있다. user.address.street에 접근해야 하는 상황이라면 에러가 발생할 수 있다.
let user = {} // 주소가 없다
alert(user.address.street) // error
요소가 페이지에 없는 경우에도 개발도중 에러가 발생한다.
const html = document.querySelector('.my-element').innerHTML // error
기존 방식에서 이런 문제들을 해결하기위해 && 연산자로 null체킹을 해주었다.
let user = {}
alert(user && user.address && user.address.street) // undefined 에러발생안함
?. 문법은 ?. 앞의 평가 대상이 Undefined나 null이면 평가를 머추고 undefined를 반환한다. && 연산으로 체크하던것을 옵셔널체이닝을 사용한다면 이와 같다.
let user = {}
alert(user?.address?.street) // undefined
user객체가 존재하지 않을떄
let user = null
alert(user?.address) // undefined
alert(user?.address.street) // undefined
user객체가 null 이기에 address.street접근 자체도 에러가 발생해야하지만, 평가 대상인 user에서 이미 undefined를 반환하여 에러가 발생하지 않았다. ?.은 문법이 위치해 있는 그자리에서만 동작하고 확장되지 않는다. 평가가 끝나고 그 즉시 평가를 멈추고 반환한다. 딱봐도 &&를 사용하는것보다 개발도 편하고, 가독성도 좋은것이 확인된다.
?.은 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춘다. 따라서 ?. 오른쪽에 있는 함수 호출등의 부가 동작들은 평가가 멈추면 일어나지 않는다.
let user = null
let x = 0
user?.sayHi(x++) // 아무일 없다.
alert(x) // 0, 값이 증가안함
?.은 연산자가 아니고 함수, 대괄호와 함꼐 동작하는 특별한 문법 구조체이다. ?.()은 존재 여부가 확실하지 않은 함수를 호출할 떄 사용 가능하다.
let user1 = {
admin() {
alert('관리자에요')
},
}
let user2 = {}
user1.admin?.() // 관리자에요
user2.admin?.() // 실행 안함
위의 경우에는 user객체는 반드시 존재하기 떄문에 admin 프로퍼티엔 .만 사용해서 접근했다.
그리고 admin의 존재여부만 확인했다.
.대신 []를 사용해 객체 프로퍼티에 접근하는 경우엔 ?.[]사용이 가능하다. 프로퍼티 존재 여부가 확실하지 않은 경우에도 안전하게 프로퍼티를 읽을 수 있다.
let user1 = {
firstName: '이름',
}
let user2 = null
let key = 'firstName'
alert(user1?.[key]) // 이름
alert(user2?.[key]) // undefined
?. 은 delete와도 함꼐 사용이 가능함.
delete user?.name // user가 존재하면 user.name 삭제
user?.name = '이름' // syntaxError
// 해당 이유는 undefined = '이름' 이기때문에
개발을 하다가 모바일 기기에서 접속했을때, input폼에 텍스트를 입력하면 문법 검사를하고, 문법이 틀리면 아래
빨간 경고줄이 뜬다. 좋은 기능이지만, 불필요한 UI를 표현할때가 있어서 해당 기능을 끄고싶어서 방법을
찾아보았다.
input에 spellCheck라는 속성이 있는데, 해당 속성에 false값을 주면 입력필드에 맞춤법 검사를 비활성화 시킬 수
있다.
<input
...
type="text"
spellCheck={false}
/>
ios 기기로 사파리에서 접속했을떄, input을 보면 상단 보더는 약간의 셰도우가 들어가있고 보더도 뭔가 다른 느낌이다. 그래서 기존 레이아웃 의도와 달라질 수 있는데, 해당 부분을 처리하기위해서 쉐도우를 없애줘야 한다. 간단하게 css 를 적용하면 지울 수 있다.
.input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
위 내용을 추가해주면 기존 웹과 동일한 UI를 나타낼 수 있다.
아마 기본적으로 사용자 편의성을 위해 인풋창을 클릭 했을때, zoom-in이 되는 것 같다. 다만 줌인이되면 위의 보더 내용처럼 UI비율이 흐트러지게된다. 해당 현상을 방지하려면 처음에 css적용이 되어야할 것 같았는데, html 헤드에 메타태그를 하나 추가해주면 처리가 가능하다.
<head>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width,maximum-scale=1, user-scalable=0"
/>
</head>
위의 내용 처럼 적용해주면 인풋을 클릭 하더라도 줌인이 되지 않는다.
ios 사파리환경에서 기본적으로 위와같은 내용들이 많이 있는것같아 개발하며 체크를 잘 해야겠다.
보통 반응형 레이아웃을 구성할 때 media query를 사용하여 브라우저의 넓이등 기준에 따라 다른 css를 적용하여 반응형 레이아웃을 구성한다. 하지만 html 요소 자체가 달라 질 수 있고, 오직 미디어쿼리만 사용하기에는 부족한 부분이 있을 수 있다.
전에 한번 정리한 내용인데, 클라이언트측에서 모바일인지 아닌지 체크를 react-device-detect 라이브러리를 사용하여 체크해준다.
import { isMObileOnly } from 'react-device-detect'
...
isMobileOnly // 모바일이면 true 데스크탑이면 false 반환
isMobileOnly값에 따라서 스타일 파일을 나눠서 적용해준다.
import { isMObileOnly } from 'react-device-detect'
import classnames from 'classnames/bind'
const styles = isMobileOnly ? require(Mobile.scss) : require(Desktop.scss)
const cx = classnames.bind(styles)
...
return <div className={cx('div')} />
React는 기본적으로 ie9, ie11 같은 구형 브라우저를 지원하지 않는다. 하지만,
방법은 있지만, 실행 속도등의 문제가 있을 수 있다.
react-app-polyfill 과 babel을 활용하면 문제를 해결할 수 있다.
리액트 개발에서 사용하는 다양한 문법을 변환해주는 라이브러리이다.
필요한것만 포함하고 있어 작고 가벼운게 특징이다.
promise,window.fetch,Symbol,Object.assign,Array.from 과 같은 것들을 변환 해준다.
설치
npm i react-app-polyfill
yarn add react-app-polyfill
공식 npm을 보면 인스톨을 한 다음에 반드시 src/index.js의 첫번째 줄에서 import 해야한다고 나와있다.
src/index.js
import 'react-app-polyfill/ie11'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './styles/reset.scss'
ReactDOM.render(<App />, document.getElementById('root'))
ie9일 경우 동일하나 ie11에서 가져온것을 ie9로 바꿔준다.
import 'react-app-polyfill/ie9'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './styles/reset.scss'
ReactDOM.render(<App />, document.getElementById('root'))
stable은 package.json의 browserslist 에 해당하는 브라우저에 대해 안정적인 코드를 사용할 수 있게 해준다. 적용이 잘 안될떄는 node_modules/.cache를 삭제하고 다시 실행한다.
import 'react-app-polyfill/ie11'
import 'react-app-polyfill/stable'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './styles/reset.scss'
ReactDOM.render(<App />, document.getElementById('root'))
babel/polyfill은 react-app-polyfill 에 비해 사이즈가 크고, 필요없는 기능이
많다고 한다. 이전에는 async,await가 react-app-polyfill에서 지원이 안되서
babel/polyfill을 사용해야 적용이 되는것 같았는데, 지금 확인해보면 react-app-polyfill도
적용이 되는듯 하다. 다만 제너레이터 함수 같은것들을 위해 적용이 필요하다.
바벨 폴리필은 두 가지 패키지로 구성되어 있다.
npm install core-js regenerator-runtime
설치 후 index.js 상단에 import 한다.
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import 'react-app-polyfill/ie9'
import 'react-app-polyfill/stable'