These are your normal dependencies, or rather ones that you need when running your code (e.g. React or ImmutableJS).
devDependencies
These are your development dependencies. Dependencies that you need at some point in the development workflow but not while running your code (e.g. Babel or Flow).
### 런타임 에러 바로잡기!!! `sys.setrecursionlimit(2000)` import sys sys.setrecursionlimit(2000) #최대 재귀를 늘려줘야 런타임 에러를 피할 수 있다
defdfs(x):#DFS 함수 정의 visited[x] = True#방문 체크 number = numbers[x] #다음 방문 장소 ifnot visited[number]: #방문하지 않았다면 dfs(number) #재귀
# 변수 할당 없이 바로 for문 돌리기! (재사용 안 함) for _ inrange(int(input())): N = int(input()) # index를 임의로 설정하기 위해서, # - 따로 생성한 뒤 append이든 # - 초기화 후 insert 하지 않고! ### `[0] +` 로 생성할 때 바로 임의로 초기화 값 전달! numbers = [0] + list(map(int, input().split())) # for 문 대신, 동일한 값으로 초기화 시 `*` 사용하기! visited = [True] + [False] * N #방문여부확인용 result = 0 for i inrange(1, N+1): ifnot visited[i]: #방문하지 않았다면 # dfs의 역할: # 1. visited 체크 # 2. 재귀호출로 사이클 체크 (visited) dfs(i) #DFS실행 result += 1#결과값 += 1 print(result)
첫째 줄에 정점의 개수 N(1 ≤ N ≤ 1,000), 간선의 개수 M(1 ≤ M ≤ 10,000), 탐색을 시작할 정점의 번호 V가 주어진다.
다음 M개의 줄에는 간선이 연결하는 두 정점의 번호가 주어진다.
1 2 3 4 5 6
N, M, V = map(int, input().split()) visited = [Falsefor _ inrange(N+1)] graph = [[0]*(N+1) for _ inrange(N+1)] # for 문이 아니라 *로 초기화 for _ inrange(M): s, e = map(int, input().split()) graph[s][e] = graph[e][s] = 1# 한 줄로 동일값 넣기
DFS
들어오는 대로 바로바로 처리 (먼저 깊게 들어가고 다시 올라와 옆으로)
LIFO = 스택
1 2 3 4 5 6 7 8 9 10
# 밑으로 방문 후 pop defdfs(V): # end 조건이 없어도 되는 이유 # for 1~N 까지 돌면서, 남은 요소 중 조건을 만족하는 게 없으면 끝남
visited[V] = True print(V, end=" ") for i inrange(1, N+1): if graph[V][i] == 1andnot visited[i]: dfs(i)
◾ HashRouter : 사이트 방문시 URL 맨 뒤에 /#/이 붙은채로 시작함 ◾ BrowserRouter : 사이트 방문시 # 등 없이 깔끔함
HashRouter
URL에 #기호가 붙는 HashRouter를 왜쓸까? ◾ 원래는 브라우저 주소창에 뭔가 페이지를 입력하면 서버에게 특정 페이지좀 보여달라는 요청이 된다. ◾ 근데 우리는 요청할 서버가 없고 그냥 리액트가 라우팅을 담당하고 있음 ◾ 그래서 잘못하면 있지도 않은 페이지를 서버에 요청을 해서 404 Page Not Found 이런 에러도 뜰 수있다. ◾ 실수로 서버에게 요청하지 않게 하려면 안전하게 #을 붙어야한다. ◾ 왜냐면 브라우저 주소창에서 # 뒤에 붙은 것들은 절대 서버로 요청되지 않기때문이다.
BrowserRouter
그럼 BrowserRouter는 어떨 때 써야 하는가?
◾ BrowserRouter를 쓰려면 서버에서 셋팅을 해야함 ◾ 특정 경로로 들어오는 요청은 404 대신 라우팅을 해야 한다, 등 ◾ 위와 같은 경로를 API를 짜놓으면 된다.
card를 추가함에 따라 기존의 rawData를 수정해야 한다. rawData[year][month]에 카드를 추가하면 데이터가 수정되기 때문. 하지만 json-server에는 수정 API (PUT method) 가 없기 때문에, 기존의 데이터에 수정된 데이터를 붙여 새로운 데이터를 만들어 POST 해줘야 한다.
PUT과 POST의 차이는 다음과 같다.
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.
똑같은 instance를 사용한 method가 2개 이상이 됨에 따라 instance를 생성하여 get , post 와 같은 method를 이용하였다. 이 때 timeout 이란, 해당 시간 동안 (이는 ms 기준이다!) 답을 얻지 못하면 block을 시켜버리는 것이다. 이 시간을 잘 조절해야 한다… (1000ms로 안 되는 데이터 송수신 양은 뜻하지 않게 에러를 부를 수 있다…)
NewCard 에는 year, month 뿐만 아니라 rawData , setUserData 를 전달해야 하기 때문이다. 이 때 rawData를 전달하는 이유는, 새로운 카드를 생성할 때 그 때의 시점에서 rawData에 데이터를 추가해야 하기 때문이다. 또한, 해당 카드를 사용하기 위해 setUserData를 전달하여 시점을 해당 카드로 이동한다.
위에서 내려준 데이터를 받아 카드를 새로 생성하는 컴포넌트이다. createCard는 데이터를 받아 세팅해야 하니 async 로 설정하고, 기본적인 cardForm 을 만들어 이를 rawData의 year, month에 설정함으로써 새로운 데이터를 push한다. 이후 createCardData api를 통해 post하여 data를 생성 (원래는 수정) 한다. 정상적으로 작동할 경우 data를 반환해야 하니 해당 데이터가 반환되는 것이 확인되면, setUserData 를 통해 데이터를 세팅한다.
const Main = ({ year, month, history }) => { ... }
export default withRouter(Main);
카드를 렌더링한 후 개별적으로 선택할 때마다, history에 각각이 가지고 있는 id 를 포함한 url을 push하여 카드가 렌더링되도록 한다. 이를 위해 Card 컴포넌트에 onClickFunc 을 전달한다. 여기서 이 function을 정의하고 전달하는 이유는, 카드 목록에서 선택한 카드를 렌더링하기 위해서는 목록에서 선택한 후 해당 url로 이동해야 하기 때문이다.
withRouter를 설정하여 match를 사용할 수 있도록 한다음, match에서 전달받은 (현재 선택한) 카드의 id를 받아온다. 이후 api를 사용하여 rawData를 불러와 find 를 통해 해당 id와 일치하는 데이터를 가져와 지금의 diaryData로 세팅한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
const Diary = ({ year, month, match }) => { const id = match.params.id; const [diaryData, setDiaryData] = useState(null);
앞에서 설정한 diaryData 를 가지고 Card 컴포넌트를 렌더링한다. 계속 사용하고 있었지만, api를 사용해서 데이터를 불러와 세팅하는 과정을 useEffect로 포함한다면 이를 올바르게 렌더링하기 위해 (null 값이 뜨는 버그가 발생하지 않게…) 데이터가 존재할 때만 렌더링되도록 && 조건을 설정해야 한다!
import FormControl from "@material-ui/core/FormControl"; import NativeSelect from "@material-ui/core/NativeSelect"; import { makeStyles, withStyles } from "@material-ui/core/styles"; import InputBase from "@material-ui/core/InputBase";
위의 라이브러리는 모두 materialUI 를 사용하기 위해 import 한 것이다.
스타일을 사용한 부분은 날씨 select 파트로, 옵션에 스타일을 주기 위해 정의하였다. materialUI에서 선택한 테마를 사용하기 위해 정의한 것들은 다음과 같다.
// newList를 따로 만드는 이유? rawData가 state의 readOnly 값이기 때문에 newList로 값을 복사해서 사용합니다 const newList = rawData[year].filter((data) => data); // 새로운 배열 반환 newList[month][id] = state; const data = await createCardData(rawData); history.goBack(); };
export default withRouter(Card);
rawData 자체는 읽기 전용이기 때문에, 이를 준수하기 위해 filter 함수를 이용해 새로운 리스트인 newList 를 생성하고 데이터를 담는다. 여기서 id에 맞게 state를 저장한 후, 새롭게 변경된 rawData는 POST api를 통해 (PUT 대신) createCardData로 업로드 해준다. 마지막으로, 수정이 완료되었을 때 완료 버튼을 클릭하여 해당 과정을 진행하는 것이므로, 이를 끝마친 후 확인 상태로 돌아가기 위해 history.goBack() 을 통해 뷰 단계로 돌아간다.
수정 버튼을 클릭하면 history.push 를 통해 현재 id 의 edit url로 이동한다.
완료 버튼을 클릭하면 전달받은 handleEdit 을 실행시켜 변경된 데이터를 저장하고 메인 뷰로 돌아간다.
pages/Diary.js
먼저, year에 맞는 data가 존재한다면 (rough하게 year만 찾기) setDiaryData를 이용해 현재 match의 id 값과 일치하는 데이터를 month 배열에서 찾아 저장한다. 또한, rawData 자체도 저장하여 Card에 이를 전달하여 수정/이용할 수 있도록 한다.
마찬가지로, null 에러가 뜨지 않기 위해 diaryData && 를 통해 데이터가 존재할 때만 Card 컴포넌트를 렌더링 하도록 한다.