라우터

보통 일반적인 웹 애플리케이션에서는 URI로 요청을 보내면 서버에서 HTML 파일을 만들어서 내려주기 때문에 

페이지 새로고침이 일어나게된다. SPA는 클라이언트 측에서 화면을 렌더링하는 소스를 가지고 렌더링을 하기 때문에

새로고침이 일어나지 않으면서 화면의 변경이 이루어 진다. SPA 에서 URI에 따라 화면을 그려주는 것이다.

이 역할을 라우터가 한다.



React 는 라우터 기능을 지원하지 않기 때문에 라우팅을 하기 위한 라이브러리를 설치해주어야 한다.

npm install react-router-dom --save
npm install cross-env --save-dev

react-router-dom 은 라우팅을 할 수 있게 해주는 라이브러리 이고

cross-env는 프로젝트에서 NODE_PATH를 사용할수 있게 해주어

import 할 때 상대경로를 사용해야 하는데 설정에 따라 절대경로를 사용 할 수 있게 해주는 라이브러리 이다.



cross-env 설정

컴포넌트 import시에 절대경로를 사용할수있도록 package.json 파일의 scripts 를 수정한다

(../pages/page1 -> page/page1 이런식으로 import 할수 있게 해준다.)

"scripts": {
"start": "cross-env NODE_PATH=src react-scripts start",
"build": "cross-env NODE_PATH=src react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}


라우터 기본설정

src/client/App.js

컴포넌트를 특정 경로에 맵핑할 App.js 파일 생성

import React, { Component } from 'react';

class App extends Component {
render() {
return (
<div>
리액트 라우터
</div>
);
}
}

export default App;

src/client/Root.js

import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import App from 'client/App';

const Root = () => (
<BrowserRouter>
<App/>
</BrowserRouter>
);

export default Root;

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Root from 'client/Root';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<Root />, document.getElementById('root'));
registerServiceWorker();




pages 디렉토리 index.js 작성

src/pages/index.js

디렉토리의 index.js는 컴포넌트 import시에 각각 개별로 import 하지 않고 비구조화 할당으로 경로 한군데서 컴포넌트를 import 할 수 있게 해준다.

ex) import { Main, Page1, Pages, Basic } from 'pages';

export { default as Main } from './Main';
export { default as Pages } from './Pages';
export { default as Page1 } from './Page1';
export { default as Page2 } from './Page2';
export { default as Page3 } from './Page3';
export { default as Basic } from './Basic';




라우터 경로 맵핑

src/client/App.js

처음에 생성한 App.js에 컴포넌트와 경로들을 맵핑해준다.

import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';
import Menu from '../components/Menu';
import { Main, Page1, Pages, Basic } from 'pages';


class App extends Component {
render() {
return (
<div>
<h2>React Tutorial</h2>
<Menu />
<Route exact path="/" component={Main}/>
<Switch>
<Route path="/page1/:name" component={Page1}/>
<Route path="/page1" component={Page1}/>
</Switch>
<Route path="/Pages" component={Pages}/>
<Route path="/basic" component={Basic}/>
</div>
);
}
}

export default App;


<Route> 컴포넌트에 path 는 경로 component는 맵핑할 컴포넌트를 지정해준다.


exact 속성은 경로가 정확히 일치해야만 라우터가 맵핑된 컴포넌트를 렌더링한다.


<Switch> 컴포넌트는 같은경로에 조건부로 라우팅을 하는 컴포넌트이다.

위 소스에서는 Switch컴포넌트에서 url경로로 파라미터가 전달된 경우와 전달되지 않은 경우를 구분하고 있다.

먼저 조건에 일치하는 컴포넌트만 렌더링 되기 때문에 조건이 들어가는 경로를 더 위에 작성해주어야 한다.





라우터 링크

src/components/Menu.js

SPA는 페이지가 새로고침되지 않으면서 경로를 이동해야 한다. a 태그를 사용해 링크를 걸면 페이지가 새로고침 되면서 페이지를 이동하기 때문에

Link 라는 컴포넌트를 사용해 경로를 링크한다.

import React from 'react';
import { Link } from 'react-router-dom';

const Menu = () => {
return (
<div>
<h3>메뉴</h3>
<ul>
<li><Link to="/">메인</Link></li>
<li><Link to="/Basic">기본 기능</Link></li>
<li><Link to="/Page1">라우터</Link></li>
<li><Link to="/Page1/BruceLee?name=BruceLee">라우터에 파라미터 전달</Link></li>
<li><Link to="/Pages">라우터 중첩</Link></li>
</ul>
<hr/>
</div>
);
};

export default Menu;




라우터에 파라미터 전달 / 값 사용, 쿼리스트링 사용

src/pages/page1.js

라우터 경로 맵핑시에 <Route path="/page1/:name" component={Page1}/> name이라는 변수명으로 파라미터를 전달받을수 있게 맵핑후

파라미터를 받아서 값을 사용 할 수 있다. 기존의 get 방식으로 쿼리스트링을 전달하는 방식으로 파라미터를 전달 할 수도 있다.


page1에 라우팅하면서 name 이라는 변수명 으로 'BruceLee' 전달, 쿼리스트링으로 name=BruceLee 전달하는 라우터 링크

<Link to="/Page1/BruceLee?name=BruceLee">라우터에 파라미터 전달</Link>


import React from 'react';
import queryString from 'query-string';

const Page1 = ({location, match}) => {
const query = queryString.parse(location.search);
const name = query.name

return (
<div>
<h2>
Page1
<br/>라우트에서 name이라는 명칭으로 지정한 params : {match.params.name}
<br/>url 쿼리로 전달받은 name : {name}
</h2>
</div>
);
};

export default Page1;

match.params 에는 <Route path="/page1/:name"> 로 전달한 값을 꺼내 사용 할 수있고


url에서 ?name=BruceLee 라고 전달한 

쿼리스트링의 경우에는 location.search 객체에서 꺼내 직접 파싱을 해도 되고 쿼리스트링 파싱 모듈을 사용해도 된다.

npm install query-string --save

쿼리스트링을 파싱하는 모듈을 설치하고 값을 파싱하는 코드

const query = queryString.parse(location.search);
const name = query.name



라우터 중첩 / NavLink 컴포넌트

src/pages/pages.js

라우터 컴포넌트 안에서 현재의 페이지를 유지하면서 페이지 내부에서 다른화면을 보여주는 라우터 중첩도 가능하다.

match.url 로 현재 라우터가 보여주고 있는 페이지를 유지하면서 그 하위 경로로 보여줄 컴포넌트들을 맵핑 시켜 라우터를 중첩한다.


NavLink 컴포넌트는 페이지가 라우팅되어 보여지고 있을 때 그 페이지를 링크하는 컴포넌트에 스타일을 줄 수 있는 컴포넌트이다.

activeStyle 이라는 속성으로 페이지가 보여지고 있을때 활성화시킬 style 을 주어 사용한다.


import React from 'react';
import { NavLink, Route } from 'react-router-dom';
import { Page1, Page2, Page3 } from 'pages';

const Pages = ({match}) => {
const activeStyle={color: 'red', fontSize: '16px'}
return (
<div>
<h2>Post List</h2>
<ul>
<li><NavLink to={`${match.url}/page1`} activeStyle={activeStyle}>페이지1</NavLink></li>
<li><NavLink to={`${match.url}/page2`} activeStyle={activeStyle}>페이지2</NavLink></li>
<li><NavLink to={`${match.url}/page3`} activeStyle={activeStyle}>페이지3</NavLink></li>
</ul>
<Route exact path={match.url} render={()=>(<h3>라우터 중첩</h3>)}/>
<Route path={`${match.url}/page1`} component={Page1}/>
<Route path={`${match.url}/page2`} component={Page2}/>
<Route path={`${match.url}/page3`} component={Page3}/>
</div>
);
};

export default Pages;









참고

https://velopert.com/reactjs-tutorials

리액트에서 state나 props가 변경되면 render 함수가 실행되고 Virtual DOM과 실제 DOM 조작이 일어나게 된다.

변경된 state나 props의 값이 이전과 동일하면 Virtual DOM 만 렌더링이 발생하고 실제 DOM은 렌더링 되지 않지만

데이터가 많은 경우에 DOM이 렌더링이 되지 않더라도 Virtual DOM은 변경이 일어나기 때문에 자원이 낭비가 된다.

이를 방지하려면 리액트의 라이프사이클인 shouldComponentUpdate 에서 변경이 일어날 state나 props를 

변경 이전의 state와 props를 체크하여 동일할 경우에는 렌더링을 하지않고 다른 경우에만 렌더링을 해야 한다.


 

데이터 필터링 + 컴포넌트 최적화 구현



키워드를 입력받아 Array 중 검색어와 일치하는 Object를 새로운 Array로 만들어 리스트 컴포넌트에 전달해주는 컴포넌트

class Optimize extends Component {
state = {
keyword: '',
userList: [
{
id: 0 ,
name: 'Bruce Lee'
},
{
id: 1 ,
name: 'Jackie Chan'
},
{
id: 2 ,
name: 'Chuck Norris'
},
{
id: 3 ,
name: 'Jet Li'
}
]
}
handleChange = (e) => {
this.setState({
keyword: e.target.value,
});
}
render() {
console.log('render optimize')
const { keyword, userList } = this.state
const filterItemList = userList.filter(
item => item.name.indexOf(keyword) !== -1
)
return (
<div>
<div><input type="text" value={keyword} onChange={this.handleChange}/></div>
<div>
<ItemList data={filterItemList}/>
</div>
</div>
);
}
}



Array를 받아서 하위 컴포넌트에 Array의 원소인 Object를  props로 전달하는 컴포넌트

이 컴포넌트에서 shouldComponentUpdate 라이프사이클에서 전달받은 Array인 이전 props와 변경이 일어날 props를

체크하고 있지만 상위 컴포넌트에서 매번 새로운 Array를 전달받기 때문에 무조건 true가 리턴되어 

계속 새로운 렌더링이 일어날수밖에없다.

class ItemList extends Component {
static defaultProps = {
data: []
}

shouldComponentUpdate(nextProps, nextState) {
return nextProps.data !== this.props.data
}

render() {
console.log('render ItemList')
const {data} = this.props
const itemList = data.map(item=>(<ItemTemplate key={item.id} item={item}/>))
return (
<div>
<div>
{itemList}
</div>
</div>
);
}
}


전달받은 Object의 내용을 보여주는 컴포넌트 shouldComponentUpdate 라이프사이클에서 전달받은 props를 이전props와 비교하여

동일할 경우엔 렌더링을 하지 않고 다른 Object인 경우만 렌더링 함

class ItemTemplate extends Component {
static defaultProps = {
item: { id: '' , name: ''}
}

shouldComponentUpdate(nextProps, nextState) {
return nextProps.item !== this.props.item;
}

render() {
console.log('render itemTemplate')
const {name} = this.props.item
return (
<div>
{name}
</div>
);
}
}







참고

https://reactjs.org/docs/getting-started.html

https://velopert.com/reactjs-tutorials

리액트에서 리스트를 추가하거나 변경하거나 제거할때는 해당 array를 직접 건드려서는 안된다.

array 를 직접 추가하는 push 나 제거하는 splice 등을 사용해서는 안되고

기존 array를 건들지 않고 기존의 array에 원하는 작업을 처리하고 가공하여 새로운 array로 리턴해주는 concat, map, filter 를 사용해야 한다.



Array에 새로운 Object 추가


값을 입력받고 등록 버튼을 누르면 부모 컴포넌트로 이벤트를 전달해주는 컴포넌트 작성

class AddForm extends Component {
static defaultProps = {
onCreate: () => { console.warn('onCreate')}
}
state = {
name:''
}
handleChange = (e)=>{
this.setState({
name: e.target.value
})
}
handleClick = ()=>{
this.props.onCreate({name:this.state.name})
this.setState({
name: ''
})
}
render() {
const name = this.state.name
return (
<div>
<input type='text' value={name} onChange={this.handleChange}/>
<button onClick={this.handleClick}>등록</button>
</div>
);
}
}


Array의 Object를 수정, 삭제


Array의 내용을 보여주고 수정 버튼이나 삭제버튼 클릭시 수정 폼으로 전환하여 수정할수 있는 상태로 바꿔주고 삭제하며

이벤트를 부모 컴포넌트에게 전달하는 컴포넌트 작성

class ItemTemplate extends Component {
static defaultProps = {
item: { id: '' , name: ''},
onRemove: () => { console.warn('onRemove')},
onUpdate: () => { console.warn('onUpdate')},
}
state = {
isEdit: false,
name: ''
}
handleChangeMode = ()=> {
this.setState({
isEdit: (this.state.isEdit) ? false : true
})
}
handleChange = (e)=>{
this.setState({
name: e.target.value
})
}
handleRemove = () => {
const { item, onRemove } = this.props;
onRemove(item.id)
}
componentDidUpdate(prevProps, prevState) {
// 수정버튼 or 확인버튼을 클릭하면 state.sEdit 이 토글되고 render() 가 재 호출되기 전
// 일반상태- > 수정상태의 경우 props의 name을 state의 name에 넣어준다
// 수정상태 -> 일반상태의 경우 state의 name을 props의 이벤트에 파라미터로 넘긴다.

const { item, onUpdate } = this.props;
// 일반상태 -> 수정상태 : isEdit true -> false
if(!prevState.isEdit && this.state.isEdit) {
this.setState({
name: item.name,
})
}
// 수정상태 -> 일반상태 : isEdit false -> true
if (prevState.isEdit && !this.state.isEdit) {
onUpdate(item.id, {
name: this.state.name,
});
}
}
render() {
const {isEdit} = this.state;

// 수정상태
if(isEdit){
const {name} = this.state;
return(
<div>
<input type="text" value={name} onChange={this.handleChange}/>
<button onClick={this.handleChangeMode}>확인</button>
</div>
)
}
// 일반상태
else{
const {name} = this.props.item;
return(
<div>
{name}
<button onClick={this.handleChangeMode}>수정</button>
<button onClick={this.handleRemove}>삭제</button>
</div>
)
}
}
}



Array의 변경사항을 반영하는 컴포넌트

class ListRender extends Component {
state = {
userList:[
{
id: 0 ,
name: 'Bruce Lee'
},
{
id:1 ,
name: 'React'
},
{
id:2 ,
name: 'user'
}
]
}
// Array에 신규 Object 추가
handleCreate = (data) => {
// 파라미터로 받은 데이터를 Array에 추가
const { userList } = this.state;
this.setState({
// push가 아닌 concat 함수를 사용해야함
userList: userList.concat({ id: userList[userList.length-1].id+1, ...data })
})
}
// 삭제 처리
handleRemove = (id) => {
// 파라미터로 받은 id와 Array의 Object의 id와 일치하면
// 해당 id와 일치하는 object를 제거하고 state에 반영
const { userList } = this.state;
this.setState({
// filter 함수는 Array의 원소중에 조건에 일치하는 원소만으로 새로운 Array를 만들어 리턴해줌
userList: userList.filter(user => user.id !== id)
})
}
// 수정 처리
handleUpdate = (id, data) => {
// 파라미터로 받은 id와 Array의 Object의 id와 일치하면
// 해당 id와 일치하는 object를 수정하고 state에 반영
const { userList } = this.state;
this.setState({
// map 함수는 forEach처럼 Array의 길이만큼 callback을 실행하여 처리된 Array를 리턴해줌
// 파라미터로 받은 id와 일치하는 Object를 찾아 기존의 데이터에 새로운 데이터를 덮어쓴다.
userList: userList.map(
user => id === user.id
? { ...user, ...data }
: user
)
})
}
render() {
const {userList} = this.state
const itemList = userList.map(item=>
(<ItemTemplate key={item.id} item={item}
onUpdate={this.handleUpdate}
onRemove={this.handleRemove}/>)
)
return (
<div>
<div><AddForm onCreate={this.handleCreate}/></div>
<div>{itemList}</div>
</div>
);
}
}




결과





참고


https://reactjs.org/docs/getting-started.html

https://velopert.com/reactjs-tutorials

1
2
3
4
5
6
7
8
9
SELECT
    SYSDATE + 1, -- 현재시간 + 1일
    SYSDATE + 1/24, -- 현재시간 + 1시간
    SYSDATE + 1/24/60,  -- 현재시간 + 1분
    SYSDATE + 1/24/60/60,  -- 현재시간 + 1초
    (SYSDATE - (SYSDATE -1)) *24, -- 오늘날짜 - 어제날짜 경과한 시간 시간단위 환산
    (SYSDATE - (SYSDATE -1)) *24*60, -- 오늘날짜 - 어제날짜 경과한 시간 분단위 환산
    (SYSDATE - (SYSDATE -1)) *24*60*60 -- 오늘날짜 - 어제날짜 경과한 시간 초단위 환산
FROM DUAL
cs


'SQL' 카테고리의 다른 글

오라클 WITH  (0) 2018.07.11
oracle date YYYY-MM-DD 변환  (0) 2017.05.11
유효성 검사 쿼리  (0) 2017.03.29
mysql 날짜 차이구하기  (0) 2017.02.26
프로시저  (0) 2017.02.26

분을 입력받아 일과 시간으로 변환하는 함수


1
2
3
4
5
6
7
8
9
10
11
var MakeDateForm = function ( min ) {
    var days = Math.floor(min / 60 / 24)
    var hours = Math.floor((min - (days * 60 * 24 )) / 60);
    var mins = min - (days * 60 * 24- (hours * 60);
 
    var daysStr = days;
    var hourStr = (hours > 9)? hours : '0' + hours
    var minStr = (mins > 9)? mins : '0' + mins
 
    return daysStr + ' 일 ' + hourStr + ':' + minStr
}
cs


WITH 문은 오라클에서 서브쿼리에 alias를 붙여 테이블처럼 사용해주는 구문이다.

같은 서브쿼리를 여러번 사용하게 될 때 유용하게 사용 할 수 있다.


1
2
3
4
5
6
7
8
9
WITH TEMP_TABLE AS(
    SELECT
        'A' AS A
        , 'B 'AS B
    FROM DUAL
)
SELECT
    *
FROM TEMP_TABLE
cs


서브쿼리에 TEMP_TABLE 이라는 alias를 주고 SELECT 문에서 테이블처럼 사용 할 수있다.

쿼리 중간에 들어가면 혼란을 줄 수 있으므로 선언문처럼 쿼리문 상단에서 사용하는편이 좋다.

'SQL' 카테고리의 다른 글

오라클 시간환산  (0) 2018.07.11
oracle date YYYY-MM-DD 변환  (0) 2017.05.11
유효성 검사 쿼리  (0) 2017.03.29
mysql 날짜 차이구하기  (0) 2017.02.26
프로시저  (0) 2017.02.26
1
2
3
4
5
6
7
8
9
10
var tDate = new Date('2018-07-10 12:30')
 
// 날짜(일) 더하기
tDate.setDate(tDate.getDate()+1)
// 시간 더하기
tDate.setHours(tDate.getHours()+6)
// 분 더하기
tDate.setMinutes(tDate.getMinutes()+30)
 
console.log(tDate)
cs



1
2
3
4
5
6
7
8
9
function DateToString(pDate) {
    var yyyy = pDate.getFullYear();
    var mm = pDate.getMonth() < 9 ? "0" + (pDate.getMonth() + 1) : (pDate.getMonth() + 1); // getMonth() is zero-based
    var dd  = pDate.getDate() < 10 ? "0" + pDate.getDate() : pDate.getDate();
    var hh = pDate.getHours() < 10 ? "0" + pDate.getHours() : pDate.getHours();
    var min = pDate.getMinutes() < 10 ? "0" + pDate.getMinutes() : pDate.getMinutes();
    return "".concat(yyyy).concat("-").concat(mm).concat("-").concat(dd).concat(" ").concat(hh).concat(":").concat(min);
};
 
cs


+ Recent posts