반응속도 테스트 클론코딩

기능

  • 반응속도 기록
  • 랜덤 간격
  • 눈 덜 아프게
  • 결과에 따라 다른 메시지
  • 예측 클릭하면 횟수 -1

느낀점

  • 기존 테스트가 색이 확 반전돼서
  • 집중은 해야 하는데 눈이 너무 아팠다
  • 그래서 배경색은 그대로에 초록점이 생기도록 했다
  • 컨텍스트 쓸까 고민하다가 어차피 깊이도 안 깊어서 그냥 prop으로 넘겨줬다

개선방향

공유 버튼 만들기

  • 결과 이미지 생성해서 클립보드 복사해주기 또는 이미지 파일로 저장

구린 css 바꾸기

  • 색을 좀 이쁘게 써보자

레포

ts react sass eslint prettier

  • react 시작할 때 편하게 하려고 만듦
  • 내가 사용하려고 만들어둔 템플릿
  • react + ts + eslint(airbnb) + prettier + sass
  • 필요 없는 테스트 코드 삭제 및 favicon 설정해두었다
  • 리액트로 만들 새로운 앱 초기 설정하는 김에 템플릿으로 만들었다

설정한 순서

1
2
3
4
5
6
7
8
9
10
npx create-react-app . --template typescript

// eslint, prettier
npx install-peerdeps --dev eslint-config-airbnb
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier

// sass(scss)
yarn add node-sass
yarn add prettier-plugin-style-order
  • 필요 패키지 설치
package.json
1
2
3
4
5
6
{
// ...
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.15.1",
"@typescript-eslint/parser": "^4.15.1",
"eslint": "7.2.0", // <-- 삭제
  • react-scripts에서 "eslint": "^7.11.0"를 사용한다고 수동으로 eslint를 설치하지 말라고 한다
1
yarn
  • package.json 에서 eslint를 삭제하고 yarn으로 종속성을 다시 설치한다
package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
// ...
"eslintConfig": {
"plugins": [
"react",
"@typescript-eslint"
],
"extends": [
"airbnb",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/react",
"plugin:prettier/recommended"
],
"env": {
"browser": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"import/no-unresolved": "off",
"import/extensions": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-use-before-define": "off",
"react/jsx-filename-extension": "off",
"prettier/prettier": [
"error",
{
"endOfLine": "auto",
"singleQuote": true,
"tabWidth": 2
}
]
}
},
  • package.json 에서 eslint 관련 설정을 해준다
.vscode/settings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"typescript.tsdk": "node_modules\\typescript\\lib",

// 프리티어 확장도구로 인한 자동 포맷팅 비활성화
"editor.formatOnSave": false,

// 파일 저장시 자동으로 lint 및 포맷팅
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},

// css파일에서 저장시 자동으로 포맷팅
"[scss]": {
"editor.formatOnSave": true
}
}
  • settings.json 을 설정한다

react에서 Map을 state로 사용할 때

맞는 예
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const [state, setState] = React.useState(new Map());

const add = (key, value) => {
setState((prev) => new Map([...prev, [key, value]]));
};


const upsert = (key, value) => {
setState((prev) => new Map(prev).set(key, value));
}

const delete = (key) => {
setState((prev) => {
const newState = new Map(prev);
newState.delete(key);
return newState;
});
}

const clear = () => {
setState((prev) => new Map(prev.clear()));
}
틀린 예
1
2
3
4
5
6
7
8
const [state, setState] = React.useState(new Map());

const add = (key, value) => {
state.set(key, value);
setState(state);
};

// ...
  • 원시 타입이 아닌 객체를 state로 다룰 때 실수하기 쉽다
  • 완전히 새로운 레퍼런스?를 넣어줘야 상태가 변경되었다고 판단하고 리렌더링 된다

배열

array-state-example.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// work
const add = (value) => {
setState((prev) => {
return [...prev, value];
});
};

// not work
const add = (value) => {
setState((prev) => {
prev.push(value);
return prev;
});
};

참고

react로 만들어본 퇴근시간 계산기

기능

메인 페이지

  • 목표 시각까지 남은 시간 계산
  • url param을 통해 목표 시각 세팅
  • 목표 시각이 지나면 퇴근하라는 메시지를 뛰움

about 페이지

  • 링크 공유 기능
  • 퇴근 시간 설정 기능
  • 자동 클립보드 복사

깃허브 페이지 spa 세팅

배경이미지 출처

해결한 문제들

gsap 적용안됨

Share.tsx
1
2
3
4
5
import { Power3, TimelineLite } from 'gsap';
import CSSPlugin from 'gsap/CSSPlugin'; // 추가

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const C = CSSPlugin; // 추가
  • 로컬에서는 애니메이션이 잘 실행되었는데, 빌드 후 애니메이션이 안 나오는 문제 해결

후기

  • 어느 정도 리액트에 익숙해진 것 같다
  • 주로 사용하는 패턴이 생긴 것 같다
  • 엄청 간단해 보이지만 이것저것 신경 쓴 것 같은데, 기록을 안 해놨다 ㅠㅠ
  • 나는 코드에 조금의 변화가 생겨도 커밋을 했었는데,,
  • 이번에는 커밋을 안 하고 한 번에 몰아서 마지막에 파일별로 커밋을 했다
  • 그래서 해결한 문제들이 기억이 잘 안 난다.. ㅠㅠ

신경 썼던 것들

input element

  • about 페이지에서는 링크를 생성하는 부분이 있다
  • 처음에는 input width가 좁았었다. 그래서 고민했던 것이…
  • input 넓이보다 안의 내용이 더 길면은 끝부분이 감춰진다

  • input에서 포커스를 해제하면 위 그림처럼,
  • 계속 앞쪽으로 포커스가 자동으로 이동했다

  • 나는 input 내용의 가장 뒤쪽,
  • url param이 변하는 것에 포커스가 가도록 만들려고 이것저것 해봤는데…
  • 결국엔 그냥 input width를 늘려버리고 끝을 냈다…

github pages SPA

  • 깃허브 페이지는 기본적으로 SPA를 지원하지 않는다
  • react-router-dom 으로 여러 경로들을 만들어 두면 index 말고는 404 페이지로 리디렉트 된다
  • 그래서 찾아보니까,,.!
404.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var segmentCount = 1;

var l = window.location;
l.replace(
l.protocol +
'//' +
l.hostname +
(l.port ? ':' + l.port : '') +
l.pathname
.split('/')
.slice(0, 1 + segmentCount)
.join('/') +
'/?p=/' +
l.pathname
.slice(1)
.split('/')
.slice(segmentCount)
.join('/')
.replace(/&/g, '~and~') +
(l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') +
l.hash
);
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function (l) {
if (l.search) {
var q = {};
l.search
.slice(1)
.split('&')
.forEach(function (v) {
var a = v.split('=');
q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
});
if (q.p !== undefined) {
window.history.replaceState(
null,
null,
l.pathname.slice(0, -1) + (q.p || '') + (q.q ? '?' + q.q : '') + l.hash
);
}
}
})(window.location);
  • 원리는 index 경로 외에 나머지 경로로 들어오면 404페이지를 반환하는데,
  • 커스텀 404페이지에서 스크립트를 통해서 index페이지로 리디렉트 하게 한다
  • 이때, url params 및 query string을 가공한다
  • index.html에서는 404.html로부터 넘겨받은 값으로 라우팅이 적용된 화면을 보여준다
  • 이걸 만든 사람은 정말 대단한 것 같다. ㄷㄷ;

소스코드

참고

github pages spa

react url params 사용하기

example.tsx
1
2
3
4
5
// 전
res = await myFetch(`get/${location.pathname.split('/')[2]}`);

// 후
res = await myFetch(`get/${id}`);
  • 처음에는 url params에 어떻게 접근할까하다가 location객체에서 수동으로 파싱해서 썼었는데

  • ‘react-router-dom’ 에서 useRouteMatch라는 메서드를 통해서 자동으로 파싱하고 필요한 차일드에 넘겨주었다

parent.tsx
1
2
3
4
5
6
7
8
9
10
11
12
// 최상위, 컴포넌트 밖
type MatchParams = {
id: string;
};

// 컴포넌트 안 최상위
const match = useRouteMatch<MatchParams>('/start/:id');

// 렌더 부분, 컴포넌트 안 return 부분
<Route path="/start/:id">
<Start id={match?.params.id || ''} />
</Route>;
child.tsx
1
2
3
4
5
6
7
8
9
// 최상위, 컴포넌트 밖
type Props = {
id: string;
};

// 컴포넌트 선언부
function Start({ id }: Props) {
// ...
}
  • 차일드에서 useRouteMatch를 사용해보았는데, useEffect에서 무한루프에 걸려서
  • 부모에서 전달해주었다

참고

Select elements must be either controlled or uncontrolled.

Select elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled select element and remove one of these props.

에러나는 코드
1
2
3
4
<select value={state} defaultValue="kr" onChange={changeLang}>
<option value="kr">한국어</option>
<option value="en">English</option>
</select>
에러 안나는 코드
1
2
3
4
<select value={state} onChange={changeLang}>
<option value="kr">한국어</option>
<option value="en">English</option>
</select>
  • defaultValue prop을 삭제하자
  • Select elements는 컨트롤되거나 안되게 둘중 하나만 하라는 건데..
  • value prop와 defaultValue prop는 같이 사용할 수 없다고 한다

참고

Cannot use JSX unless the '--jsx' flag is provided.ts(17004)

  • react typescript에서 tsconfig가 계속 자동수정되는 문제 해결하기
오류 메시지 모음
1
2
3
4
5
6
Cannot use JSX unless the '--jsx' flag is provided.ts(17004)

Specify JSX code generation: 'preserve', 'react', 'react-jsx', 'react-jsxdev' or'react-native'. Requires TypeScript version 2.2 or later.

The following changes are being made to your tsconfig.json file:
- compilerOptions.jsx must be react-jsx (to support the new JSX transform in React 17)

  • .ts, .tsx 확장자의 아무 파일이나 열고,
  • F1 > TypeScript 검색
  • TypeScript: Select TypeScript Version... 선택
  • 주의 : (타입스크립트 관련 파일이여야 위 명령이 검색된다)

  • Use Workspace Version 을 선택해준다.
.vscode/settings.json
1
2
3
4
{
// (...)
"typescript.tsdk": "node_modules\\typescript\\lib"
}
  • .vscode/settings.json 가보면 이렇게 업데이트 된것을 확인할 수 있다

TMI

상황

  • react typescript eslint airbnb rule 로 개발하는데
  • yarn start로 시작을 하면 자동으로 tsconfig.json의
  • compilerOptions > jsx 가 자동으로 “react-jsx”로 업데이트 됬다
  • 그런데 나는 “react-jsx”일 때 빨간줄이 마구 그어졌다
  • 그래서 yarn start 이후에 수동으로 “react”로 변경해 주었었다…

해결

  • 워크스페이스의 typescript가 사용되지 않아서 발생한 문제였다
  • compilerOptions > jsx 가 “react-jsx” 여도 빨간줄이 안생긴다!

참고

hexo icarus 테마 프로필영역 css 수정

  • 오랜만에 블로그 이미지 들을 바꿨다
  • 적용시키고 나니까 128x128 사이즈에 안어울리는 것 같아서
  • 크기 제한을 풀었다
layout/widget/profile.jsx 37 line
1
2
3

{/* <figure class="image is-128x128 mx-auto mb-2"> */}
<figure class="image mx-auto mb-2">
  • is-128x128 을 없애준다
  • 또는 자신의 고유한 클래스를 넣어주고 커스텀해도된다

react Each child in a list should have a unique key prop.

  • key prop 에 대한 글이 아니고, 단순히 에러 메시지를 뛰우지 않기위해 하는 단순무식한 글이다.

example.js
1
2
3
4
5
6
7
<div className="product__rating">
{Array(rating)
.fill()
.map((_, i) => (
<p key={i}>✨</p>
))}
</div>
  • 반복문을 통해 자식 엘리먼트를 생성할 때,

    Each child in a list should have a unique key prop.

  • 이 오류 콘솔에 뜨게된다
  • 콘손에 빨간 오류가 보기 싫어서
  • 인덱스 값으로 key prop을 명시해준다

참고

react에서 html 자동완성 쓰기, emmet 사용하기

settings.json
1
2
3
4
5
6
{
// (...)
"emmet.includeLanguages": {
"javascript": "javascriptreact" // <-- react에서도 emmet 사용가능하도록
}
}
  • settings.json에 추가한다
  • 추가로 "emmet.triggerExpansionOnTab": true, 설정을 추가하면 tab키를 눌러 자동완성할 수 있다

참고