본 포스팅은 facebook React Korea에 올라온 질문을 바탕으로 작성한 포스팅입니다.


프로젝트에 npm 모듈 적용을 어려워하시는 분들을 위한 예제입니다.


시작하겟습니다.



프로젝트를 생성하겠습니다.


create-react-app 을 이용하여 생성하겠습니다.


터미널에서 create-react-app react-image-cropper 으로 프로젝트 생성해주세요.


그리고 react-image-cropper 를 설치해주세요.


npm 을 사용하면,  npm i -S react-image-cropper

yarn 을 사용하면, yarn add react-image-cropper 입니다.


그리고 src/App.js 에 추가해보도록 하겠습니다.


상단에 import 해주세요.


import {Cropper} from 'react-image-cropper'


그리고 원하는 이미지를 넣어주겠습니다.


저는 그냥 파일로써 import하여 사용하겠습니다.


test.jpg라는 이미지 파일을 src 폴더에 넣은뒤 import 하겠습니다.


import testImage from './test.jpg';



App Component Class 작성입니다.

class App extends Component {
constructor(props) {
super(props);
this.state = {
image: '',
imageLoaded: false,
}
}
handleImageLoaded(state){
this.setState({
[state + 'Loaded']: true
});
}

handleClick(state){
let node = this.refs[state];
this.setState({
[state]: node.crop()
});
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
// 여기부터 cropper component 사용한 부분
<h3>Default image crop</h3>
<Cropper src={testImage} ref="image" onImgLoad={() => this.handleImageLoaded('image')}/>
<br/>
{this.state.imageLoaded ? <button onClick={() => this.handleClick('image')}>crop</button> : null}
<h4>after crop</h4>
{this.state.image ? <img src={this.state.image} alt=""/> : null}
</div>
);
}
}


생성자에서 state를 생성하여, crop된 image를 저장하는 부분과 , 이미지 로드가 되어있는지 아닌지에 대해 저장하는 부분을 작성하도록 합니다.


handleImageLoaded는 이미지가 로드되면 state.imageLoaded를 true로 바꿔줍니다.


handleClick은 클릭시  만약 이미지가 로드되어 있다면, 크롭 이미지를 저장합니다.


저장하고, yarn start 혹은 npm start로 실행하여 확인하여 보시기 바랍니다.





'React' 카테고리의 다른 글

2. express 적용하기  (0) 2017.07.10
1. CRA로 프로젝트 생성  (1) 2017.07.10
블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,

이전 포스팅에 이어서 react router 입니다.


이번이 react router의 마무리 포스팅입니다.


중첩라우팅


이번에는 /post 라는 주소로 접속시 postList를 보여주는 것을 중첩 라우팅으로 구현하여 보겠습니다.


postList 라는 컴포넌트를 만들고, 이 컴포넌트 안에서


/post 라는 주소로 접속시에는 postList 라고 띄워주고, parameter를 넣은 주소 즉 /post/:postId 로 접속시에는 postId를 보여주는것을 만들어 보겠습니다.


우선 containers 폴더에 PostList.tsx 파일을 생성해주세요.


그리고 App.tsx 파일에서 /post 접속시 렌더링되는 component를 변경하겠습니다.


<Route path="/post" component={PostList} />


* src/containers/index.tsx 에 PostList 추가와 App.tsx 에 import 시키는것 잊지 마세요.

* Post.tsx 파일을 components 폴더를 생성하여 빼는 것도 생각해봤는데, 이번에는 그냥 하도록 하겠습니다.


src/containers/PostList.tsx

import * as React from 'react';
import { Route, RouteComponentProps } from 'react-router-dom';
import { Post } from '.';

const PostList = (props: RouteComponentProps<{}>) => {

return (
<div>
<Route exact={true} path={props.match.url} render={() => <h3>포스트 목록</h3>}/>
<Route path={`${props.match.url}/:postId`} component={Post}/>
</div>
);
};

export default PostList;


우선 함수형 컴포넌트로 PostList를 만들겠습니다.


그리고 현재 접속된 주소를 알기위해서 RouteComponentProps를 받고,

라우트를 걸어주겠습니다.

들어온 주소가 정확하게 일치하다면 ( exact = {true} )  포스트 목록이라는 h3 태그를 렌더합니다.

하지만 만약 주소에 parameter가 있다면 Post 컴포넌트를 렌더링 한다는 코드입니다.


* 현재 접속 주소를 아는 방법은 match.url 말고 다른 방법들도 많이 있습니다.

한번 해보시는것을 추천드립니다.

이전 포스팅을 참고하면 알수있을것이라 생각됩니다.


실행시켜보겠습니다.



버튼도 누르면 제대로 동작함을 확인 가능합니다.



Switch


지금까지 Route를 사용하면서, 속성값으로 exact를 사용해보셨습니다.

이것을 쓰지 않을경우, 주소값 일부만 포함되더라도 렌더링 되는것을 막기위해 쓰던 속성이었는데요, Switch 모듈을 사용하면 이것을 쓰지 않고도 사용이 가능합니다.

( 다만 루트 주소의 경우에는 exact를 쓰는것이 좋습니다 )

사용법이나 효과는 다른 언어들에서 사용되는 Switch와 매우 비슷합니다.


Switch는 Route중 매치되는 첫번째 것만 렌더하게 되는데요,


한번 사용해보겠습니다.


App.tsx에서 Switch를 import 해주시고, Route 부분을 Switch 로 감싸주세요.


import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';


<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/post" component={PostList} />
</Switch>


실행해도 다른점이 나오지 않을것입니다.


그렇다면 이번에는 Route를 하나 추가하여 해보겠습니다.


<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/post" component={PostList} />
<Route path="/:params" render={() => <h1>params</h1>} />


이렇게 추가하고, Switch 를 삭제한다음 실행해보도록 하겠습니다.


그리고 about 이나 post로 접속을 해보면




이처럼 params 가 계속 뜨는것을 볼수있습니다.


이것을 막기 위해서 exact를 다 쓰기보다는 Switch로 해결해보도록 하겠습니다.


다시 Route들을 Switch 로 감싸주세요.


<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/post" component={PostList} />
<Route path="/:params" render={() => <h1>params</h1>} />
</Switch>


그리고 실행하면, 원하던대로 렌더링 되는것을 확인할수 있습니다.


하지만 만약 /:params가 위에 있다면 어떻게 될까요?


<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/:params" render={() => <h1>params</h1>} />
<Route path="/about" component={About} />
<Route path="/post" component={PostList} />
</Switch>


이렇게 about과 post보다 먼저 쓰여지게 되는경우 어떻게 작동하는지 확인해 보도록 하겠습니다.



about, post 모두 params만 렌더링 하게 됩니다.


왜냐하면 가장 먼저 매칭되는것이 :params 이니까요.

( 이러한 이유로 루트의 경우에는 exact를 쓰는것이 편리합니다. )


따라서 Switch를 쓰실때에는 Route 순서에 유의하여 작성하셔야 합니다.


이번에는 지정되지 않은 path로의 접근시 404 페이지를 보여주는 예제를 만들어 보겠습니다.


간단하게 Switch 맨 아래 path를 지정하지 않은 Route를 만들어 주세요.


src/containers/NotFound.tsx

import * as React from 'react';

const NotFound = () => {
return (
<h1>404 Not Found</h1>
);
};

export default NotFound;


src/conatiners/App.tsx

<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/post" component={PostList} />
<Route component={NotFound} />
</Switch>


실행한뒤 이상한 주소로 접속해주세요.



저는 localhost:3000/jaro 로 접속했습니다.


제대로 404 Not Found 가 렌더링 되는것을 확인할수 있습니다.


Switch

- <Route> 를 감싸서 사용

- Javascript의 Switch와 비슷

- <Route> 중 매치되는 첫번째만 렌더

- 매치되는 path가 없을때는 아무것도 렌더하지 않음 혹은 path를 지정하지 않은 것을 렌더

- 순서에 유의


Redirect


이번에는 Redirect에 대해 살펴보겠습니다.

Redirect의 경우에는 어떠한 주소로 접속시 그것을 다른 주소로 보내주는것입니다.

다만 여기서의 redirect는 push가 아닌 replace방식이라 history에 남지 않습니다.

location 객체를 통해서도 redirect가 가능합니다.


예제를 통해 살펴보겠습니다.


Admin 이라는 컴포넌트를 생성하겠습니다.


src/containers/Admin.tsx

import * as React from 'react';
import { Redirect } from 'react-router-dom';

const Admin = () => {
const auth = true;
return auth ?
<h1>Admin페이지</h1> :
<Redirect to="/" />;
};

export default Admin;


여기서는 Redirect를 사용하기 위해 import 시켜주었습니다.


또한 인증에 따라 보여주는 페이지가 다르므로 auth 를 보고 true면 Admin 페이지라고 렌더링 하고

그렇지 않다면 redirect 시켜주기 위해 Redirect를 사용하였습니다.

( auth는 구현하지 않고 그냥 쓰도록 하겠습니다. )

기본적으로 Redirect는 Link 와 사용법이 거의 같습니다.


그리고 App.tsx 파일에 Route를 추가해주세요. ( 링크도 추가해주세요 )


<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/post" component={PostList} />
<Route path="/admin" component={Admin} />
<Route component={NotFound} />
</Switch>


실행 시켜 보겠습니다.



auth를 true로 해놨으므로 Admin 페이지가 렌더링 되는것을 확인할수 있습니다.


이번에는 auth를 false로 바꾸고 실행해보도록 하겠습니다.



아무리 admin 페이지에 접근하려 해도 접근이 되지 않고, 루트로 redirect되는것을 확인할수 있습니다.


이번에는 redirect의 또다른 사용법입니다.


만일 예전에 쓰던 주소를 다른 주소로 변경하였을때 사용법인데요,


만약 이전에 /info 라는 주소가 있었는데 이 주소를 더이상 쓰지 않고 about으로 redirect 하려 한다면, Switch 안에 redirect를 추가해주시면 됩니다.


App.tsx 에도 Redirect를 import 해주고,

import { BrowserRouter as Router, Route, Link, Switch, Redirect } from 'react-router-dom';


<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Redirect from="/info" to="/about" />
<Route path="/post" component={PostList} />
<Route path="/admin" component={Admin} />
<Route component={NotFound} />
</Switch>


Redirect를 추가해주세요.


from 은 이전주소, to는 redirect 시킬 주소입니다.


실행한뒤 /info 주소로 접속해보시면, redirect되는것을 확인할수 있습니다.


from은 꼭 Switch와 같이 쓰시기 바랍니다.


왜 그런지 궁금하신분들은 Switch를 빼고 해보시면 바로 이유를 알수있을거라 생각됩니다.


( 주소로 접근시 무조건 redirect 됩니다.. )


NavLink


NavLink 는 조금 특별한 Link입니다.

to 에 지정한 path와 URL이 매칭되는 경우 특별한 스타일 부여 가능합니다.


이전에 만든 Link들을 NavLink로 바꾸어서 사용해보도록 하겠습니다.


<nav>
<ul>
<li>
<NavLink to="/" activeStyle={{ fontWeight: 'bold', color: 'blue' }}>Home</NavLink>
</li>
<li>
<NavLink to="/about" activeStyle={{ fontWeight: 'bold', color: 'blue' }}>About</NavLink>
</li>
<li>
<NavLink to="/post" activeStyle={{ fontWeight: 'bold', color: 'blue' }}>Post</NavLink>
</li>
<li>
<NavLink to="/admin" activeStyle={{ fontWeight: 'bold', color: 'blue' }}>Admin</NavLink>
</li>
</ul>
</nav>


모두 NavLink로 바꾸고 activeStyle를 추가해주었습니다.


이제 path와 URL이 매치되면 글자가 굵어지고 파란색이 되어야 합니다.


실행해 보도록 하겠습니다.



괜찮은거 같은데 뭔가 이상합니다.


Home 글자는 계속 Active 상태인것처럼 나오네요.


루트 NavLink에는 exact={true} 를 추가해주세요.



이제 원하는대로 동작합니다.



여기까지가 기본적인 react router V4 에 대한 내용이었습니다.

강의를 해주신 이현섭님께 감사드립니다.


원래 다음 포스팅은 스터디 순서에 맞게 react 를 server side rendering으로 바꾸는 법을 해야하지만, 좀더 공부를 하고 포스팅을 하기 위해 잠시 넘어가고 redux에 대해 포스팅 하려고 합니다.


좀더 열심히 공부하여 더 좋은 포스팅을 쓰도록 노력하겠습니다.


감사합니다.


이번 포스팅의 소스주소입니다.


https://github.com/JaroInside/tistory-react-typescript-study/tree/12.react-router-part3



참고 슬라이드 - http://slides.com/hyunseob/react-router#/

참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0



'React > React&typeScript' 카테고리의 다른 글

13. redux - part2  (0) 2017.07.24
13. redux - part1  (0) 2017.07.23
12. REACT ROUTER V4 - part2  (0) 2017.07.20
12. React Router V4 - part1  (0) 2017.07.19
11. 서버사이드 렌더링 , 클라이언트 사이드 렌더링  (4) 2017.07.18
블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,

지난 포스팅에 이어 react-router를 계속 이어가보도록 하겠습니다.


Route props


우선 Route의 props를 살펴보기전에 parameter부터 살펴보도록 하겠습니다.


part1 에서 사용했던 프로젝트의 containers 폴더에


post.tsx 를 새로 생성하고, 함수형 컴포넌트를 작성해주세요.


그리고 App.tsx에 Post와 Link를 삽입해주시기 바랍니다.


src/contatiners/Post

import * as React from 'react';

const Post = () => {
return (
<h1>Post - func Component</h1>
);
};

export default Post;


src/containers/index.tsx

import App from './App';
import Home from './Home';
import About from './About';
import Post from './Post';

export { App, Home, About, Post };



src/containers/App.tsx

import * as React from 'react';
import './App.css';

import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

import { Home, About, Post } from '.';

const logo = require('./logo.svg');

class App extends React.Component<{}, {}> {
render() {
return (
<Router>
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/post">Post</Link>
</li>
</ul>
</nav>
<Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/post/:postId" component={Post} />
</div>
</Router>
);
}
}

export default App;


post의 path에 :postId 가 추가되어있는것을 볼수 있는데요,

이것을 parameter라고 부릅니다.

간단히 애기하면 Url을 통해 넘겨지는 값이라고 생각하시면 편할것 같습니다.


일단 프로젝트 실행후

localhost:3000/post/1 주소로 접속해보겠습니다.



Post component가 렌더링 된것을 확인할수 있습니다.

그렇다면 /1 이라고 보낸 postId parameter는 어디로 간것일까요?


react dev tool 로 확인해보도록 하겠습니다.



생성된 Post component에 3개의 props가 있는것을 확인할수 있습니다.


그중 match를 먼저 살펴보면



params에 postId를 확인할수 있습니다.


그럼 이제 postId를 화면의 띄워보겠습니다.


Post.tsx를 수정해주세요.


import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Post = (props: RouteComponentProps<{ postId: string}>) => {
return (
<p>post {props.match.params.postId}</p>
);
};
export default Post;

컴포넌트에서 Route를 통해 들어온 props를 사용하기 위해서는 RouteComponentPorps라는것을 사용해주어야 합니다.


따라서 Props에 정의해주고, postId가 string임을 알려줍니다. ( typesciprt니까...)


그리고 이번에는 렌더를 props.match.params.postId로 하여 parameter값을 확인할수 있게 합니다.


저장하고 실행해보겠습니다.



이전처럼 postId에 1을 넣으면 1값이 나오고, 이상한 값을 넣어도 제대로 렌더링이 되는것을 확인할수 있습니다.


이런식으로 parameter를 얻어올수 있습니다.


이번에는 다른 props를 사용하는 예제를 살펴보겠습니다.


NextPost 라는 예제입니다.


Post component에 버튼을 추가해보록 하겠습니다.


import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Post = (props: RouteComponentProps<{ postId: string}>) => {
function goNextPost() {
const currPostId = props.match.params.postId;
const nextPostId = +props.match.params.postId + 1 + '';
const { pathname } = props.location;
const nextPath = pathname.replace(currPostId, nextPostId);
props.history.replace(nextPath);
}
return (
<div>
<h3>Post {props.match.params.postId}</h3>
<p>{new URLSearchParams(props.location.search).get('body')}</p>
<button onClick={goNextPost}>Next post</button>
</div>
);
};

export default Post;


params에는 숫자만 들어온다고 가정하고 만든 goNextPost 함수입니다.


버튼을 누르면 paramId가 1씩 증가되는 버튼으로


함수를 살펴보겠습니다.


currPostId 에서는 현재의 postId 값을 받아옵니다.

nextPostId 는 증가된 값을 가져오는데, props.match.params.postId 앞에 + 를 붙여준 이유는

string 값으로 들어오기때문에 강제적으로 number로 바꿔주기 위해서입니다. ( 꼼수입니다 )

props.location에서 현재 pathname을 가져온뒤 replace 함수를 사용하여 주소를 바꿔줍니다.

그리고 히스토리도 바꿔줍니다.


물론 이것보다 더 간단하게 구현할수도 있습니다.


import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Post = (props: RouteComponentProps<{ postId: string}>) => {
function nextPost() {
const next = +props.match.params.postId + 1;
props.history.push(`/post/${next}`);
}
return (
<div>
<h1>post {props.match.params.postId}</h1>
<button onClick={nextPost}>nextPost</button>
</div>
);
};

export default Post;


이렇게 하면 더 간단하게 구현 가능하지만,


방법은 여러가지이므로 여러방법을 시도해보시는것을 추천드립니다.


-> prop를 여러 방면에서 다뤄보고 좀더 포스팅을 업데이트 하겠습니다.



* props.history의 replace와 push의 차이입니다.

import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Post = (props: RouteComponentProps<{ postId: string}>) => {
function goNextPost() {
const currPostId = props.match.params.postId;
const nextPostId = +props.match.params.postId + 1 + '';
const { pathname } = props.location;
const nextPath = pathname.replace(currPostId, nextPostId);
props.history.replace(nextPath);
}
return (
<div>
<h3>Post {props.match.params.postId}</h3>
<button onClick={goNextPost}>Next post</button>
</div>
);
};
export default Post;


이렇게 작성된 Post component 를 실행했을때,


props.history.replace(nextPath);

여기서 replace를 

props.history.push(nextPath);

push로 바꿨을때의 차이점은


처음에 실행하였을때는 알수 없습니다.


하지만 버튼을 여러번 눌러본뒤 브라우져의 뒤로가기 버튼을 눌러보세요.


replace는 버튼을 누르기 전으로 되돌아가지만, push는 이전 버튼을 누르기 전으로 돌아갑니다.


즉 history를 가지고 있는가 없는가의 차이입니다.



이번에는 Query String 파싱 예제를 해보겠습니다.


만약 localhost:3000/post/2?body=reactrouter 라는 query string이 들어오면 어떻게 될까요?


이 querystring을 사용하여 어떠한 작업을 하고 싶다면??


props의 location.search 를 사용하시면 됩니다.


이것을 parsing 하기 위해서 이번 포스팅에서는 URLSearchParams 라는것을 사용할것인데요,


사실 이것은 브라우져의 지원율이 좀 낮기때문에 사용하는것에 제약을 두시기 바랍니다.


지금은 공부하는것이므로 사용해보겠습니다.


( query-string 이라는 라이브러리를 다운받아 쓰셔도 됩니다! )


import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Post = (props: RouteComponentProps<{ postId: string}>) => {
function goNextPost() {
const currPostId = props.match.params.postId;
const nextPostId = +props.match.params.postId + 1 + '';
const { pathname } = props.location;
const nextPath = pathname.replace(currPostId, nextPostId);
props.history.replace(nextPath);
}
return (
<div>
<h3>Post {props.match.params.postId}</h3>
<p>{new URLSearchParams(props.location.search).get('body')}</p>
<button onClick={goNextPost}>Next post</button>
</div>
);
};

export default Post;


return 에 query string을 표시해주는것을 추가하였습니다.


URLSearchParams를 이용하여 props.location.search의 body={ 값 } 을 해석하여 렌더링 할것입니다.


만약 .get('body') 를 빼면 어떻게 나오는지는 한번 해보시기 바랍니다.


자 그럼 실행하여 location:3000/post/2?body=리액트라우터

라고 해보세요.



query string이 제대로 렌더링 되는것을 확인할수 있습니다.


즉 query string을 이용하여 어떠한 작업이 필요하다면 props.loaction.search를 이용하여 query string을 가져와서 사용하면 될것 같습니다.


정리입니다.


Route를 통해 component에는 총 3개의 객체가 넘어오게 됩니다.


1. history

2. location

3. match


- history

- 브라우져의 window.history와 비슷합니다.

- 주소를 임의로 변경하거나 되돌아갈수 있도록 합니다,

- 주소를 변경하더라도 SPA의 특성에 맞게 페이지 전체를 리로드 하지 않고 페이지 일부만 리로드 합니다.


-location

- 브라우져의 window.location와 비슷합니다.

- 현재 페이지에 대한 정보를 가지고 있으며, URL을 쉽게 쪼개서 가지고 있습니다.

- URL의 query정보 또한 가지고 있습니다.


- match

- Route의 path에 정의한 것과 매치된 정보를 담고 있습니다.


이번 포스팅에서는 Route에서 compoenet로 넘어오는 prop에 대해 알아보았습니다.


소스코드 주소입니다.


https://github.com/JaroInside/tistory-react-typescript-study/tree/12.react-router-part2


clone 후 branch checkout 하여 확인 가능합니다.


감사합니다.



참고 슬라이드 - http://slides.com/hyunseob/react-router#/
참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0



블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,