티스토리 뷰
아래의 내용을 통해 React에서 다루는 핵심 개념을 이해하고 구현하는 방법을 알아보자. 개발자라면, 한 번정도는 읽어야할 공식문서 실습 부분을 한국어로 정리했다. 추가로 frontend를 구성하고 있는 nodejs에 대한 정보는 아래의 url을 참고하자.
React란
react는 사용자 인터페이스, 즉 fontend를 구축하기 위한 직관적이고 효율적이자 유연한 JavaScript 라이브러리이다. 그리고 Component라고 불리는 코드의 조각들을 이용하여 복잡한 UI를 구성한다.
컴포넌트를 구성하는 방식은 여러가지가 있다. 먼저 React.Component의 하위 클래스를 사용하여 아래와 같이 컴포넌트를 만드는 것이 가능하다.
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
위의 코드는 React의 구조를 보다 쉽게 작성할 수 있는, JSX라는 특수한 문법이다. XML과 유사하다고 할 수 있다. 기본적으로 컴포넌트의 return 뒷부분에 있는 html을 통해 React에게 화면에 표현하고 싶은 것을 알려준다. 만약 데이터가 변경된다면 React는 컴포넌트를 업데이트하고 다시 렌더링 한다.
위에서 ShoppingList라는 클래스를 만들었다. 이는 React 컴포넌트 클래스 또는 React 컴포넌트 타입이라고 한다. 이러한 컴포넌트는 props로 매개변수를 받을 수 있다. 그리고 render 함수를 통해 표시할 화면의 계층 구조를 반환한다고 할 수 있다.
render 함수는 내가 화면에 보여주기위해 적은 코드를 리턴한다. 그리고 React는 그 리턴된 코트를 받아서 화면에 보여준다. render 함수가 반환하는 과정을 좀 더 자세히 보면, 위의 html 코드 부분을 바로 리턴하는 게 아니라, html 코드 부분을 경량화시킨 React element 를 반환한다. 그래서 <div /> 태그는 빌드하는 시점에 아래와 같이 React.createElement('div')로 변환된다.
// 변환전 코드
<div className="shopping-list">
<h1>Shopping List for {props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>;
// 변환 후 코드
/*#__PURE__*/
React.createElement("div", {
className: "shopping-list"
}, /*#__PURE__*/React.createElement("h1", null, "Shopping List for ", props.name),
/*#__PURE__*/React.createElement("ul", null,
/*#__PURE__*/React.createElement("li", null, "Instagram"),
/*#__PURE__*/React.createElement("li", null, "WhatsApp"),
/*#__PURE__*/React.createElement("li", null, "Oculus")
)
);
JSX로 작성된 코드는 React.createElement()를 사용하는 형태로 변환됨을 알 수 있다. 우리는 JSX를 사용할 것이기 때문에 React.createElement()를 위와 같이 직접 호출할 일은 없다고 할 수 있다.
JSX는 Javascript를 그대로 사용할 수 있기 때문에 강력하다고 할 수 있다. JSX 내부의 중괄호 안에서는 Javascript를 사용가능하다. 그리고 React element 자체가 Javascript 객체이므로 변수에 저장하거나 전달하는 것도 가능하다.
React의 Component는 캡슐화 되어있고, 독립적으로 작동되어 질 수 있다. 위의 JSX는 DOM 컴포넌트인 <div / >태그나 <li />태그를 랜더 했다. 그러나 위에서 적은 클래스 컴포넌트 자체를 랜더하는 것도 가능하다. 그래서 <ShoppingList /> 같은 방법으로 여러군데에서 랜더하여 사용 가능하다.
실습코드 전체 살펴보기
아래의 코드는 JSX로 작성된 코드이다. 코드를 확인해보고, 아래의 코드를 기준으로 설명을 추가해 보겠다.
class Square extends React.Component {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
위의 코드는 React Component가 Square, Board, Game으로 세가지로 코드가 작성되어 있는 것을 확인 할 수 있다.
- 가장아래에 있는 ReactDOM.render로 Game Component를 랜더한다.
- Game Component는 Board Component를 랜더한다.
- Board Component는 Square Component를 랜더한다.
- Square Component는 <Button> 태그를 랜더한다.
이 정도 확인하고 다음으로 넘어가 보자.
Props로 데이터 전달하기
Board Component에서 Square Component로 데이터를 전달하는 코드를 작성하면서 Props에 대해 이해를 해보자. 이렇게 Board Component가 부모 컴포넌트이고, Board Component에 포함된 Square Component을 자식컴포넌트라고 할 수 있다.
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />;
}
}
위 코드는 renderSquare 함수를 실행하면 Square Component가 리턴되는 코드이다. Square에 데이터를 전달하기 위해 value={i} 와 같은 코드를 추가했다. 추가한 코드는 왼쪽 항이 Square에서 사용될 변수명이고, 오른쪽 항이 넘겨줄 데이터이라고 할 수 있다. 이제 아래와 같이 Square Component에서 value를 사용하면된다.
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
위의 Square Component를 보면 {this.props.value} 값이 추가된 것을 알수 있다. Board Component에서 보내준 데이터를 사용하려면, this.props를 통해 사용할 수 있다. 그리고 value라는 변수로 보내줬기 때문에 this.props 뒤에 value를 붙여서 사용하면된다.
사용자와 상호작용하는 컴포넌트 만들기
사용자와 상호작용 한다는 말을 들을 때, Jacascript에서 가장 먼저 떠오르는 것은 EventListener이다. 위의 Square Component의 버튼 부분을 아래와 같이 수정해 보자.
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => console.log('click')}>
{this.props.value}
</button>
);
}
}
버튼을 누르면, 'click'라는 문자가 브라우저 개발자 도우의 console에 출력되는 코드이다. 이때, React에서는 class를 지정 시 class가 아닌 className으로 클래스를 지정한다는 것을 확인하자. 그리고 onclick도 onClick로 작성되는 것을 확인하자.
추가적으로 함수자체의 this 바인딩이 아닌 상위 스코프의 this만 참고할 수 있도록 화살표 함수를 앞으로도 사용할 것이다. onClick={() => console.log('click')} 의 코드도 위의 props부분과 동일하다. 위 코드는 button 태그에 props로 onClick을 전닿하고 있는 것이다. 그러면 React는 Click 시에만 이 함수를 호출하게 된다.
컴포넌트의 상태관리
위에서 Component 내부에서 Click 관련 EventListener를 만들었다. 이제 클릭했을 때 특정을 값을 기억할 수 있도록 만들어보자. 이러한 것은 우리는 상태관리라고 한다. 코드로는 state라고 표현을 한다.
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square"
onClick={() => this.setState({value: 'X'})}
>
{this.state.value}
</button>
);
}
}
리액트는 state 설정을 생성자인 constructor 내부에서 해야한다. 이는 component 내에서 this.state를 정의하는 것을 private 하게 간주해야함을 의미한다고 생각해도 된다. 또한 javascript와 같이 하위 클래스의 생성자를 정의할 때는 위와 같이 super을 항상 호출해 준다. 즉, 모든 React Component Class는 생성자를 가질 시 super(props) 호출을 해야한다. 따라서 우리는 this.state 안에서 현재 상태 값을 위와 같이 value변수로 null을 넣어서 만들어 준것이다.
render 아래 부분은 클릭 했을 때, 상태의 값을 변화시키는 로직이라고 보면 된다. 변화시키는 방법은 this.setState를 활용해서 상태값을 변화시킨다. 위 코드는 클릭 시 null 값이 'X'로 변경된다. 그리고 this.state.value 값으로 화면에 표현할 수 있다.
정리하면, React는 버튼 태그에서 클릭이 발생을 하면, onClick 핸들러가 this.setState를 호출하면서 상태값을 변경하게 만든다. 그리고 변경 이후에 화면을 re-render하게 되고, React는 this.state.value를 통해 변경된 값을 화면에 보여준다. 추가 개념으로 위와 같이 Component에서 setState를 호출하면, React는 Component에 포함되는 자식 컴포넌트도 같이 업데이트(re-render)를 해준다.
State 끌어올리기
위에서 상태값을 state로 지정을 했다. 이제 자식 component의 state를 부모 component로 올리는 방법에 대해 알아보자. 자식 component의 state를 부모 component에서 요청하여 가지고 와서 사용하는 방식은 React에서는 좋은 선택이 아니다. 이는 버그에 취약하고 리팩토링이 어렵다. 따라서 React에서는 부모 component에서 state를 지정하고 자식에서 props로 내려주고, 자식 component에서는 props로 받은 state를 사용하는 방식으로 사용된다.
정리하면 여러개의 자식 component에서 데이터를 모아야하는 경우나, 두 개의 자식컴포넌트들이 서로 통신을 하게 하려는 경우에는 상위 부모 component에서 state를 정의해야한다. 그리고 정의한 state들 필요한 자식 component에 내려주면된다.
여기서 React를 처음 공부하면 헷갈리는 부분이 있다. 부모에서 정의한 state를 자식이 props로 받아서 사용중이라고 해보자. 이때 자식은 props로 받은 state를 접근은 할수 있으나, state를 직접 변경 할 수 없다. 여기서 직접 변경 할 수 없다는 말은, state를 변경하려면 부모에서 state를 변경할 수 있는 함수를 props로 받은 후에, state를 변경하는 함수를 호출하는 식으로 state를 변경해야 한다.. 이러한 이유 때문에 직접 변경 할 수 없다고 한다. 다음 예시에서 관련 부분을 참고하여 이해해 보자.
위의 Square component에서 정의했던 state를 상위 component인 Board component로 끌어올려 보겠다.
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}{this.renderSquare(1)}{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}{this.renderSquare(4)}{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}{this.renderSquare(7)}{this.renderSquare(8)}
</div>
</div>
);
}
}
우선 Square component에 있던 state 상태값들을 Board인 부모 component로 올려준것을 확인 할 수 있다. 그리고 Board component 내부에서 리턴되고 있는 Square component를 보면, value와 onClick 두 개의 props를 전달 한 것을 확인 할 수 있다. value 값을 내려줬기 때문에 props에서 value를 받기 위해서 this.props.value로 변경해 준다. 그리고 onClick을 받기위해서는 this.props.onClick()으로 Square component 내부를 변경해 주면된다.
그럼 Square Component인 <button> 을 클릭했을때 발생하는 로직 순서를 알아보자.
- Build-in DOM <button> Component에 포함된 onClick porp는 React에게 클릭에 대해 EventListener을 설정하라고 알려준다.
- 버튼 클릭 시 React는 위의 onClick 핸들러를 호출한다.
- onClick 핸들러는 다음 내부로직을 실행하기 때문에, {() => this.props.onClick()} this.props.onClick()를 호출한다.
- 이때 Board에서 Square로 전달한 prop가 onClick={() => this.handleClick(i)} 이기 때문에, Square를 눌렀지만 Board의 handleClick(i)을 호출한다.
React Event 관련 이름짓기. 우리는 위의 <button> element에서 onClick를 사용했다. 여기서 사용된 onClick 속성은 React 에 일반 javascript의 onclick과 달리 react에 내장되어 있는 것이다. 그럼에도 이름 지정은 자유롭게 만들어도 문제 없이 잘 돌아가기는 한다. 하지만 일반적으로 사용되어 되어지는 네이밍은 다음과 같다. Event를 나타내는 prop는 on[Event] 로 사용되고, 이벤트를 처리하는 함수에는 handle[Event]로 사용하는 것이 일반적이다. |
Square component는 state값이 없고, Board component에서 values를 받아서 사용한다. 그리고 클릭이 일어났을 때 Board component에 알리는 역할을 하게 되었다. 이를 React에서는 Square component를 제어된 컴포넌트(Controlled components)라고 부른다.
설명을 위해 위의 코드에서 일부분만 아래로 가지고 왔다.
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
...
handleClick 함수는 기존의 state를 변경해주는 코드가 들어있다. 좀 더 자세히 보면 slice()를 호출해서 기존 배열을 수정하는게 아닌, 복사본을 만들어서 갈아끼우는 것을 알 수 있다. 여기서 바로 React의 Immutability(불변성)가 나온다.
React에서 중요한 불변성(Immutability)
위의 코드를 보면, 상태 변경 시 squares array를 변경하는게 아니라 새로운 배열을 만들어서 갈아끼워 넣었다. 그 이유을 알기 위해서, 우선은 일반적인 데이터 변경 방법을 알아보자. 크게 2가지 방법이 있다.
첫 번째는 데이터 값을 직접 변경(mutate)하는 것이다. 다른 말로 표현하면, Mutation한 데이터 수정라고 할 수 있다.
let player = {score: 1, name: 'Jeff'};
player.score = 2;
// 결과값
// {score: 2, name: 'Jeff'}
두 번째는 원하는 변과 값이 포함된 새로운 복사본 데이터로 교체하는 것이다. 다른 말로 표현하면, Mutation하지 않은 데이터 수정라고 할 수 있다.
let player = {score: 1, name: 'Jeff'};
let newPlayer = Object.assign({}, player, {score: 2});
// 이제 player는 변하지 않았지만 newPlayer는 {score: 2, name: 'Jeff'}입니다.
// 객체 spread 구문을 사용한다면 이렇게 쓸 수 있습니다.
// var newPlayer = {...player, score: 2};
보통 많은 블로그나 작성 글들을 보면, object.assign 보다는 spread 구문을 많이 사용한다. 위의 두가지 결과는 사실 동일하다. 그러나 두 번째 방식은 not mutating 하는 방법은 아래의 여러 이점을 가진다.
1. 복잡한 기능들이 단순해 진다. (Complex Features Become Simple)
공식문서에서는 Complex Features Become Simple 이런 표현을 썼다. 쉽게 말하면, 기존 데이터를 완전히 변화를 시키면, 이전 데이터로 복구하는 것이 불가능하다. 즉, 특정 작업을 취소 시 이전 작업으로 돌아가는 것이 가능하다. 따라서 재사용이 가능하다고 할 수 있다.
2. 변화를 감지할 수 있다. (Detecting Changes)
Mutable objects에서는 직접적으로 수정이 되기 때문에 변화를 감지하는 것이 어렵다. 그러나 immutable objects에서는 단순히 참조하고 있는 객체가 이전 객체와 다르다면 변화한 것이다.
3. React가 다시 랜더할 시기를 결정할 수 있다. (Determining When to Re-Render in React)
React에서 immutability의 가장 큰 이점은 pure components를 만드는데 도움을 준다는 것이다. 이 말은 리액트가 변화의 발생을 인지하고 component를 re-rendering할 수 있음을 의미한다. 이 정도만 이해하고 넘어가자. 좀 더 깊게 들어가면, 변화에 따른 re-rendering을 위해 shouldComponentUpdate나 React.PureComponent를 사용 할 수 있는데, 이 부분은 성능최적화(Optimizing Performance)부분에서 다시 다뤄보겠다.
리스트 state 추가하기
위의 내용을 통해 mutable과 immutable에 대한 이해는 어느정도 되었고, React에서 state를 변경할 떄는 immutable하게 변경해야함을 알았다. 이제 list 상태를 추가할 때는 어떤 식으로 변경을 해야할 지 알아보자.
우선은 아래와 같은 state 내부에 history array가 있다고 해보자.
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
};
}
render() {
return (
<div className="game">
...
</div>
);
}
}
위의 history array를 setState로 object를 추가하는 핸들러는 아래와 같다.
handleClick(i) {
const history = this.state.history;
const squares=Array(9).fill(null),
this.setState({
history: history.concat([{
squares: squares,
}]),
});
}
handleClick 코드를 읽어보면, squares 배열을 하나 만든다. 그리고 setState를 보면, 기존의 state.history에서 squares 배열을 concat을 이용하여 추가하는 것을 볼 수 있다. 위의 코드의 핵심은 concat이다. 사실 보통은 배열 추가시 push() 함수를 많이 쓴다. 하지만 리액트 상태변화 시 기존 배열을 변경하지 않기 위해서 concat()을 활용하여 변경함을 이해하자.
React에서 자주 쓰이는 map 함수
Javascript의 Array는 아래와 같이 map() 매서드를 사용 할 수 있다.
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2);
// 결과값
// [2, 4, 6]
React elements는 first-class JavaScript object(1급 객체)로 우리의 application에 전달하는 것이 가능하다. 그리고 React에서는 다양한 item들을 render 할때 React elements의 배열을 사용한다. 그리고 이 배열을 render 할 때 우리는 map() 매서드를 사용한다고 보면 된다.
아래의 코드는 map() 함수를 활용해서 datas의 내용을 하나씩 button element에 넣어서 화면에 보여주는 코드를 작성한 것이다.
render() {
const datas = [
{
id: 'Alexa',
content: 'AlexaAlexaAlexa'
},
{
id: 'Ben',
content: 'BenBenBen'
}
{
id: 'Claudia',
content: 'ClaudiaClaudiaClaudia'
}
]
const data = datas.map((name, index) => {
const welcome = name ?
'Welcome!!' + name.content :
'Not name';
return (
<li key={index}>
<button onClick={() => this.handleClick(index)}>{welcome}</button>
</li>
);
});
return (
<div className="game">
<div className="game-info">
<ol>{data}</ol>
</div>
</div>
);
}
- map함수는 datas의 배열을 순회하면서, 내부의 객체를 name 변수에 담는다.
- name 변수를 재가공 해서 welcome 변수에 담는다.
- welcome 변수를 button element의 text로 넣어준다.
map의 두번째 인자인 index는 0부터 순차적으로 순서에 맞게 나오게 된다. 그리고 button을 li 태그로 감싸고 있는 것을 확인 할 수 있다. 위의 li태그와 같이, React에서는 배열이나 반복자를 생성 시 유일한 "key" prop를 포함시켜줘야한다.
key 넣기
리스트 랜더 시 React는 기본적으로 list item들의 정보를 저장한다. 그리고 list가 변경되면, React는 무엇이 변경 됐는지를 알고 최적화를 해야한다. 이러한 이유로 key prop을 포함시켜주는 것이 필요하다. key prop가 포함되면 react는 key를 기준으로 추가, 제거, 변화, 순서변경 등을 판단한다.
아래의 코드가
<li>Alexa: 7 tasks left</li>
<li>Ben: 5 tasks left</li>
다음과 같이 변한 경우를 생각해 보자.
<li>Ben: 9 tasks left</li>
<li>Claudia: 8 tasks left</li>
<li>Alexa: 5 tasks left</li>
변경된 사항을 보자.
- li 태그 하나 증가되어서 데이터가 추가되었다.
- Ben과 Alewa의 순서가 변경되었다.
사람은 위의 변화를 눈으로 판단할 수 있다. 그러나 computer program인 React는 무엇이 어떤한 의도로 변화되었는지를 판단하기 어렵다. 따라서 key 속성을 list item에 넣어줘서 어떻게 달라지는지를 React가 알수 있게 만드는 것이 필요하다. key를 넣어주는 방법은 다양하다. 보통은 id값으로 숫자를 넣어준다고 생각할 수 있지만, 애래와 같이 중복되지 않는 string을 넣어주는것도 가능하다.
<li key="Ben">Ben: 9 tasks left</li>
<li key="Claudia">Claudia: 8 tasks left</li>
<li key="Alexa">Alexa: 5 tasks left</li>
그리고 map에 의해 user 데이터 객체를 받는다고 한다면 아래와 같이도 가능하다.
<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
DB에서 받아온 id값을 user.id로 넣고, 나머지 데이터도 위와 같이 넣어준 예시이다. key에 들어간 값은 unique하기만 하면 된다. React는 re-rendering 시 각 list item들을 가져가서 이전 list item의 key와 일치하는 key를 탐색한다. 만약 현재 list에서 존재하지 않는 키가 있다면, React는 새로운 component를 생성한다. 반대로 현재 list에 이전에 존재하던 key가 사라진다면, 그 key를 가진 component를 제거한다. 그리고 동일 한 key가 위치이동을 한다면, component를 이동시킨다.
쉽게 말하면, React는 key를 통해 각각의 컴포넌트를 구별한다. 그리고 re-rendering 시 구별한 컴포넌트의 state를 유지시킨다. state 관점에서는 component의 key가 변경된다면, component는 제거 되고 새로운 state와 함께 다시 생성된다고 할 수 있다.
따라서 고유하게 지정된 key를 쓰지 않으면, 굉장히 비효율적으로 react가 작동할 수 있기 때문에 주의해야한다. 적절한 key를 포함하지 않는 데이터라면 재구성을 고려해야한다. 또한 공식문서에서는 동적 리스트를 만든다면 반드시 적절한 key를 할당하는것을 강렬히 추천한다.
추가적으로 key는 전역으로 고유할 필요는 없고, 관련 item들 사이에서만 고유한 값을 가지면 된다. React는 element 생성 시 바로 key 속성을 추출하고, 반환된 element에 바로 저장한다. key는 props로 this.props.key로 참조할 수 있을 것 같이 보이지만, 참조하는 것은 불가능하다. React에서 key는 단순히 컴포넌트를 업데이트 할지를 판단하는데만 쓰이고 조회는 불가능하다.
여기까지 전부 이해가 된다면, react의 기본 컨셉에 대한 이해는 했다고 볼 수 있다.
'Web > React' 카테고리의 다른 글
[Error] Unknown property 'crossorigin' found, use 'crossOrigin' instead react/no-unknown-property (0) | 2022.06.14 |
---|---|
[react] 상태관리 기초 - 효율적으로 데이터 저장하고 수정하기 (0) | 2022.06.02 |
export 'withRouter' (imported as 'withRouter') was not found in 'react-router-dom' (0) | 2022.03.31 |
react router 페이지 상단으로 올리기 (0) | 2022.03.28 |
[React] CSS 배경화면 가운데 글 넣기 (0) | 2022.03.25 |
- Total
- Today
- Yesterday
- read_csv
- next.config.js
- pandas
- nextjs autoFocus
- 자연어처리
- error:0308010C:digital envelope routines::unsupported
- vuejs
- Queue
- Express
- DFS
- UserCreationForm
- useHistory 안됨
- Python
- NextJS
- useState
- mongoDB
- nodejs
- react autoFocus
- react
- 자료구조
- django
- logout
- TensorFlow
- BFS
- Vue
- 클라우데라
- Deque
- login
- typescript
- JavaScript
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |