0%

Diary App

0️⃣

data.json 파일

  • 이 mock data 파일은 npx create-react-app 으로 생성된 리액트 폴더의 root에 저장해야 한다. ( src 폴더 밖에 )
  • 해당 파일을 읽기 위해
    1. npm i json-server --save-dev 을 통해 json-server을 설치하고
    2. npx json-server --watch data.json --port port# 을 통해 json-server을 실행하여 읽을 data 파일과 서버 포트 번호를 지정해준다.
1
2
3
4
{
"posts": {
"data": {
"2021": [

여기서 posts는 /posts url을 의미하고, data는 그 페이지 안에 들어있는 data 묶음을 의미한다.

data 안에는 2021년에 대한 data 배열이 있는데, 이를 자세히 보면

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
"2021": [
[],
[],
[],
[],
[
{
"id": 1,
"date": 20210501,
"title": "다이어리 제목 어쩌고 저쩌고",
"image": "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/a1108dc0-bf35-418b-956d-338d154a5911/image1.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210508%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210508T050603Z&X-Amz-Expires=86400&X-Amz-Signature=4c752ad0fba5a98411eafd63ba9c406626003981a362eb9b675739cd386846e0&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22image1.jpg%22",
"weather": "맑음",
"tags": ["태그1", "태그2"]
},
.
.
.
],
[],
[],
[],
[],
[],
[],
[]
]

이렇게 12개의 배열이 담겨있다. 즉, 월별 데이터가 각 배열에 들어있다는 의미.


1️⃣

component 구조

  1. /components

Main 페이지를 보면, 크게

1
2
3
4
5
6
7
├ MainHeader
├ Calendar
├ Title
├ Main
├ Card
├ NewCard
├ Footer

이렇게 나뉜다.

여기서 공통적으로 적용되는 부분은

1
2
3
4
5
/common
├ MainHeader
├ Calendar
├ Title
├ Footer

이다.

이 외에 페이지 (Main / Diary) 에 따라 달라지는 부분은

1
2
3
4
5
/main
├ Card
├ NewCard
/diary
├ ...

가 있을 것이다.

  1. pages

위의 컴포넌트를 모두 적용시킨 페이지의 경우 따로 폴더를 만들어 페이지별로 컴포넌트를 생성한다.

1
2
3
/pages
├ Main
├ Diary
  1. 그 외…
  • assets
  • lib

2️⃣

App.js

1
2
3
4
5
6
7
8
9
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import MainHeader from "./components/common/MainHeader";
import Calendar from "./components/common/Calendar";
import Title from "./components/common/Title";
import Footer from "./components/common/Footer";
import Main from "./pages/Main";
import Diary from "./pages/Diary";
// Card와 NewCard를 제외하고 모두 불러와주세요!

root ( App.js ) 에서 부를 모든 컴포넌트 import 하기

데이터 이동이 있는 등 리렌더링이 진행되는 컴포넌트는 모두 BrowserRouter 에 넣는다. main, diary 페이지가 모두 사용하는 MainHeader , Calendar , Title 가 이에 해당하며, 특히 컴포넌트 클릭시 url이 바뀌고 렌더링 되는 컴포넌트가 달라져야 하는 경우 Switch 안에 넣는다. Switch 내에는 Route 로 url 별 컴포넌트 렌더링을 달리 정의한다.

여기서 main은 path="/" 일 때이므로 exact를 설정해 Main을 넘겨주고, /diary 일 때는 Diary 컴포넌트를, 그 외에는 Page Not Found 를 띄워준다. Route 는 위에서부터 차례로 내려가며 path를 비교해 처음으로 매칭되는 컴포넌트를 렌더링한다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// BrowserRouter 내부에 모두 넣어줍시다
// Switch를 사용하는 이유 -> URL이 path에 없으면 Page Not Found를 불러오기 위해서
function App() {
return (
<>
<BrowserRouter>
<MainHeader />
<Calendar />
<Title />
<Switch>
<Route exact path="/" component={Main} />
<Route path="/diary/:id" component={Diary} />
<Route component={() => <div>Page Not Found</div>} />
</Switch>
</BrowserRouter>
<Footer />
</>
);
}

exact에 대한 자세한 설명은 다음을 참고하자.

For example, imagine we had a Users component that displayed a list of users. We also have a CreateUser component that is used to create users. The url for CreateUsers should be nested under Users. So our setup could look something like this:

1
2
3
4
<Switch>
<Route path="/users" component={Users} />
<Route path="/users/create" component={CreateUser} />
</Switch>

Now the problem here, when we go to http://app.com/users the router will go through all of our defined routes and return the FIRST match it finds. So in this case, it would find the Users route first and then return it. All good.

But, if we went to http://app.com/users/create, it would again go through all of our defined routes and return the FIRST match it finds. React router does partial matching, so /users partially matches /users/create, so it would incorrectly return the Users route again!

The exact param disables the partial matching for a route and makes sure that it only returns the route if the path is an EXACT match to the current url.

So in this case, we should add exact to our Users route so that it will only match on /users:

1
2
3
4
<Switch>
<Route exact path="/users" component={Users} />
<Route path="/users/create" component={CreateUser} />
</Switch>

3️⃣

공통 컴포넌트 update!

  1. MainHeader.js

Router Props

브라우저와 리액트앱의 라우터를 연결하게 되면 그 결과 라우터가 history api에 접근할 수 있게 되며 각각의 Route와 연결된 컴포넌트에 props로 match, location, history라는 객체를 전달하게 된다.

Match

match 객체에는 와 URL이 매칭된 대한 정보가 담겨져있다. 대표적으로 match.params로 path에 설정한 파라미터값을 가져올 수 있다.

Location

location 객체에는 현재 페이지의 정보를 가지고 있다. 대표적으로 location.search로 현재 url의 쿼리 스트링을 가져올 수 있다.

👆 요청 url

History

history 객체는 브라우저의 history와 유사하다. 스택(stack)에 현재까지 이동한 url 경로들이 담겨있는 형태로 주소를 임의로 변경하거나 되돌아갈 수 있도록 해준다.

withRouter 을 이용해 컴포넌트를 감싸면 컴포넌트 자체에서도 location , history 와 같이 url을 활용할 수 있도록 한다.

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
// Route가 아닌 요소에서 location을 사용하기 위해서 withRouter를 사용합니다
import { withRouter } from "react-router-dom";
import styled from "styled-components";
import MenuIcon from "../../assets/MenuIcon.svg";
import ProfileIcon from "../../assets/ProfileIcon.svg";

export default withRouter(MainHeader);

const MainHeaderWrap = styled.div`
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 91px;

&__title {
font-size: 36px;
font-weight: bold;
font-style: italic;
color: #cea0e3;
&:hover {
cursor: pointer;
}
}

&__profile {
margin-right: 10px;
}

&__hr {
width: 1200px;
height: 13px;
background: linear-gradient(90deg, white, #cea0e3);
}
}
`;

props에 history 을 받아와 컴포넌트 간 이동을 컨트롤한다. history.push() 를 통해 url 이동이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const MainHeader = ({ history }) => {
// history를 props로 가져옵니다.
return (
<MainHeaderWrap>
<div className="header">
{/* header는 menu, title, profile의 flex-container 입니다. */}
<img className="header__menu" src={MenuIcon} alt="" />
<div className="header__title" onClick={() => history.push("/")}>
{/* title을 누르면 path="/", 즉 Main 페이지로 이동 */}
Diary App
</div>
<img className="header__profile" src={ProfileIcon} alt="" />
</div>
<div className="header__hr"></div>
{/* hr은 위의 menu, title, profile과 수직상에 있기 때문에 flex를 적용시키지 않기 위해 밖으로 뺐습니다 */}
{/* hr을 header div 안으로 넣으면 menu, title, profile과 동일 선상에서 가로로 정렬됩니다. 궁금하면 넣어보세요 ㅎㅅㅎ */}
</MainHeaderWrap>
);
};
  1. Title.js
1
2
3
4
5
6
7
8
9
10
11
const Title = ({ location }) => {
// location을 props로 가져옵니다 (현재 페이지 정보를 담고 있는 객체)
// location.pathname이 "/"이면 "이번 달 일기"를 저장하고, 아니면 "오늘의 일기"를 저장합니다.
const title = location.pathname === "/" ? "이번 달 일기" : "오늘의 일기";

return (
<TitleWrap>
<div className="title">{title}</div>
</TitleWrap>
);
};
  1. Footer.js
1
2
3
4
5
6
7
8
9
const Footer = () => {
return (
<FooterWrap>
<div className="footer">
Copyright&copy; 2021. BE SOPT Web part. All rights reserved.
</div>
</FooterWrap>
);
};

&copy 는 Copyright 표시인 © 를 보여주는 html 요소이다 👾

  1. Calendar.js

Calendar 컴포넌트 및 다른 컴포넌트에도 공통된 state를 내려주기 위해 App.js에서 공통된 state를 생성 및 설정하고, props로 내려준다.

App.js에서 년도와 월을 저장한 후, 데이터를 Calendar.js로 전달해주는 방식입니다!

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useState } from 'react'; // state를 사용하기 위해 useState를 불러옵니다

// Default 값으로 현재 연도와 월을 저장하기 위해, 현재 연도와 월을 불러오는 함수입니다
const getCurrDate = () => {
const now = new Date();
const currYear = now.getFullYear();
const currMonth = now.getMonth();
return { year: currYear, month: currMonth }; // 객체로 한 번에 리턴해줍시다
};

function App() {
// state를 각각 선언해주세요~ useState()의 인자로 각각 현재 연도와 월을 전달합니다
const [year, setYear] = useState(getCurrDate().year);
const [month, setMonth] = useState(getCurrDate().month);
return (
<>
<BrowserRouter>
<MainHeader />
<Calendar
currYear={year}
setCurrYear={setYear}
currMonth={month}
setCurrMonth={setMonth}
/> {// Calendar 컴포넌트에 state로 선언한 모든 것들을 props로 전달합니다 }

Calendar.js

App.js에서 넘겨준 props를 받아준다.

1
const Calendar = ({ currYear, setCurrYear, currMonth, setCurrMonth }) => {

useRef는 DOM 요소를 조작하기 위해 생성하는 상응하는 리액트 내 DOM 객체이다.

1
2
3
// useRef를 사용하여 "DOM 요소를 가져올 수 있는 변수"를 선언합니다
const leftButton = useRef();
const rightButton = useRef();
1
const isMain = location.pathname === "/" ? true : false;
1
2
3
4
5
6
7
8
9
<img
src={LeftOff}
alt=""
onClick={() => isMain && setCurrYear(currYear - 1)}
onMouseEnter={() => (leftButton.current.src = LeftOn)}
onMouseLeave={() => (leftButton.current.src = LeftOff)}
ref={leftButton}
className="calendar__year--left"
/>

onClick 시에는 year를 다시 세팅해야 하므로 props로 받은 setCurrYear 함수를 이용해 year를 알맞게 설정한다. 이 때, url이 “/“ 인 경우에만 월 이동이 가능하게 설정하기 위해 isMain && 조건을 적용한다.

onMouseEnteronMouseLeave 의 경우에는 마우스를 DOM 요소에 호버 온/오프 했을 때의 변화를 설정하는 것이므로, ref를 전달하고 해당 ref의 current.src 에 알맞은 svg 을 전달하여 알맞은 아이콘을 렌더링하도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div className="calendar__month">
{monthList.map((month) => {
return (
<div
key={month}
onClick={() => setCurrMonth(month)}
style={
month === currMonth
? { fontSize: "22px", fontWeight: "bold" }
: {}
}
className="calendar__month--button"
>
{month}월
</div>
);
})}
</div>

map 함수를 사용할 시, 되도록 key props를 설정하도록 습관을 들이자! 또한, map 사용 시 꼭 return 문을 사용해야하는 것은 아니지만, 만약 사용한다면 {} 로 return 문을 감싸야 한다!

여기서는 월을 선택했을 때 currMonth 을 다시 세팅하고, 그에 맞는 스타일을 적용해야 하므로 style 객체에 조건문을 설정하여 알맞게 렌더링되도록 한다. 여기서 jsx 의 특성을 엿볼 수 있는데, 일단 javascript 문법이 들어가기 때문에 style={} 로 문법임을 선언해주고, 이 안에 조건문을 설정한 후 실제 스타일 객체는 {} 로 전달해준다!


4️⃣

Card 설정

Main.js

Main 페이지는 Card를 포함하는 최상단 페이지이므로, 자식 컴포넌트로 Card를 렌더링하고 props로 userData를 전달한다.

1
2
3
4
5
return (
<MainWrap>
<Card userData={userData} />
</MainWrap>
);

Card.js

1
2
3
4
5
6
// 서버에 date가 20200509 형식으로 저장되어있기 때문에, 이를 "5월 9일" 형태로 반환하는 함수입니다
const getDateFormat = (date) => {
const month = parseInt((date % 10000) / 100);
const day = date % 100;
return `${month}월 ${day}일`;
};

props로 받은 userData 객체를 구조분해할당하여 개별적인 변수에 담는다.

1
2
const Card = ({ userData }) => {
const { date, title, image, weather, tags } = userData;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// text 길이가 길어지면 ... 로 생략하는 방법(아래의 3가지 속성을 세트로 사용합니다)
// white-space: nowrap; -> text가 길면 보통 다음 줄로 넘어가는데, "한 줄에만; nowrap" 작성하여 넘치게 합니다
// overflow: hidden; -> 넘친 텍스트를 숨깁니다
// text-overflow: ellipsis; -> 넘친 텍스트를 ... 로 표시합니다

&__title {
font-size: 18px;
height: 25px;
margin: 0 12px;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

5️⃣

API 설정

Main 컴포넌트 자체만 넘기지 않고 props도 함께 전달하기 위해 Route 내 component props을 변경한다.

1
2
3
4
5
6
7
8
// Main 컴포넌트에서는 "해당 월의 데이터만" 가지고 있어야하므로 Main에 현재 연도와 월을 넘겨줍니다
// ex) 2021년 5월에 해당하는 데이터만 가지고 오기 위함! 연도나 월이 변경되면 자동으로 API를 다시 요청합니다
<Switch>
<Route
exact
path="/"
component={<Main year={year} month={month} />}
/>

GetUserData API를 작성하여 json-server에 있는 data를 가져옵니다. 실제로 가져오기 전에 Postman으로 테스트 해보면 좋겠죠?

api 요청 함수는 무조건 async , await 으로 작성하여 비동기 처리한다! 데이터를 받지 않으면 진행되지 않아야 버그를 방지한다.

get url 의 경우, 앞서 data.json 에서 설명했듯이 설정한 서버 포트 넘버 3001번에서 /posts url 에서 data를 가져온다. 이 때, api로 가져오는 data는 객체 내에 data에 대한 metadata와 함께 data를 가져오므로 기본적으로 다음과 같은 구조를 가지고 있다.

1
2
3
4
5
6
7
8
9
rawData {
...metadata,
data: {
.
.
.
data: {}
}
}

여기서는 data 라고 명명하였으므로, data 내 data에 접근하여 data를 가져온다.

1
2
3
4
5
export const getCardData = async () => {
try {
const rawData = await axios.get("http://localhost:3001/posts");
return rawData.data.data;
}

App.js에서 내려준 year , month 를 props로 받아 data의 property로서 접근하여 알맞은 데이터를 가져온다. api에서 data를 받아올 것이므로 초기값은 null 로 설정하고, useEffect 를 이용해 setState한다. 이 때, useEffect 에 전달하는 callback 함수의 경우 밖에서 정의 후 전달하는 것이 아닌 함수 내에서 정의하는 것이므로, IFFY 를 위해 기본적인 함수 생성문 () => {} 정의 후 내부에 IFFY를 위한 (() => {})() 를 정의한다. 이 때, api를 이용해 data를 호출하므로 async, await로 data를 받아오고 설정해야 한다. (api 함수 자체가 async, await 함수이므로) 또한, year과 month가 바뀔 때에만 리렌더링을 해야하므로 dependency 배열에 이 두 변수를 설정한다. 즉, dependencies가 year, month 이므로 year와 month가 변경되면 api를 다시 요청하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
const Main = ({ year, month }) => {
// useState를 사용하여 전체 데이터 값을 저장합니다. 여기서는 테스트를 위해 초기값으로 더미 데이터를 넣었습니다!
// 이후에는 서버에서 API 요청 결과값을 받으면 userData에 저장합니다.
const [userData, setUserData] = useState(null);

useEffect(() => {
(async () => {
const data = await getCardData();
data[year] && setUserData(data[year][month]);
})();
}, [year, month]);

Reference

Router : https://gongbu-ing.tistory.com/45

오리지널 리스트 바꾸기

  1. reverse() 이용
1
2
arr.reverse() #reversing using reverse()
# reversing Array

새로운 리스트 반환하기

  1. 슬라이싱 이용
1
2
res = arr[::-1] #reversing using list slicing
# new reversed array
  1. reversed() 이용
1
2
result=list(reversed(arr)) #reversing using reversed()
# new reversed Array

자료형 set은 크게 두가지 특징을 가지고 있다.

  1. 중복을 허용하지 않는다.
  2. set 내부에 있는 값(value) 들은 순서가 존재하지 않는다.

👉 1 의 특징을 이용하여 중복된 값을 list에서 제거한다.

1
listExample = list(set(listExample))

1
2
3
4
5
6
7
8
def makeDictionary(array):
dictionary = dict()
for idx, val in enumerate(array):
if idx not in dictionary:
dictionary[idx] = [val]
else:
dictionray[idx].append(val)
return dictionary

string to char array

1
2
s = "mystring"
l = list(s)
1
l = [c for c in "foobar"]

정렬

1
2
l.sort() # ascending
l.sort(reverse=True) # reverse, descending

삽입

array.insert(index, value)

1
2
3
4
var=7
array = [1,2,3,4,5,6]
array.insert(0, var)
print(array) # [7, 1, 2, 3, 4, 5, 6]

with문과 함께 사용하기

1
2
with open("foo.txt", "w") as f:
f.write("Life is too short, you need python")
1
2
with open("foo.txt", "r") as f:
lines = [line.rstrip("\n") for line in f]

rstrip() 을 이용해 \n 을 제외한 line 값을 배열에 저장한다.

#1.

  • 1차원 배열을 0으로 초기화 시켜놓고,
  • input을 한 줄씩 읽으며 split 시킨 값을 int로 변환하여 list에 담아, 배열의 0 값들을 대체한다.

#2.

  • 빈 배열을 만들어,
  • input으로 들어온 값을 split하여 int로 변환한 후 list로 만들고, 빈 배열에 append한다.

#3.

  • input으로 들어온 값을 split하여 int로 변환한 후 list에 담고, 이를 [] 안에서 n번 반복하여 최종적으로 2차원 배열을 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
n,m=map(int, input().split())

#1
mylist=[0 for _ in range(n)]

for i in range(n):
mylist[i]=list(map(int, input().split()))


#2
mylist=[]
for i in range(n):
mylist.append(list(map(int, input().split())))


#3
mylist=[list(map(int, input().split())) for _ in range(n)]

#4.

크기가 정해져 있음 + seperator 없이 이어져있는 string을 int array로 만드는 경우

  • 배열을 크기에 맞게 초기화 시키고,
  • [int(s) for s in input()] 로 대입
1
2
3
arr = [[0]*(M+1)]*(N+1)
for i in range(1,N+1):
arr[i] = [int(s) for s in input()]

#5.

  • 초기화 없이 생성만 하고 append 로 이어붙이기
1
2
3
arr = []
for _ in range(N):
arr.append(list(int(s) for s in input()))

Reference

https://minjoos.tistory.com/2

  • is는 변수가 같은 Object(객체)를 가리키면 True
  • ==는 변수가 같은 Value(값)을 가지면 True

1
2
3
4
5
6
7
8
9
10
1. 터미널에서 원하는 폴더 진입
2. npm install -g create-react-app
3. create-react-app "리액트 프로젝트명"
4. cd "리액트 프로젝트명"
5. git init
6. heroku create "프로젝트명" -b https://github.com/mars/create-react-app-buildpack.git
7. git add .
8. git commit -m "커밋 메시지"
9. git push heroku master
10. heroku open

JavaScript

JavaScript의 제한된 버전을 선택하여 기본값인 sloppy mode를 해제하는 방법으로, 일반적인 코드와 다른 semantics를 지니고 있다. 하지만 sloppy mode와 strict mode의 코드는 공존할 수 있어, 차후 strict mode를 이용하여 스크립트의 모드를 변경할 수도 있다.


그래서 strict mode란 무엇일까?

  1. 기존에 문제를 발생시키지 않고 조용히 무시되던 에러들을 throwing한다.
  2. JavaScript 엔진의 최적화 작업을 어렵게 만드는 실수들을 바로잡는다. 이는 동일한 내용이지만 sloppy mode 코드일 때보다 더 빨리 작동하도록 돕는다.
  3. 이후의 ECMAScript 업데이트로 인한 자동적인 변경을 제한한다.
  4. 모듈은 기본적으로 strict mode이다.

이를 적용하는 조건은 다음과 같다.

1
2
3
4
5
6
7
8
9
// 스크립트 전체
"use strict";
...

// 일부
function strict() {
"use strict";
...
}

전체 스크립트 또는 부분 함수에 적용 가능하다.

  • {} 로 묶여진 블럭문에는 적용되지 않는다. 따라서, context와 같은 곳에 적용을 시도하면 동작하지 않는다.

  • eval, Function 코드, 이벤트 핸들러 속성, setTimeout() 등의 함수에 전달된 문자열은 전체 스크립트로, 적용 가능하다.


변경 사항

이러한 strict mode는 구문과 런타임 동작 등을 모두 변경시킨다. 예를 들어,

  1. 변환 실수를 오류로 해석하거나 (문법 오류/ 런타임 에러 발생),
  2. 특정 이름의 특정 용도에 대한 특정 변수를 계산하는 방법을 단순화하며,
  3. evalarguments 를 단순화하고,
  4. “안전한 “자바 스크립트를 작성하도록 돕고,
  5. 미래 ECMAScript의 진화에 대비합니다.

위와 같은 변경으로 인해, 기존에는 인정되던 실수를 에러로 인식하여 발생시킨다.

  1. 글로벌 변수를 생성하는 것을 금한다.

    1
    2
    3
    "use strict";
    // 전역 변수 mistypedVariable 이 존재한다고 가정
    mistypedVaraible = 17; // ReferenceError 를 발생시킴
  2. 일반 코드에서 조용히 넘어가는 모든 실패 (쓸 수 없는 전역 또는 프로퍼티에 할당, getter-only 프로퍼티에 할당, 확장 불가 객체에 새 프로퍼티 할당) 에 대해 예외를 발생시킨다.

    • NaN 은 쓸 수 없는 전역 변수 ( NaN 에 할당하는 일반적인 코드는 아무 것도 하지 않는다. ) 로, 예외를 발생시킨다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    "use strict";

    // 쓸 수 없는 프로퍼티에 할당
    var undefined = 5; // TypeError 발생
    var Infinity = 5; // TypeError 발생

    // 쓸 수 없는 프로퍼티에 할당
    var obj1 = {};
    Object.defineProperty(obj1, "x", { value: 42, writable: false });
    obj1.x = 9; // TypeError 발생

    // getter-only 프로퍼티에 할당
    var obj2 = { get x() { return 17; } };
    obj2.x = 5; // TypeError 발생

    // 확장 불가 객체에 새 프로퍼티 할당
    var fixed = {};
    Object.preventExtensions(fixed);
    fixed.newProp = "ohai"; // TypeError 발생
  3. 삭제할 수 없는 프로퍼티를 삭제하려 할 때; 시도가 어떤 효과도 없을 때, 예외를 발생시킨다.

    1
    2
    "use strict";
    delete Object.prototype; // TypeError 발생
  4. 일반 코드에서는 마지막으로 중복된 인수가 이전에 지정된 인수를 숨기지만, 유니크한 함수 파라미터 이름을 요구한다.

    1
    2
    3
    4
    function sum(a, a, c){ // !!! 구문 에러
    "use strict";
    return a + b + c; // 코드가 실행되면 잘못된 것임
    }

장점

변수 사용 단순화
  • 코드상의 변수 이름을 특정 변수 정의로 매핑하는 방법을 단순화한다.
  • 자바스크립트는 때때로 이름을 코드상의 변수 정의로 기본 매핑하는 것을 런타임때까지 실행이 불가하게 하는데, 이것이 발생하는 대부분의 경우를 제거하여 컴파일러가 strict mode 코드를 더 잘 최적화 할 수 있게 한다.
  1. with 를 사용하지 못하게 한다.

  2. 엄격모드 코드의 eval 은 새로운 변수를 주위 스코프에 추가하지 않는다.

  3. 셋째로, 엄격모드는 일반 이름을 제거하는 것을 금지합니다. 엄격 모드에서 delete name 은 구문 에러입니다.

    1
    2
    3
    4
    5
    6
    "use strict";

    var x;
    delete x; // !!! 구문 에러

    eval("var y; delete y;"); // !!! syntax error

eval 과 arguments 를 더 간단하게 하기
  • 바인딩을 추가하거나 삭제하고 바인딩 값을 변경하기 위한 eval

  • 명명된 인수를 가리키는, indexed 프로퍼티 arguments


JavaScript “보안”

브라우저에서 자바스크립트는 사용자의 개인정보에 접근할 수 있기 때문에, 자바스크립트는 금지된 기능에 접근을 막기 위해 실행 전, 반드시 부분적으로 변경되어야 한다. 하지만 자바스크립트의 유연성으로 인해 런타임 체크없이 이것을 수행하는 것은 사실상 불가능하다. strict mode를 이용하면 특정 방식으로 호출되므로 런타임 검사의 필요성이 크게 줄어든다.

  1. this 로 함수에 전달된 값은 강제로 객체가 되지 않는다 (a.k.a. “boxed”). 즉, 브라우저에서 엄격모드의 함수내 에서는 더 이상 window 객체를 this 를 통해 참조할 수 없다.
    • 정의된 this 는 boxed 객체가 되지 않으며,
    • 정의되지 않은 경우 thisundefined 가 된다.

미래의 ECMAScript 버전을 위한 준비
  • 새롭게 선보일 ECMAScript 버전은 새로운 구문을 소개할 것이고, ECMAScript5에서의 엄격 모드는 변환을 쉽게 하기 위해 몇 가지의 제한을 적용한다.

React

아래와 같은 코드를 index.js 에서 많이 보았을 것이다.

1
2
3
4
5
6
7
8
import React from 'react';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);

React에서 strict mode는 <React.StrictMode></React.StrictMode> 을 이용해 적용한다.

  • StrictMode는 잠재적인 에러를 잡는 툴이다.
  • Fragment와 같이, StrictMode는 가시적인 UI를 렌더링하지 않지만, 안에 정의되는 후손 (자식 노드) 들에 추가적인 검사를 시행하고 경고를 띄운다.
  • strict mode 체크는 development mode에서만 시행되고, production 빌딩에 영향을 주지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';

function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
  1. StrictMode는 HeaderFooter 컴포넌트에 영향을 주지 않는다.
  2. 하지만, ComponentOneComponentTwo 및 그 후손들은 영향을 받는다.

React에서 strict mode는 어떻게 작동할까?

  1. 안전하지 않은 생명주기를 사용하는 컴포넌트 발견
  2. 레거시 문자열 ref 사용에 대한 경고
  3. 권장되지 않는 findDOMNode 사용에 대한 경고
  4. 예상치 못한 부작용 검사
  5. 레거시 context API 검사

이를 좀 더 자세히 알아보자.


특징

안전하지 않은 생명주기를 사용하는 컴포넌트 발견

Strict 모드가 활성화되면, React는 안전하지 않은 생명주기 메서드를 사용하는 모든 클래스 컴포넌트 목록을 정리해 다음과 같이 컴포넌트에 대한 정보가 담긴 경고 로그를 출력한다.

1


레거시 문자열 ref 사용에 대한 경고

이전의 React에서 레거시 문자열 ref API와 콜백 API라는, ref를 관리하는 두 가지 방법을 제공하였었다. 이제는 객체 ref가 문자열 ref를 교체하는 용도로 널리 더해졌기 때문에, Strict 모드는 문자열 ref의 사용에 대해 경고한다.


권장되지 않는 findDOMNode 사용에 대한 경고

이전의 React에서 주어진 클래스 인스턴스를 바탕으로 트리를 탐색해 DOM 노드를 찾을 수 있는 findDOMNode를 지원하였었다. 이제는 DOM 노드에 바로 ref를 지정할 수 있기 때문에 보통 필요로 하지 않는다.


예상치 못한 부작용 검사

개념적으로 React는 두 단계로 동작한다.

  • 렌더링 단계 는 특정 환경 (DOM과 같은) 에 어떤 변화가 필요한 지 결정하는 단계로, 이 과정에서 React는 render 를 호출하여 이전 렌더와 결과값을 비교한다.
  • 커밋 단계 는 React가 변경 사항을 반영하는 단계로 (React DOM의 경우 React가 DOM 노드를 추가, 변경 및 제거하는 단계), 이 단계에서 React는 componentDidMountcomponentDidUpdate 와 같은 생명주기 메서드를 호출한다.

커밋 단계는 일반적으로 매우 빠르지만, 렌더링 단계는 느릴 수 있어 문제가 발생할 수 있다.

Screenshot 2021-05-30 at 20 45 33)

특히 위의 메서드들은 여러 번 호출될 수 있기 때문에, 부작용을 포함하지 않는 것이 중요하다. Strict 모드가 자동으로 부작용을 찾아주는 것은 불가능하지만, 조금 더 예측할 수 있게끔 만들어서 문제가 되는 부분을 발견할 수 있게 도와준다.

이를 위해 다음과 같은 함수를 의도적으로 이중으로 호출하여 찾을 수 있다.

  • 클래스 컴포넌트의 constructor, render 그리고 shouldComponentUpdate 메서드
  • 클래스 컴포넌트의 getDerivedStateFromProps static 메서드
  • 함수 컴포넌트 바디
  • State updater 함수 (setState의 첫 번째 인자)
  • useState, useMemo 그리고 useReducer에 전달되는 함수

레거시 context API 검사

레거시 context API는 오류가 발생하기 쉬워 이후 릴리즈에서 삭제될 예정이다.

2



Reference

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode

https://ko.reactjs.org/docs/strict-mode.html