안경잡이개발자

728x90
반응형

  지금까지 강의를 잘 따라오신 여러분들은 다음과 같이 완성된 프로젝트를 확인할 수 있게 될 거예요.


※ 완성된 프로젝트 ※



※ 복습하기 ※


  프로젝트를 완성해 나가면서 배웠던 큼직한 요소들에 대해서 복습을 해봅시다!


1) React 및 파이어베이스 호스팅


  본 강좌의 핵심이었던 React 개발 및 파이어베이스 호스팅에 대해서 잊으시면 안 돼요. React에서 다루는 각 데이터를 파이어베이스에 저장하고 관리했으며 파이어베이스 호스팅 서비스를 이용해 웹 사이트를 운영할 수 있었어요.



2) 플라스크(Flask) API 서버 구축


  이후에 우리는 AWS EC2를 이용하여 플라스크에 워드 클라우드 API를 달아서 구동시켰습니다. 프리 티어를 이용해 무료로 서버를 구축하고, 이에 웹 서버를 올리는 방식으로 효과적으로 API를 구축했답니다.



3) 클라우드 플레어(Cloud Flare) HTTPS 서비스


  이후에는 도메인 주소를 생성하고, 클라우드 플레어 서비스를 이용해서 우리의 API 서버에 HTTPS를 입히는 과정을 거쳤어요.



4) 파이어베이스(Firebase)


  파이어베이스(Firebase)의 데이터베이스 서비스를 활용하는 방법에 대해서도 공부하는 시간을 가졌습니다.



5) 안드로이드 스튜디오(Android Studio)


  우리는 최종적으로 만들어진 웹을 앱 형태로 배포하기 위하여 안드로이드 스튜디오를 이용해 웹 앱을 개발하고, APK 파일을 추출했어요.



728x90
반응형

728x90
반응형

1. 텍스트 가지치기 모듈 다운로드


  yarn add react-text-truncate


  (설치가 안 된다면, 개발 환경을 관리자 권한으로 실행해보세요.)



2. 소스코드 작성하기


▶ ./src/components/Texts.js


import TextTruncate from 'react-text-truncate';
import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import TextField from '@material-ui/core/TextField';
import { Link as RouterLink } from 'react-router-dom';
import Link from '@material-ui/core/Link';

const styles = theme => ({
hidden: {
display: 'none'
},
fab: {
position: 'fixed',
bottom: '20px',
right: '20px'
},
});

const databaseURL = "https://react-example-55161.firebaseio.com/";

class Texts extends React.Component {

constructor(props) {
super(props);
this.state = {
fileName: '',
fileContent: null,
texts: '',
textName: '',
dialog: false
}
}

_get() {
fetch(`${databaseURL}/texts.json`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(texts => this.setState({texts: (texts == null) ? {} : texts}));
}

_post(text) {
return fetch(`${databaseURL}/texts.json`, {
method: 'POST',
body: JSON.stringify(text)
}).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(data => {
let nextState = this.state.texts;
nextState[data.name] = text;
this.setState({texts: nextState});
});
}

_delete(id) {
return fetch(`${databaseURL}/texts/${id}.json`, {
method: 'DELETE'
}).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(() => {
let nextState = this.state.texts;
delete nextState[id];
this.setState({texts: nextState});
});
}

componentDidMount() {
this._get();
}

handleDialogToggle = () => this.setState({
dialog: !this.state.dialog,
fileName: '',
fileContent: '',
textName: ''
})

handleValueChange = (e) => {
let nextState = {};
nextState[e.target.name] = e.target.value;
this.setState(nextState);
}

handleSubmit = () => {
const text = {
textName: this.state.textName,
textContent: this.state.fileContent
}
this.handleDialogToggle();
if (!text.textName || !text.textContent) {
return;
}
this._post(text);
}

handleDelete = (id) => {
this._delete(id);
}

handleFileChange = (e) => {
let reader = new FileReader();
reader.onload = () => {
let text = reader.result;
this.setState({
fileContent: text
})
}
reader.readAsText(e.target.files[0], "EUC-KR");
this.setState({
fileName: e.target.value
});
}

render() {
const { classes } = this.props;
return (
<div>
{Object.keys(this.state.texts).map(id => {
const text = this.state.texts[id];
return (
<Card key={id}>
<CardContent>
<Typography color="textSecondary" gutterBottom>
내용: {text.textContent.substring(0, 24) + '...'}
</Typography>
<Grid container>
<Grid item xs={6}>
<Typography variant="h5" component="h2">
{text.textName.substring(0, 14) + '...'}
</Typography>
</Grid>
<Grid item xs={3}>
<Typography variant="h5" component="h2">
<Link component={RouterLink} to={"detail/" + id}>
<Button variant="contained" color="primary">보기</Button>
</Link>
</Typography>
</Grid>
<Grid item xs={3}>
<Button variant="contained" color="primary" onClick={() => this.handleDelete(id)}>삭제</Button>
</Grid>
</Grid>
</CardContent>
</Card>
);
})}
<Fab color="primary" className={classes.fab} onClick={this.handleDialogToggle}>
<AddIcon />
</Fab>
<Dialog open={this.state.dialog} onClose={this.handleDialogToggle}>
<DialogTitle>텍스트 추가</DialogTitle>
<DialogContent>
<TextField label="텍스트 이름" type="text" name="textName" value={this.state.textName} onChange={this.handleValueChange} /><br /><br />
<input className={classes.hidden} accept="text/plain" id="raised-button-file" type="file" file={this.state.file} value={this.state.fileName} onChange={this.handleFileChange} />
<label htmlFor="raised-button-file">
<Button variant="contained" color="primary" component="span" name="file">
{this.state.fileName === ''? ".txt 파일 선택" : this.state.fileName}
</Button>
</label>
<TextTruncate
line={1}
truncateText="..."
text={this.state.fileContent}
/>
</DialogContent>
<DialogActions>
<Button variant="contained" color="primary" onClick={this.handleSubmit}>추가</Button>
<Button variant="outlined" color="primary" onClick={this.handleDialogToggle}>닫기</Button>
</DialogActions>
</Dialog>
</div>
);
}
}

export default withStyles(styles)(Texts);


▶ ./src/components/Detail.js


import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';

class Detail extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Card>
<CardContent>
{this.props.match.params.textID}
</CardContent>
</Card>
);
}
}

export default Detail;


▶ ./src/components/App.js


import React from 'react';
import { HashRouter as Router, Route } from 'react-router-dom';
import AppShell from './AppShell';
import Home from './Home';
import Texts from './Texts';
import Words from './Words';
import Detail from './Detail';

class App extends React.Component {
render() {
return (
<Router>
<AppShell>
<div>
<Route exact path="/" component={Home}/>
<Route exact path="/texts" component={Texts}/>
<Route exact path="/words" component={Words}/>
<Route exact path="/detail/:textID" component={Detail}/>
</div>
</AppShell>
</Router>
);
}
}

export default App;


※ 실행 결과 ※


  텍스트 페이지에서 텍스트 파일을 올려서 내용을 확인할 수 있습니다.





  [보기] 버튼을 누르면 상세 페이지로 이동됩니다. 현재는 ID 값만 보내도록 처리했습니다.



728x90
반응형

728x90
반응형

  리액트(React)는 라이브러리로서 별도의 사용 방법이 정해져 있습니다. 최신 가장 많이 사용되고 있는 자바스크립트 버전은 ES6입니다. ES6부터는 최신 프로그래밍 트렌드를 따르는 다양한 문법들이 추가되었습니다.


  흔히 리액트를 이용해 개발하는 사람들은 그러한 새로운 문법을 사용하기 때문에 웹 브라우저(Web Browser)가 ES6을 지원하지 않는다면 우리가 작성한 웹 사이트가 어떠한 사람에게는 동작하지 않고, 어떠한 사람에게만 동작하는 문제가 생길 수 있습니다. 그래서 바벨(Babel)을 이용하면 우리가 작성한 소스코드를 다양한 웹 브라우저 환경에 구애받지 않고 실행되도록 만들 수 있어요.


  또한 최근에는 상당히 복잡한 의존성 트리를 갖추어 다양한 기능을 제공하는 웹 서비스가 많습니다. 말 그대로 자바스크립트(JavaScript)의 복잡성이 생긴 상태입니다. 이러한 복잡성 문제를 해결하기 위해 등장한 개념이 웹팩(Webpack)입니다.


  웹팩(Webpack)모듈 번들러(Module Bundler)입니다. 그래서 프로젝트의 구조를 분석해 자바스크립트 모듈들을 번들로 묶어서 패킹할 수 있습니다. 실질적으로 웹팩은 특정한 메인 코드에서 출발하여 모든 import 구문 등을 분석하여 의존성을 파악해 로더를 이용해 처리하고 번들로 묶인 자바스크립트 코드 생성합니다.


  이러한 웹팩은 Node.js를 이용해서 구동시킬 수 있습니다. 이 때 NPM(Node Package Manager)를 이용해 설치하면 됩니다.


  결과적으로 리액트 프로젝트를 만들고자 한다면 웹팩, 바벨 등을 이용해서 직접 프로젝트를 구성해야 합니다. 하지만 이러한 과정은 초보자 분들에게 많은 지식을 요구한다는 점에서 학습에 방해요소로서 작용합니다. 그래서 우리는 CRA(Create React App)이라는 오픈소스를 사용해서 바로 React 프로젝트를 구축하여 실습을 진행해 볼 예정입니다.


※ Node.js와 NPM 설치 ※


  과거에는 Node.js를 설치한 이후에 NPM을 추가적으로 설치해야 했습니다. 하지만 현재는 Node.js를 설치하면 자동으로 NPM도 함께 설치가 됩니다. 그래서 Node.js가 설치되어 있지 않으신 분들은 바로 Node.js를 설치하시면 됩니다.


  ▶ Node.js 설치하기: https://nodejs.org/ko/


  저는 최신 버전인 11.6.0 버전으로 설치를 해보겠습니다.



  설치는 단순히 다음(Next) 버튼만 눌러도 성공적으로 진행됩니다.



  설치 완료에는 버전(Version)을 확인하는 명령어로 정상 설치되었는지 확인하시면 됩니다.



※ CRA(Create React App) 설치하기 ※


  NPM 명령어를 이용해서 CRA를 설치할 수 있습니다.


  npm install -g create-react-app



※ React 프로젝트 생성하기 ※


  저희는 강좌에서 간단한 형태의 SNS 서비스를 만들어 볼 예정입니다. 따라서 특정한 폴더로 이동하여 create-react-app 명령어를 이용하여 SNS라는 폴더를 만들어 보도록 하겠습니다.



  보시면 create-react-app는 yarn이라는 패키지 매니저를 이용해 설치됩니다. 그래서 결과적으로 yarn start 명령어를 이용해 프로젝트를 구동시킬 수 있습니다. 만약에 yarn이 없다는 오류 메시지가 출력되는 경우 다음의 명령어로 yarn을 설치할 수 있습니다.


  npm install --global yarn


  결과적으로 다음과 같이 yarn start 명령어로 앱이 구동되어야 합니다.



  실행 결과 기본 포트인 3000번 포트로 웹 사이트가 구동되는 것을 확인할 수 있습니다.



  실제로 브라우저를 통해 접속하면 다음과 같이 접속이 완료됩니다.



  이제 비주얼 스튜디오 코드(Visual Studio Code)와 같은 에디터(Editor)을 이용해서 해당 프로젝트 폴더를 여시면 됩니다. 그러면 다음과 같이 웹 사이트를 위한 온갖 리소스들이 기본적으로 미리 구축되어 있는 것을 확인할 수 있습니다.



  기본적으로 Create React App으로 생성된 프로젝트는 앱을 수정한 이후에 저장만 해도 알아서 웹 사이트에 적용된다는 특징이 있습니다.



  위와 같이 App.js 파일을 수정하여 Hello React!라는 메시지를 출력할 수 있도록 해봅시다. 그러면 브라우저를 수동으로 새로고침 하지 않아도 자동으로 브라우저가 새로고침 되면서 내용이 변경됩니다.



728x90
반응형