서버리스 아키텍처 소셜미디어 개발기 3편

지난 번 글에서는 기초적인 편집기를 만드는 작업을 했습니다. 사용자 스토리에 기반한 하나의 편집기와 버튼으로만 구현할 수 있도록 화면을 구성했구요.

서버리스 아키텍처 소셜미디어 개발기 2편
_지난 글에 이어 계속 됩니다._techstory.shma.so

이번에는 Firebase를 본격적으로 다뤄보도록 하겠습니다.

1. 사용자 스토리2

김개발이 사이트를 방문해서 자신이 어제 유심하게 읽은 글을 올릴 수 있다. 이렇게 하면 다른 사람들이 볼 수 있다.
* 에디터 창은 하나만 있고 거기서 글을 작성하고 업로드 하면 글이 외부 클라우드 공간에 저장이 된다.

이제는 사용자 스토리의 약간 디테일한 부분을 살펴보고그 부분에 대한 것들이 어떻게 더 풀어져 나가는지 살펴 보도록 하겠습니다.

에디터 창은 하나만 있고 거기서 글을 작성하고 업로드 하면 글이 외부 클라우드 공간에 저장이 된다.

에디터 창은 하나만 있고 라는 표현을 썼을까요? 대부분의 소셜미디어들은 즉각적으로 남기고 싶은 말들을 남기고 업로드를 누르면 알아서 필요한 정보들을 이쁘게 만들어서 내어 줍니다. 그것과 유사하게 우리도 카드라는 형태를 만들어 볼 것인데, 일단 뒷쪽에 표현된 업로드 하면 글이 외부 클라우드 공간에 저장이 된다. 라는 데에 조금 더 관심을 기울여 만들어 보도록 하겠습니다.

2. Firebase

Firebase를 제가 처음 본 것은 Angular를 한참 공부하던 시절에 Todo App 기본 예제가 Firebase로 되어 있었습니다. 꽤 괜찮구나 하고 생각했는데 어느새 구글이 인수, 재작년 부터는 Google I/O의 주력 상품이 되어 있더군요.

일단 사이트를 방문해 볼까요?

Firebase | App success made simple
_Firebase gives you the tools and infrastructure you need to build better apps and grow successful businesses._firebase.google.com

IBM의 cloudant, mongolab 같은 일종의 DBAAS(Database Aa A Service)라고 보시면 되는데 제공되는 혜택이나 서비스들이 워낙 막강해서 개발하면서 고민해야 하는 많은 부분들을 줄여줄 수 있습니다.

가장 큰 장점이라고 하는 부분들(자랑하고 있는 부분)은 분석과 개발 툴입니다. 확실히 대시보드는 깔끔하고 개발할때 언제나 필요했던 요소들을 sdk 로 제공해 주는데 대표적인 예로는 클라우드 메세징, 인증, 실시간 데이타베이스, 저장소, 호스팅, 원격구성, Test Lab, 오류 보고 입니다.

이 중에 우리가 사용할 녀석은 아무래도 인증과 실시간 데이타베이스 이 두가지 입니다. 서버리스 아키텍처를 지향하니까요~

구글 I/O의 처음부터 앱 만들기를 보시면 상당히 도움이 되실거 같구요. 하지만 이와 별개로 저도 처음부터 시작해 보도록 하겠습니다.

가입

가입은 Google ID가 있으면 가능합니다. 오른쪽 상단의 로그인 버튼을 누르고 로그인을 한뒤 로그인 버튼 옆에 있는 콘솔로 이동 버튼을 누릅니다.

그럼 Console의 대문에 들어가게 되는데

거기서 새 프로젝트 만들기를 눌러서 새 프로젝트를 만들 수 있습니다.

프로젝트 만들기

다음과 같은 팝업창이 뜬 것을 확인할 수 있을 겁니다.

프로젝트 이름을 StandUp, 국가/지역을 대한민국으로 설정하고 프로젝트 만들기 버튼을 클릭합니다.
그러고 나면 몇초 후에 다음과 같은 아름다운 대시보드 화면을 볼 수 있습니다.

네! 이제 끝났습니다~ 개발에 필요한 부분이 나올 때에 Firebase 의 기능은 조금 더 설명하기로 하고 가장 중요한 한가지만 하고 작업을 진행하도록 하겠습니다.

웹 앱에 Firebase 추가 버튼을 클릭하면 다음과 같은 창이 뜨는데 거기에 나온 config 값을 사용합니다.

3. 환경 설정

config.js 파일을 다음과 같이 만들어 줍니다.

var config = {  
apiKey: process.env.REACT\_APP\_FIREBASE\_KEY,  
authDomain: process.env.REACT\_APP\_AUTH\_DOMAIN,  
databaseURL: process.env.REACT\_APP\_DB\_URL,  
storageBucket: process.env.REACT\_APP\_STRG\_BKT,  
messagingSenderId: process.env.REACT\_APP\_MSG\_SENDER\_ID  
}  
export default config;

여기서 두가지를 설명하고 가야할 것 같습니다. 첫번째는 process.env.으로 시작하는 값들입니다. create-react-app 설명을 찾아보시면 node에서 process.env에 들어가는 값을 관리하는 것을 이용해서process.env. REACT_APP_ 로 시작하게 되는 값들은 환경변수로 사용할 수 있습니다.

그렇다면 당연히 여러분이 같이 만드신 프로젝트 루트 디렉토리에 .env 파일을 만들고

REACT\_APP\_FIREBASE\_KEY = "AIzaSyCt6rkIrXNKr9xV0l-7Ei\_2m3vY6RlxdJ0"  
REACT\_APP\_AUTH\_DOMAIN = "standup-4125e.firebaseapp.com"  
REACT\_APP\_DB\_URL= "[https://standup-4125e.firebaseio.com][anchor4]"  
REACT\_APP\_STRG\_BKT= "standup-4125e.appspot.com"  
REACT\_APP\_MSG\_SENDER\_ID= "605830974241"

와 같이 값을 만드시면 사용할 수 있습니다.

물론 API key 값들은 어느 정도 일정 시간이 지나면 제가 바꿀 것이기는 합니다. 값은 참조하시라고 남겨두었습니다.

이렇게 설정하고 나면 잘 가지고 오는지를 확인해 봐야겠지요?
App.js 같은 곳에 import 와 console 로그를 출력하는 소스를 집어 넣고 확인해 보도록 하겠습니다.

import config from './config'  
console.log(config.apiKey);

훌륭하게 출력이 되는 군요. 주의할 점 두가지는 create-react-app 의 react-scripts 버전이 0.5.0 이상이어야 하며 .env 에 등록한 변수는 꼭 node 서버를 재기동 시켜주셔야 인식 한다는 것입니다.

4. HTML 편집기 작성

이제 기본적으로 HTML 편집기 에 들어갈 몇가지 유저스토리를 추가해서 작성해 보도록 하겠습니다.

김개발이 사이트를 방문해서 자신이 어제 유심하게 읽은 글을 올릴 수 있다. 이렇게 하면 다른 사람들이 볼 수 있다.
* 에디터 창은 하나만 있고 거기서 글을 작성하고 업로드 하면 글이 외부 클라우드 공간에 저장이 된다.
* 아무런 내용도 김개발이 입력하지 않으면 업로드하지 않는다.( 버튼이 눌러지지 않는다. )

유저 스토리에서 에디터 창은 하나만 있지만 개발을 하면서 데이타를 저렇게 저장할 수는 없을테니 어떻게 구조화 할지 고민을 일단 한번 해 보도록 합시다. 소셜미디어데 들어가는 데이타를 다시한번 생각해 보면 아래와 같은 형태로 구조화 되어 있지 않을까 확인할 수 있습니다.

글 내용  
작성자  
URL  
--- 링크 타이틀  
--- 링크 요약  
--- 링크 이미지

글 내용은 사용자가 작성한 내용을 그대로 넣으면 되고, 작성자는 일단 Anonymous(겐지)로 지정하면 되는데 URL의 아래 내용은 어떻게 할까요? 관련 내용은 다음번에 조금더 기술하도록 하고 오늘은 일단 URL만 글 내용에서 분리하는 작업을 해 보겠습니다.

Editor.js 파일의 innerEdit 클래스에 이벤트를 한번 추가해 보도록 하겠습니다.

<div className="innerEdit"  
contentEditable="true"  
placeholder="글쓰기..."  
onPaste={this.onPaste}  
onKeyUp={this.editorChange}\></div\>

onKeyUp 이벤트는 URL을 입력하고 엔터나 스페이스를 쳤을 때 패턴 매칭을 통해 URL을 뽑아내고 onPaste 이벤트는 복사 붙여넣기 할때의 이벤트를 잡아내는 역할을 합니다.

onPaste(event){  
event.clipboardData.items\[0\].getAsString(text=\>{  
if(this.detectURL(text)){  
this.setState({embedlyUrl:text,content:this.state.content}});  
}  
})  
}  
editorChange(event){  
let checkText = this.detectURL(event.currentTarget.textContent);  
if(!this.state.embedlyUrl&&  
(event.keyCode===32||event.keyCode===13)&&  
checkText){  
this.setState({embedlyUrl:checkText,   
content:event.currentTarget.textContent});  
}else{  
this.setState({content:event.currentTarget.textContent});  
}  
}

onPaste와 editorChange는 모두 event 에서 값을 받지만 text 값을 얻어내는 방법이 조금씩 다릅니다.
onPaste는 클립보드의 아이템에서 값을 getAsString이라는 함수를 통해 얻어내는데 콜백함수의 매개 변수로 화살함수를 사용했습니다.
(여러번 ES2015 문법에 대해서 설명을 드렸지만 화살함수는 this를 의도적으로 지정할 수 있고 실제 함수를 풀어 쓰면 다음과 같은 형태의 함수로 바꿀 수 있습니다.)

onPaste(event){  
var that = this;  
event.clipboardData.items\[0\].getAsString(function(text){   
if(that.detectURL(text)){  
that.setState({embedlyUrl:text});  
}  
})  
}

그리고 onPaste와 editorChange 모두 편집기의 텍스트 값에 들어가는 URL을 받아서 state 값에 저장을 하는 역할을 하고 있습니다. 하지만 content에 들어가는 값을 얻어내는 과정은 두가지가 조금 다릅니다.
먼저 editorChange 함수는 엔터와 스페이스바를 입력했을때

event.keyCode===32||event.keyCode===13

편집기의 값을 확인하는 한편 onPaste는 event의 클립보드에서 텍스트를 잡아내는 거라 데이타를 완성하는 부분이 다릅니다.

지금까지의 Editor.js 소스는 다음과 같습니다.

class Editor extends Component {  
/\*사용되는 메쏘드들을 모두 this 로 사용할 수 있도록 바인딩 해 준다.\*/  
constructor(props){  
super(props);  
this.onPaste = this.onPaste.bind(this);  
this.editorChange = this.editorChange.bind(this);  
this.getCard = this.getCard.bind(this);  
this.hasValue = this.hasValue.bind(this);  
//embedlyUrl과 content로 분리해 준다. 사용자 추가 전  
this.state ={  
embedlyUrl : undefined,  
content : undefined  
}  
}  
// 복사 붙여넣기에 사용되는 이벤트를 다뤄준다.  
onPaste(event){  
//클립보드 아이템의 첫번째 배열에서 text 를 받는다.  
event.clipboardData.items\[0\].getAsString(text=\>{  
//원래는 text가 String 형태인지 확인해 봐야하지만 getAsString이라 에러보다는 비정상 작동  
//이 이루어질 수 있다.  
// detectURL 이라는 dummy function 이 필요하다.  
if(detectURL(text)){  
//content 의 state는 이미 붙여진 상태 이후기 때문에 state를 그대로 가져와도 됨  
this.setState({embedlyUrl:text});  
}  
})  
}  
editorChange(event){  
// detectURL 이라는 dummy function 이 필요하다.  
let checkText = detectURL(event.currentTarget.textContent);  
if(!this.state.embedlyUrl&&  
(event.keyCode===32||event.keyCode===13)&&  
checkText){  
this.setState({embedlyUrl:checkText,content:event.currentTarget.textContent});  
}else{  
this.setState({content:event.currentTarget.textContent});  
}  
}  
//일단은 Dummy 함수가 필요하다.  
detectURL(text){  
return undefined;  
}  
render() {  
return (  
<div className="wrapEditor"\>  
<Profile isAnonymous={this.props.isAnonymous}/\>  
<div className="textEditor"\>  
<div className="innerEdit"  
contentEditable="true"  
placeholder="글쓰기..."  
onPaste={this.onPaste}  
onKeyUp={this.editorChange}\></div\>  
</div\>  
<div className="actionBar"\>  
<button className="upload"  
onClick={this.props.submit}\><span\>스탠드업!</span\></button\>  
</div\>  
</div\>  
);  
}  
}

하지만 공통적으로 어떤 URL을 찾아내는 작업의 경우는 detectURL이라는 공통함수를 만들어야 되겠군요. 지금은 dummy 함수 입니다.
아직까지는 이런 공통적인 함수나 로직 없이 진행해서 테스트코드에 대한 언급을 하지 않았지만 이번에는 언급을 하고 시작하고자 합니다.

5. 테스트 코드

create-react-app 은 기본적으로 jest를 내장하고 있습니다. 그래서 jest를 이용하는 방법과 똑같이 작성하시면 됩니다.

  • __tests__ 폴더아래에 있는 .js 파일
  • .test.js로 끝나는 파일
  • .spec.js로 끝나는 파일

들은 기본적으로 테스트 파일로 인식하는데,

$npm test

를 실행시키면 진행해 볼 수 있습니다.

먼저 App.js 가 잘 로딩이 되는지에 대해 src 폴더 아래 App.test.js 라는 파일을 만들어 보겠습니다.

import React from 'react';  
import ReactDOM from 'react-dom';  
import App from './App';

it('renders without crashing', () =\> {  
const div = document.createElement('div');  
ReactDOM.render(<App /\>, div);  
});

실행 결과는 다음과 같이 표현 됩니다.

\> standup@0.1.0 test /Users/SongGangho/dev/standup  
\> react-scripts test --- env=jsdom  
No tests found related to files changed since last commit.  
Press \`a\` to run all tests, or run Jest with \` --- watchAll\`.

Watch Usage  
› Press a to run all tests.  
› Press o to only run tests related to changed files.  
› Press p to filter by a filename regex pattern.  
› Press q to quit watch mode.  
› Press Enter to trigger a test run.

npm test를 실행시키면 기본적으로 실행을 하고 대기 모드로 들어가고 언제나 Watch Usage를 뱉습니다.
a 는 모든 테스트를 실행시켜주고, p는 정규식에 매치가 되는 filter로 테스트 케이스를 돌릴 수 있고q는 감지(watch)모드를 종료 시킵니다. 감지모드일 경우는 파일이 변하면 알아서 테스트 케이스를 변경합니다.

저의 경우는 파일을 만들고 마지막 커밋을 하고파일에 변화가 없는 경우라 이렇게 표시가 되는군요.

기본적으로 커밋이 안된 내용에 대해서만 감지하고 있다가 반영을 하는 훌륭한 메커니즘을 가지고 있습니다.

하지만 파일을 약간 수정하고 나면 그걸 바로 실행시켜 줍니다. 아래와 같이 실행이 됩니다.

PASS src/App.test.js  
✓ renders without crashing (22ms)

Test Summary  
› Ran all tests related to changed files.  
› 1 test passed (1 total in 1 test suite, run time 1.327s)

iTerm 같은데서는 PASS가 녹색으로 표시 되어 있습니다.

6. detectURL

어딜보자

이제 컴포넌트가 잘 로딩되는지에 대한 테스트는 마쳤으니 한번 detectURL에 대한 테스트 코드를 작성해 보도록 하겠습니다.

일단 src 폴더 아래에 __tests__ 폴더를 만들고, 우리가 테스트할 Editor.js파일과 똑같은 이름의 Editor.js 파일을 아래와 같이 만듭니다.

이 때 첫번째 원칙은 에러가 나는 코드를 만드는 것이죠. 이미

return undefined;

를 통해 코드를 만들어 놓았습니다. 이제는 테스트 코드를 아래와 같이 만들어 보겠습니다.

//Editor.js  
import Editor from '../Editor';  
let ed = new Editor;  
it('detect URL ', () =\> {  
expect(ed.detectURL("my [www.devpools.kr][anchor5] ")).toEqual("[www.devpools.kr][anchor5]");  
});

어떤 text값이 들어 오더라도 URL을 반환을 해 주는데 여러개가 있을지라도 하나의 URL을 반환을 받는 테스트 코드를 짜 보았습니다.

이렇게 실행을 시키면

PASS src/App.test.js  
FAIL src/\_\_tests\_\_/Editor.js  
● detect URL

expect(received).toEqual(expected)

Expected value to equal:  
"[www.devpools.kr][anchor5]"  
Received:  
"undefined"

Difference:

Comparing two different types of values:  
Expected: array  
Received: undefined

at Object.<anonymous\> (src/\_\_tests\_\_/Editor.js:4:44)

Test Summary  
› Ran all tests.  
› 1 test failed, 1 test passed (2 total in 2 test suites, run time 0.125s)

와 같이 나오고 PASS 는 녹색 FAIL은 빨강색으로 분리가 됩니다. 해석을 하면 [www.clien.net] 을 기대했는데, undefined가 왔다. 라고 하는 군요.

그럼 일단 패스만 시켜보기 위해 다음과 같이 detectURL을 변경해 보겠습니다.

detectURL(text){  
return "[www.devpools.kr][anchor5]";  
}

이렇게 할 경우는 당연히

PASS src/App.test.js  
PASS src/\_\_tests\_\_/Editor.js  
Test Summary  
› Ran all tests related to changed files.  
› 2 tests passed (2 total in 2 test suites, run time 0.096s)

모두 통과하게 됩니다.

자, 하지만 이렇게 해서는 모든 경우를 통과할 수는 없을 겁니다. 이런 텍스트를 잘 다루기 위해 많이 사용되는 방법이 정규식인데요. 아래의 test 케이스를 모두 통과하는 정규식을 한번 짜 볼까합니다.

it('detect URL 1', () =\> {  
expect(ed.detectURL("my [www.devpools.kr][anchor5] "))   
.toEqual("[www.devpools.kr][anchor5]");  
});  
it('detect URL 2', () =\> {  
expect(  
ed.detectURL("[http://www.devpools.kr][anchor5]는 [www.github.com][anchor7]이 궁금하다"))  
.toEqual("[http://www.devpools.kr][anchor5]");  
});  
it('detect URL 3', () =\> {  
expect(  
ed.detectURL("[www.github.com][anchor7]은 [http://www.devpools.kr][anchor5]이 전문가"))  
.toEqual("[http://www.devpools.kr][anchor5]");  
});

1번은 그냥 하나를 확인하는 내용이고 2번과 3번은 텍스트 내의 URL 들을 모두 확인해 봐야 합니다. 하지만 URL의 경우는 http 로 시작하는 경우가 있고, 그냥 .net .com 같은 접미어로 끝나는 여러가지 경우가 있습니다. 이 모든 경우를 다 커버할 수 있는 내용의 정규식보다는 간단하게 http(혹은 https) 로 시작하는 패턴과 www 로 시작하는 패턴을 파악해서 배열로 반환하도록 해보겠습니다. 둘다 존재한다면 http 까지 전체 있는 URL을 먼저 반환하면 더 좋겠죠.

detectURL(text){  
var urls = (text.match(/(https?:\\/\\/\[^\\s\]+)/g)||  
text.match(/([www.\[][anchor8]^\\s\]+)/g));  
if(urls.length\>0) return urls\[0\];  
else return undefined;  
}

혹시 관련해서 좋은 정규식 패턴을 아시는 분이 계시면 답변 주시면 소스를 변경하겠습니다. 혹은 PU는 더 두팔벌려 환영하겠습니다.

위와 같은 소스를 반영을 했더니 npm test는 모두 녹색 PASS를 반환합니다.

PASS src/App.test.js  
PASS src/\_\_tests\_\_/Editor.js  
Test Summary  
› Ran all tests.  
› 4 tests passed (4 total in 2 test suites, run time 0.106s)

7. 폼 제출하기 전에 고려할 사항

흠.. 다음 수는요.

이제 클라우드 서비스에 반영할 준비는 거의 끝나가는데요. 기본적으로 편집기에 있는 값을 던지기 전의 준비사항만 더 살펴 볼까요. 일단 편집기에 값이 있는지 없는지에 대한 확인을 해 봐야 할 거 같습니다.

편집기의 값이 존재하고 string 값이 들어왔을 때에만 제출하는 코드를 짜야할 것이라고 판단할 때 테스트 코드는 다음과 같이 정의를 하면

it('hasValue 1',()=\>{  
expect(ed.hasValue(1)).toEqual(false);  
});  
it('hasValue 2',()=\>{  
expect(ed.hasValue(new Date)).toEqual(false);  
});  
it('hasValue 3',()=\>{  
expect(ed.hasValue("1")).toEqual(true);  
});  
it('hasValue 4',()=\>{  
expect(ed.hasValue()).toEqual(false);  
});  
it('hasValue 5',()=\>{  
expect(ed.hasValue({})).toEqual(false);  
});  
it('hasValue 6',()=\>{  
expect(ed.hasValue(\[\]\])).toEqual(false);  
});

실행 코드는 다음과 같습니다.

hasValue(value){  
if((value && (typeof value) === "string"))  
return (!value)?false:(value.trim()===""?false:true);  
else return false;  
}

결과는 당연히

PASS src/App.test.js  
PASS src/\_\_tests\_\_/Editor.js  
Test Summary  
› Ran all tests related to changed files.  
› 8 tests passed (8 total in 2 test suites, run time 0.092s)

와 같이 나옵니다

이제 다음번에는 firebase 를 이용한 입력과 조회를 해 봐야겠습니다.

지금까지의 Editor.js 소스는 다음과 같습니다.

설명하지 않은 함수 중에 getCard라는 함수가 있습니다. 이 함수는 나중에 편집기에 카드가 들어가는 모습을 보기 위해 URL이 들어갈 수 있도록 간단하게 설계해 두었습니다. Embed.ly 관련 내용이 들어갈때 가장 많은 설명이 들어갈 컴포넌트가 될 것입니다.

day3 에 관련된 소스는 아래에서 확인할 수 있습니다.

ehrudxo/standup
_standup project for newbi_github.com

혹은 git 명령어

$git pull origin day3 && git checkout day3

하시면 로칼에서 확인할 수 있습니다.

By Keen Dev on November 7, 2016.

Exported from Medium on May 31, 2017.

서버리스 아키텍처 소셜미디어 개발기 2편

지난 글에 이어 계속 됩니다.

서버리스 아키텍처 소셜미디어 개발기 1편
_지난편 보기_techstory.shma.so

지난 편에서 Hello World 와도 같은 프로그램을 빌드까지 했다고 하면 Day2에서는 실제 페이지를 만들기 위한 기본 작업들로 시작해서 User Story2번의 기초 작업을 해 보도록 하겠습니다.

슬슬 시작해 볼까?

일단 create-react-app의 처음 만들어진 폴더 구조는 다음과 같습니다.

이후 진행에 폴더를 찾는데 참조하시기 바랍니다.

User Story 들어가기 전에 몸좀 풀죠.

1. 로고, 파비콘(favicon), 헤더

처음 실행한 예제에서 이쁘기는 하지만 무지하게 큰 움직이는 로고를 만나보셨을 겁니다. 그리고 헤더가 엄청나게 큰 영역을 차지하고 있다는 것, 파비콘도 react 이미지라는 사실을 알 수 있죠.

이런 것들을 변경하는 팁을 약간 언급하고 넘어가겠습니다.

로고

개발자에게 디자인은 또 다른 어려움일거라 생각합니다.

로고 밑 그래픽 생성기 - Cool Text
_Cool Text(쿨 텍스트)는 화려한 로고를 웹페이지나 다른 용도로 위해 만들어 주는 무료 그래픽 생성기입니다. 간편하게 원하시는 이미지를 클릭만 해주세요. 그 후 폼을 작성해 주시면 바로 당신만의 이미지를 만…_ko.cooltext.com

위와 같이 로고를 만들어 주는 사이트를 이용해 볼 수 있습니다.
Stand Up으로 주고 헤더 색깔을 녹색( #02B875 )으로 지정할 예정이기 때문에 비슷하게 맞춰서 다운 받아 둡니다.

훌륭합니다.

favicon

favicon은 브라우저에서 타이틀 텍스트 옆에 있는 작은 아이콘입니다. 즐겨찾기에도 표시가 되는 아이콘인데, 이 것도 만들어주는 사이트가 있습니다.

Favicon & App Icon Generator
_Convert PNG to ICO, JPG to ICO, GIF to ICO. Create favicon.ico and iOS / Android App Icons. Edit a favicon to fit your…_www.favicon-generator.org

같은 사이트는 그런 역할을 해 주는 군요.
(재밌는 것은 이 사이트은 파비콘이 없다는 사실입니다.)

저는 제 캐리커쳐를 올려서 favicon을 만들었습니다.

헤더

헤더의 색과 사이즈를 변경할 수 있습니다. src 폴더 아래의 App.css 파일의 CSS 파일을 다음과 같이 고쳐서

.App-logo {  
height: 45px;/\* 이미지 사이즈 줄이고\*/  
}  
.App-header {  
background-color: \#02B875;/\* 타이틀 바 색감을 좀 바꾸고\*/  
height: 50px;/\* 전체 사이즈를 줄였습니다.\*/  
padding: 5px;  
color: white;  
}

로고 이미지와 favicon 을 바꾸도록 App.js를 바꿔보도록 하겠습니다.

import React, { Component } from 'react';  
import logo from './img/stand\_up\_logo.png';  
import './App.css';

class App extends Component {  
render() {  
return (  
<div className="App"\>  
<div className="App-header"\>  
<img src={logo} className="App-logo" alt="logo" /\>  
</div\>  
<p className="App-intro"\>  
이제 여기서 부터 프로젝트는 시작입니다. 로고는 일단은 그냥 둡시다.  
</p\>  
</div\>  
);  
}  
}  
export default App;

favicon은 public folder밑에 파일을 대체만 해 주면 됩니다.

지금부터는 모바일 화면 기준으로 보기 위해 개발자 도구의 모바일 보기 기능을 활용하겠습니다.

그럼 다음과 같이 보여야 정상입니다.

호오. 근데 번지는 효과는 어디로 갔나.. 나중에 손좀 봐야겠네요.

파비콘도 정상이네요

이제 준비가 되었습니다. User Story를 진행합시다

2. User Story 2

정의된 User Story는 다음과 같습니다.

김개발이 사이트를 방문해서 자신이 유심하게 읽은 글을 올릴 수 있다. 이렇게 하면 다른 사람들이 볼 수 있다.
* 에디터 창은 하나만 있고 거기서 글을 작성하고 업로드 하면 글이 외부 클라우드 공간에 저장이 된다.

유심하게 읽은 글을 올릴 수 있다. 부분을 참조해서 요건을 정의해 봅시다

요건 확인

“무엇을 올린 것인가”를 고려해 볼 때 소셜미디어들의 화면을 보도록 하겠습니다.

위의 이미지에서 기본적인 요건을 정의해 보면 다음의 내용들을 기본으로 가지고 있습니다.

작성자 (writer)  
작성일 (createdAt)  
내용 (contents)  
링크  
--- 링크 이미지  
--- 링크 제목  
--- 링크 설명  
--- 링크 주소

물론 다 라고 할 수는 없지만 일단 눈에띄는 부분과 변하지 않는 부분 위주로 살펴보죠. 이후에 더 자세하게 바꾸도록 하겠습니다.

3. Editor 개발

그렇다면 Editor에 위의 내용들을 입력해서 업로드하기 직전까지의 작업을 지금부터 해 볼 것입니다. 어디에 어떻게 올릴 것인지는 나중에 고민하기로 하고 가장 쉬운 에디터 부터 만들어 보겠습니다.

이런 이쁜 editor를 만들어 보자

요건 정의를 통해 스케치해 본 위의 이미지와 같은 형태의 에디터를 만들 때 필요한 부분은 사용자 프로파일타이틀(‘무엇을 공유할까요’) 부분과 나머지 입력부 , 버튼 등이 있을 거 같습니다.

사용자 프로파일 만들기(타이틀 포함)

ㄱㅈㄱ ㅎㄲㅎㄷ(가족과 함께한다?)

지금은 사용자의 세션을 생각하지 말고 Anonymous모드로 프로파일 부분을 먼저 만들어 보도록 하겠습니다.

Profile.js, Profile.css 파일을 아래와 같이 만들어 보겠습니다.

//JavaScript  
import React from 'react';  
import './Profile.css';  
import Anonymous from './img/anonymous.png';

function Profile(isAnonymous){  
if(isAnonymous){ //익명일 경우  
return(  
<div className="anonymous"\>  
<div className="today\_title"\>  
무엇을 공유할까요?  
</div\>  
<div className="anonymous\_name"\>  
겐지  
</div\>  
<div className="anonymous\_img\_wrap"\>  
<img src={Anonymous} alt="profiles" className="anonymous\_img"/\>  
</div\>  
</div\>  
)  
}else{//익명이 아닐 경우는 일단은 빈 div  
return <div/\>;  
}  
}  
export default Profile;

CSS는 다음과 같습니다.

/\*Profile.css\*/  
.anonymous{  
text-align: left;  
background-color: white;  
border-bottom: 1px solid \#dddfe2;  
width :100%;  
height: 50px;  
}  
.anonymous\_img\_wrap{  
text-align: right;  
border-radius: 50%;  
display: inline;  
float: right;  
}  
.anonymous\_img{  
border-radius: 50%;  
height: 35px;  
padding: 5px;  
}  
.anonymous\_name{  
padding-right: 10px;  
float: right;  
margin-top: 17px;  
}  
.today\_title{  
float: left;  
margin-top: 17px;  
padding-left: 10px;  
}

익명일 경우에 겐지는 일단 Username으로 정하겠습니다.
일반 function 함수로 컴포넌트를 만들었고 props 로 isAnonymous 값을 전달 받는다는 사실을 알 수 있습니다.

입력부와 버튼 만들기

이제는 입력부를 살펴 볼까요?

전통적으로 웹 에디터의 입력부 작성하는 방법은

<form target="\[some url\]"\>  
<textarea name="\[text\_area\_name\]"/\>  
</form\>

이지만 몇가지 이유에서 div 태그를 이용하도록 하겠습니다. (form 에 넣을 경우는 브라우저 이벤트가 우리가 넣는 이벤트에 우선하기 때문에 엔터를 칠 경우 submit이 되고, Single Page Applicaion의 경우 URL 이 옮겨지는 등의 제어를 위해 들어가야 하는 스크립트들이 많아지는 것등에 대한 코드가 많아지는 일들이 있습니다.)

div 의 경우는 content-editable 속성을 주면 편집이 가능합니다.
placeholder 까지 더한코드를 살펴보겠습니다. (placeholder란 HTML 입력 폼에서 사용자가 입력하기 전까지 화면에 표시되는 문자열을 이야기 합니다)

<div class="innerEdit"  
contentEditable="true"  
placeholder="글쓰기..."\></div\>

하지만 div는 그냥 placeholder가 작동하지 않습니다. 그래서 innerEdit 이라는 CSS클래스를 다음과 같이 작업을 해 줘야 합니다

\[contenteditable=true\]:empty:before{  
content: attr(placeholder);  
color: \#aaa;  
display: block; /\* For Firefox \*/  
}

합쳐 만들기

이제 아까 작성한 Profile.js 를 결합하고 눌렀을 때 동작하는 코드를 만들어 보도록 하겠습니다.(콘솔에 이벤트만 먼저 찍어보도록 하겠습니다.)

Editor.js 및 Editor.css 파일을 아래와 같이 작성해 보겠습니다.

이제 Editor.js 파일을 App.js 파일에 컴포넌트로 완성을 해야 입력 폼이 완성이 된다고 볼 수 있습니다.
(소스에서 이미 확인하셨겠지만 props로 들어오는 값들이 있습니다.)

App.js 파일은 다음과 같이 수정하겠습니다.

변경된 소스를 잘 보셨으면 아시겠지만 Editor에 부모가 가진 메쏘드들을 전달하는 패턴을 쓸때 열거형

...

을 사용했습니다. 자주 쓸 경우는 불필요한 리소스들까지도 연결 될 수 있으니 잘 확인해서 사용하시기 바랍니다

이렇게까지 작업을 하면 다음과 같이 페이지가 뜨게 되고 콘솔 값을 확인해 볼 수 있습니다.

이뻐요~

다음은 콘솔 값이 아니라 간단한 리스트를 페이지에 찍어보고 클라우드 서버(firebase)에 저장하는 내용을 다뤄보도록 하겠습니다.

이번엔 코드가 많아서 따라오기 힘드셨겠지만,

ehrudxo/standup
_standup project for newbi_github.com

day2 branch를 잘 참조 하시면 좋은 성과가 있을 거라고 생각됩니다.

겐지가 함께 하니깐요. 출처 : http://m.ruliweb.com/game/pc/84032/board/read/383 (루리웹)

By Keen Dev on October 22, 2016.

Exported from Medium on May 31, 2017.