├── .DS_Store
├── ch1
├── .DS_Store
├── a.txt
├── anonymous.js
├── anonymous2.js
├── arrow-test.js
├── b.txt
├── babeltest
│ ├── .babelrc
│ ├── bmi.js
│ ├── index.html
│ ├── package.json
│ └── src
│ │ ├── a.js
│ │ └── b.js
├── c.txt
├── calculator_main.js
├── calculator_module.js
├── dice-server.js
├── es2015io
│ ├── .DS_Store
│ ├── .babelrc
│ ├── package.json
│ └── src
│ │ ├── calctest.js
│ │ ├── main.js
│ │ ├── main2.js
│ │ └── main3.js
├── es2015io2
│ ├── .babelrc
│ ├── package.json
│ └── src
│ │ ├── gobsem.js
│ │ └── main.js
├── fib.js
├── generator-test.js
├── hello-server.js
├── project_a
│ ├── index.js
│ └── package.json
├── readfile-async.js
├── readfile-cb.js
├── readfile-cb2.js
├── readfile-cb3.js
├── readfile-generator.js
├── readfile-promise.js
├── readfile-sync.js
├── readfile.js
├── request-downloadfile.js
├── test.png
└── test.txt
├── ch2
├── .DS_Store
├── cp-arrow.html
├── cp-class.html
├── cp-greeting.html
├── cp-imagetext.html
├── cp-imagetext2.html
├── cp-stopwatch.html
├── greeting.html
├── hello-react.html
├── hello
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── logo.svg
├── img
│ ├── pic1.jpeg
│ ├── pic2.jpeg
│ └── pic3.jpeg
├── jsx-conv
│ ├── a.js
│ ├── b.js
│ └── package.json
├── jsx-embed.html
├── jsx-embed2-error.html
├── jsx-embed2.html
├── jsx-error.html
├── jsx-escape.html
├── jsx-style.html
├── jsx-test.html
├── list-items.html
├── list-items2.html
├── package.json
├── react-binclock.html
├── react-clock.html
├── react-clock2.html
├── react-stopwatch.html
├── st-checkbox.html
├── st-click.html
├── st-click2.html
├── st-clock.html
├── state-tf.png
├── temp.html
├── temp.js
├── test-webpack
│ ├── calc.js
│ ├── main.js
│ └── webpack.config.js
├── test-webpack2
│ ├── main.html
│ ├── package.json
│ ├── src
│ │ ├── Hello.js
│ │ └── main.js
│ └── webpack.config.js
└── tree.html
├── ch3
├── .DS_Store
├── cycle
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── logo.svg
├── form_multi
│ ├── .DS_Store
│ ├── build.config.js
│ ├── build.sh
│ ├── index.html
│ ├── out
│ │ └── bundle.js
│ └── src
│ │ ├── MultiForm.css
│ │ ├── MultiForm.js
│ │ └── index.js
├── form_number
│ ├── .DS_Store
│ ├── build.config.js
│ ├── build.sh
│ ├── number_form.html
│ ├── out
│ │ └── bundle.js
│ └── src
│ │ ├── NumberForm.js
│ │ └── index.js
├── form_simple
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── SimpleForm.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── logo.svg
├── inch_to_cm
│ ├── .DS_Store
│ ├── build.sh
│ ├── index.html
│ ├── out
│ │ └── bundle.js
│ ├── package.json
│ ├── src
│ │ ├── .DS_Store
│ │ ├── InchToCm.js
│ │ ├── ValueInput.js
│ │ └── index.js
│ └── webpack.config.js
├── input_compo
│ ├── .DS_Store
│ ├── index.html
│ ├── out
│ │ └── bundle.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── .DS_Store
│ │ ├── FormInput.js
│ │ └── index.js
│ └── webpack.config.js
├── input_zip
│ ├── .DS_Store
│ ├── build.sh
│ ├── index.html
│ ├── out
│ │ └── bundle.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── .DS_Store
│ │ ├── ZipInput.js
│ │ └── index.js
│ └── webpack.config.js
├── parts
│ ├── .DS_Store
│ ├── build.config.js
│ ├── build.sh
│ ├── cbox.html
│ ├── out
│ │ ├── .DS_Store
│ │ ├── cbox.js
│ │ ├── radio.js
│ │ ├── select.js
│ │ ├── text.js
│ │ └── textarea.js
│ ├── radio.html
│ ├── select.html
│ ├── src
│ │ ├── .DS_Store
│ │ ├── cbox.js
│ │ ├── radio.js
│ │ ├── select.js
│ │ ├── text.js
│ │ └── textarea.js
│ ├── text.html
│ └── textarea.html
├── refs-focus.html
├── refs-instance-test.html
├── sagent
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── fruits.json
│ │ └── index.html
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ └── test-sagent.js
├── st-color.html
└── stopwatch
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ └── src
│ ├── Stopwatch.css
│ ├── Stopwatch.js
│ ├── index.js
│ └── logo.svg
├── ch4
├── .DS_Store
├── TestNative
│ ├── .babelrc
│ ├── .buckconfig
│ ├── .flowconfig
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .watchmanconfig
│ ├── app.json
│ ├── index.android.js
│ ├── index.ios.js
│ ├── package-lock.json
│ ├── package.json
│ └── yarn.lock
├── electron_clipfmt
│ ├── .DS_Store
│ ├── index.html
│ ├── main.js
│ ├── out
│ │ └── index.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── index.js
│ └── webpack.config.js
├── electron_hello
│ ├── .DS_Store
│ ├── index.html
│ ├── main.js
│ ├── out
│ │ └── index.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── index.js
│ └── webpack.config.js
├── mstdn_cli
│ ├── .DS_Store
│ ├── 1_create_app.js
│ ├── 2_auth.js
│ ├── 3_get_timeline.js
│ ├── 4_toot.js
│ ├── index.html
│ ├── main.js
│ ├── out
│ │ └── index.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── index.js
│ │ └── styles.js
│ └── webpack.config.js
└── native_mstdn
│ ├── .babelrc
│ ├── .buckconfig
│ ├── .flowconfig
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .watchmanconfig
│ ├── MastodonClient.js
│ ├── app.json
│ ├── index.android.js
│ ├── index.ios.js
│ ├── package-lock.json
│ ├── package.json
│ └── yarn.lock
├── ch5
├── .DS_Store
├── bbs
│ ├── .DS_Store
│ ├── bbs-server.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── bundle.js
│ │ └── index.html
│ ├── src
│ │ └── index.js
│ └── webpack.config.js
├── chat
│ ├── .DS_Store
│ ├── chat-server.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── bundle.js
│ │ └── index.html
│ ├── src
│ │ ├── index.js
│ │ └── styles.js
│ └── webpack.config.js
├── express_test
│ ├── .DS_Store
│ ├── dice-ex.js
│ ├── dice-q.js
│ ├── dice.js
│ ├── hello.js
│ ├── html
│ │ ├── .DS_Store
│ │ ├── fuga
│ │ │ └── index.html
│ │ ├── hoge.html
│ │ └── index.html
│ ├── package.json
│ ├── post-show.js
│ ├── post-test.js
│ ├── post-upload.js
│ ├── pub
│ │ └── .DS_Store
│ └── static.js
├── flux_test
│ ├── .DS_Store
│ ├── index.html
│ ├── out
│ │ └── bundle.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── actions.js
│ │ ├── appDispatcher.js
│ │ ├── index.js
│ │ └── stores.js
│ └── webpack.config.js
├── nedb_test
│ ├── .DS_Store
│ ├── a.js
│ ├── package.json
│ └── test.db
├── router_params
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── CustomerApp.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ └── registerServiceWorker.js
├── router_test
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── HelloApp.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ └── registerServiceWorker.js
└── router_test2
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ └── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── HelloApp2.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ └── registerServiceWorker.js
├── ch6
├── .DS_Store
├── handwriting
│ ├── .DS_Store
│ ├── 1-download.js
│ ├── 2-conv2csv.js
│ ├── 3-split.js
│ ├── 4-train.js
│ ├── 5-test.js
│ ├── 6-server.js
│ ├── database
│ │ └── .DS_Store
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── bundle.js
│ │ └── index.html
│ ├── src
│ │ ├── index.js
│ │ └── styles.js
│ └── webpack.config.js
├── sns
│ ├── .DS_Store
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── bundle.js
│ │ ├── default.css
│ │ ├── index.html
│ │ └── user.png
│ ├── server
│ │ ├── .DS_Store
│ │ ├── database.js
│ │ ├── timeline.db
│ │ └── user.db
│ ├── sns-server.js
│ ├── src
│ │ ├── index.js
│ │ ├── sns_login.js
│ │ ├── sns_timeline.js
│ │ ├── sns_users.js
│ │ └── styles.js
│ └── webpack.config.js
└── wiki
│ ├── .DS_Store
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── bundle.js
│ ├── default.css
│ └── index.html
│ ├── src
│ ├── index.js
│ ├── styles.js
│ ├── wiki_edit.js
│ ├── wiki_parser.js
│ ├── wiki_parser.pegjs
│ └── wiki_show.js
│ ├── test-parser.js
│ ├── webpack.config.js
│ └── wiki-server.js
├── package-lock.json
└── package.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/.DS_Store
--------------------------------------------------------------------------------
/ch1/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch1/.DS_Store
--------------------------------------------------------------------------------
/ch1/a.txt:
--------------------------------------------------------------------------------
1 | *** a.txt ***
--------------------------------------------------------------------------------
/ch1/anonymous.js:
--------------------------------------------------------------------------------
1 | // 익명 함수를 사용해 함수를 정의합니다.
2 | const f1 = function (s) { console.log(s) }
3 | const f2 = (s) => { console.log(s) }
4 | // 익명 함수는 일반적인 함수처럼 사용합니다.
5 | f1('foo')
6 | f2('bar')
--------------------------------------------------------------------------------
/ch1/anonymous2.js:
--------------------------------------------------------------------------------
1 | // 소문자를 대문자로 변환하는 예
2 | const s = 'Keep On Asking, and It Will Be Given You.'
3 | const r = s.replace(/[a-z]+/g, function (m) {
4 | return m.toUpperCase()
5 | })
6 | console.log(r)
7 |
8 | // 배열의 숫자를 정렬하는 예
9 | const ar = [100, 1, 20, 43, 30, 11, 4]
10 | ar.sort((a, b) => { return b - a })
11 | console.log(ar)
--------------------------------------------------------------------------------
/ch1/arrow-test.js:
--------------------------------------------------------------------------------
1 | // 화살표 함수의 예
2 | const x3 = (n) => n * 3
--------------------------------------------------------------------------------
/ch1/b.txt:
--------------------------------------------------------------------------------
1 | *** b.txt ***
--------------------------------------------------------------------------------
/ch1/babeltest/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["es2015"] }
--------------------------------------------------------------------------------
/ch1/babeltest/bmi.js:
--------------------------------------------------------------------------------
1 | // BMI 클래스를 정의합니다.
2 | class BMI {
3 | constructor (height, weight) {
4 | this.height = height
5 | this.weight = weight
6 | this.bmi = this.calc()
7 | }
8 | calc () {
9 | const heightM = this.height / 100
10 | return this.weight / (heightM ** 2)
11 | }
12 | print () {
13 | let res = '표준'
14 | if (this.bmi >= 25) res = '비만'
15 | else if (this.bmi >= 18.5) res = '표준'
16 | else res = '저체중'
17 | console.log('BMI =', this.bmi, res)
18 | }
19 | }
20 | // 테스트
21 | const bmi = new BMI(160, 60)
22 | bmi.print()
--------------------------------------------------------------------------------
/ch1/babeltest/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ch1/babeltest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "babeltest",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build": "babel bmi.js -o bmi.out.js",
7 | "watch": "babel bmi.js -w -o bmi.out.js",
8 | "start": "node bmi.out.js"
9 | },
10 | "devDependencies": {
11 | "babel-cli": "^6.23.0",
12 | "babel-preset-es2015": "^6.22.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ch1/babeltest/src/a.js:
--------------------------------------------------------------------------------
1 | setTimeout(() => {
2 | console.log('foo')
3 | }, 100)
4 |
--------------------------------------------------------------------------------
/ch1/babeltest/src/b.js:
--------------------------------------------------------------------------------
1 | // Foo 클래스를 정의합니다.
2 | class Foo {
3 | constructor () {
4 | this.value = 100
5 | }
6 | bar() {
7 | console.log('Foo.bar')
8 | console.log(this.value)
9 | }
10 | }
11 | // Foo를 사용합니다.
12 | const f = new Foo()
13 | f.bar()
--------------------------------------------------------------------------------
/ch1/c.txt:
--------------------------------------------------------------------------------
1 | *** c.txt ***
--------------------------------------------------------------------------------
/ch1/calculator_main.js:
--------------------------------------------------------------------------------
1 | // 모듈을 읽어 들입니다.
2 | const calculator = require('./calculator_module.js')
3 | // 모듈의 함수를 사용합니다.
4 | console.log('3+5=', calculator.add(3, 5))
5 | console.log('4*8=', calculator.mul(4, 8))
--------------------------------------------------------------------------------
/ch1/calculator_module.js:
--------------------------------------------------------------------------------
1 | // 덧셈과 곱셈하는 함수
2 | function add (a, b) {
3 | return a + b
4 | }
5 | function mul (a, b) {
6 | return a * b
7 | }
8 | // 외부에 공개합니다.
9 | module.exports = {
10 | 'add': add,
11 | 'mul': mul
12 | }
--------------------------------------------------------------------------------
/ch1/dice-server.js:
--------------------------------------------------------------------------------
1 | // http 모듈을 읽어 들입니다.
2 | const http = require('http')
3 | const ctype = { 'Content-Type': 'text/html;charset=utf-8' }
4 |
5 | // 웹 서버를 실행합니다. --- (※1)
6 | const svr = http.createServer(handler) // 서버를 생성합니다.
7 | svr.listen(8081) // 8081번 포트를 사용합니다.
8 |
9 | // 서버에 접근이 있을 때의 처리 --- (※2)
10 | function handler (req, res) {
11 | // URL을 구분합니다.
12 | const url = req.url
13 | // 최상위 페이지일 때
14 | if (url === '/' || url === '/index.html') {
15 | showIndexPage(req, res)
16 | return
17 | }
18 | // 주사위 페이지일 때
19 | if (url.substr(0, 6) === '/dice/') {
20 | showDicePage(req, res)
21 | return
22 | }
23 | // 그 밖의 경우
24 | res.writeHead(404, ctype)
25 | res.end('404 not found')
26 | }
27 |
28 | // 인덱스 페이지에 접근했을 경우 --- (※3)
29 | function showIndexPage (req, res) {
30 | // HTTP 헤더를 출력합니다.
31 | res.writeHead(200, ctype)
32 | // 응답 본문을 출력합니다.
33 | const html = '
4 |
Hello
5 |
6 |
7 | - xxx
8 | - xxx
9 | - xxx
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ch3/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/.DS_Store
--------------------------------------------------------------------------------
/ch3/cycle/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/ch3/cycle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cycle",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4"
8 | },
9 | "devDependencies": {
10 | "react-scripts": "0.9.5",
11 | "standard": "^10.0.2"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ch3/cycle/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/cycle/public/favicon.ico
--------------------------------------------------------------------------------
/ch3/cycle/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ch3/cycle/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/ch3/cycle/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | class App extends Component {
3 | // 마운트
4 | constructor (props) {
5 | super(props)
6 | console.log('constructor')
7 | }
8 | componentWillMount () {
9 | console.log('componentWillMount')
10 | }
11 | componentDidMount () {
12 | console.log('componentDidMount')
13 | }
14 | // 변경
15 | componentWillReceiveProps (nextProps) {
16 | console.log('componentWillReceiveProps')
17 | }
18 | shouldComponentUpdate (nextProps, nextState) {
19 | console.log('shouldComponentUpdate')
20 | return true
21 | }
22 | componentWillUpdate () {
23 | console.log('componentWillUpdate')
24 | }
25 | componentDidUpdate () {
26 | console.log('componentDidUpdate')
27 | }
28 | // 언마운트
29 | componentWillUnmount () {
30 | console.log('componentWillUnmount')
31 | }
32 | render () {
33 | console.log('render')
34 | const setStateHandler = (e) => {
35 | console.log('* call setState()')
36 | this.setState({r: Math.random()})
37 | }
38 | return (
39 |
40 |
42 |
43 | )
44 | }
45 | }
46 | export default App
--------------------------------------------------------------------------------
/ch3/cycle/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div')
7 | ReactDOM.render(
, div)
8 | })
9 |
--------------------------------------------------------------------------------
/ch3/cycle/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ch3/cycle/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.render(
7 |
,
8 | document.getElementById('root')
9 | )
10 |
--------------------------------------------------------------------------------
/ch3/form_multi/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/form_multi/.DS_Store
--------------------------------------------------------------------------------
/ch3/form_multi/build.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: path.join(basedir, 'src/index.js'),
6 | output: {
7 | path: path.join(basedir, 'out'),
8 | filename: 'bundle.js'
9 | },
10 | devtool: 'inline-source-map',
11 | module: {
12 | rules: [
13 | {
14 | test: /.js$/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['es2015', 'react']
18 | }
19 | },
20 | {
21 | test: /.css$/,
22 | use: [
23 | {
24 | loader: 'style-loader'
25 | },
26 | {
27 | loader:'css-loader',
28 | options: {
29 | modules: true
30 | }
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/ch3/form_multi/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | config=$(cd $(dirname $0) &&pwd)/build.config.js
3 | cd ../.. && webpack --config $config $*
4 |
5 |
--------------------------------------------------------------------------------
/ch3/form_multi/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/form_multi/src/MultiForm.css:
--------------------------------------------------------------------------------
1 | .multiForm {
2 | width: 400px;
3 | text-align: left;
4 | margin-left: auto;
5 | }
6 |
7 | .multiForm p {
8 | margin: 4px;
9 | padding: 4px;
10 | }
11 |
--------------------------------------------------------------------------------
/ch3/form_multi/src/MultiForm.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | // 여러 개의 입력 항목을 가진 컴포넌트
3 | export default class MultiForm extends Component {
4 | constructor (props) {
5 | super(props)
6 | // 입력 양식의 초깃값을 설정합니다. --- (※1)
7 | this.state = {
8 | name: '윤인성',
9 | age: 25,
10 | hobby: '독서'
11 | }
12 | }
13 | // 값이 변경됐을 때 --- (※2)
14 | doChange (e) {
15 | const userValue = e.target.value
16 | const key = e.target.name
17 | this.setState({[key]: userValue})
18 | }
19 | // 전송 버튼을 눌렀을 때
20 | doSubmit (e) {
21 | e.preventDefault()
22 | const j = JSON.stringify(this.state)
23 | window.alert(j)
24 | }
25 | // 화면 렌더링 --- (※3)
26 | render () {
27 | // 이벤트를 메서드에 바인딩합니다.
28 | const doSubmit = (e) => this.doSubmit(e)
29 | const doChange = (e) => this.doChange(e)
30 | return (
31 |
55 | )
56 | }
57 | }
--------------------------------------------------------------------------------
/ch3/form_multi/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import MultiForm from './MultiForm'
4 | const st = {
5 | textAlign: 'left',
6 | padding: '10px'
7 | }
8 | ReactDOM.render(
9 |
10 |
11 |
,
12 | document.getElementById('root')
13 | )
14 |
--------------------------------------------------------------------------------
/ch3/form_number/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/form_number/.DS_Store
--------------------------------------------------------------------------------
/ch3/form_number/build.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: path.join(basedir, 'src/index.js'),
6 | output: {
7 | path: path.join(basedir, 'out'),
8 | filename: 'bundle.js'
9 | },
10 | devtool: 'inline-source-map',
11 | module: {
12 | rules: [
13 | {
14 | test: /.js$/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['es2015', 'react']
18 | }
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch3/form_number/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | config=$(cd $(dirname $0) &&pwd)/build.config.js
3 | cd ../.. && webpack --config $config $*
4 |
5 |
--------------------------------------------------------------------------------
/ch3/form_number/number_form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/form_number/src/NumberForm.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | // 숫자 입력 컴포넌트
3 | export default class NumberForm extends Component {
4 | constructor (props) {
5 | super(props)
6 | this.state = { value: '' }
7 | }
8 | // 값이 변경됐을 때 --- (※1)
9 | doChange (e) {
10 | const curValue = e.target.value
11 | // 숫자 이외의 값을 제거합니다.
12 | const newValue = curValue.replace(/[^0-9]/g, '')
13 | this.setState({value: newValue})
14 | }
15 | // 전송 버튼을 눌렀을 때
16 | doSubmit (e) {
17 | window.alert('전송: ' + this.state.value)
18 | e.preventDefault()
19 | }
20 | // 화면 렌더링 --- (※4)
21 | render () {
22 | // 이벤트를 메서드에 바인딩합니다.
23 | const doSubmit = (e) => this.doSubmit(e)
24 | const doChange = (e) => this.doChange(e)
25 | return (
26 |
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/ch3/form_number/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import NumberForm from './NumberForm'
4 |
5 | const st = {textAlign: 'center'}
6 | ReactDOM.render(
7 |
8 |
9 |
,
10 | document.getElementById('root')
11 | )
12 |
--------------------------------------------------------------------------------
/ch3/form_simple/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/ch3/form_simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "form1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4"
8 | },
9 | "devDependencies": {
10 | "react-scripts": "0.9.5",
11 | "standard": "^10.0.2"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ch3/form_simple/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/form_simple/public/favicon.ico
--------------------------------------------------------------------------------
/ch3/form_simple/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ch3/form_simple/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | font-size: 2em;
4 | padding: 4px;
5 | margin: 4px;
6 | }
7 |
8 | .App-logo {
9 | animation: App-logo-spin infinite 20s linear;
10 | height: 80px;
11 | }
12 |
13 | .App-header {
14 | background-color: #222;
15 | height: 150px;
16 | padding: 20px;
17 | color: white;
18 | }
19 |
20 | .App-intro {
21 | font-size: large;
22 | }
23 |
24 | @keyframes App-logo-spin {
25 | from { transform: rotate(0deg); }
26 | to { transform: rotate(360deg); }
27 | }
28 |
--------------------------------------------------------------------------------
/ch3/form_simple/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { SimpleForm } from './SimpleForm'
3 | import './App.css'
4 | // 메인 화면 컴포넌트입니다.
5 | export default class App extends Component {
6 | render () {
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ch3/form_simple/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(
, div);
8 | });
9 |
--------------------------------------------------------------------------------
/ch3/form_simple/src/SimpleForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | // 입력 양식 컴포넌트입니다.
3 | export class SimpleForm extends React.Component {
4 | constructor (props) {
5 | super(props)
6 | // 상태를 초기화합니다. --- (※1)
7 | this.state = { value: '' }
8 | }
9 | // 값이 변경됐을 때 --- (※2)
10 | doChange (e) {
11 | const newValue = e.target.value
12 | this.setState({value: newValue})
13 | }
14 | // 전송 버튼을 눌렀을 때 --- (※3)
15 | doSubmit (e) {
16 | window.alert('전송: ' + this.state.value)
17 | e.preventDefault()
18 | }
19 | // 화면 렌더링 --- (※4)
20 | render () {
21 | // 이벤트를 메서드에 바인딩합니다.
22 | const doSubmit = (e) => this.doSubmit(e)
23 | const doChange = (e) => this.doChange(e)
24 | return (
25 |
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/ch3/form_simple/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ch3/form_simple/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import './index.css';
5 |
6 | ReactDOM.render(
7 |
,
8 | document.getElementById('root')
9 | );
10 |
--------------------------------------------------------------------------------
/ch3/inch_to_cm/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/inch_to_cm/.DS_Store
--------------------------------------------------------------------------------
/ch3/inch_to_cm/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | config=$(cd $(dirname $0) &&pwd)/webpack.config.js
3 | cd ../.. && webpack --config $config $*
4 |
5 |
--------------------------------------------------------------------------------
/ch3/inch_to_cm/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch3/inch_to_cm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "src",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "dependencies": {
7 | "react-scripts": "^0.9.5",
8 | "standard": "^10.0.2",
9 | "superagent": "^3.5.2"
10 | },
11 | "devDependencies": {
12 | "babel-core": "^6.24.1",
13 | "babel-loader": "^6.4.1",
14 | "babel-preset-es2015": "^6.24.1",
15 | "babel-preset-react": "^6.24.1",
16 | "css-loader": "^0.28.0",
17 | "react": "^15.5.4",
18 | "react-dom": "^15.5.4",
19 | "standard": "^10.0.2",
20 | "style-loader": "^0.16.1",
21 | "webpack": "^2.3.3"
22 | },
23 | "scripts": {
24 | "test": "echo \"Error: no test specified\" && exit 1"
25 | },
26 | "keywords": [],
27 | "author": "",
28 | "license": "ISC"
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/ch3/inch_to_cm/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/inch_to_cm/src/.DS_Store
--------------------------------------------------------------------------------
/ch3/inch_to_cm/src/InchToCm.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import ValueInput from './ValueInput'
3 | // inch와 cm 변환 컴포넌트
4 | export default class InchToCm extends Component {
5 | constructor (props) {
6 | super(props)
7 | // ValueInput에 출력할 값을 상태로 저장합니다. --- (※1)
8 | this.state = {
9 | inch: 0, cm: 0
10 | }
11 | }
12 | // inch가 변경됐을 때 --- (※2)
13 | inchChanged (e) {
14 | const inchValue = e.value
15 | const cmValue = inchValue * 2.54
16 | this.setState({
17 | inch: inchValue,
18 | cm: cmValue
19 | })
20 | }
21 | // cm가 변경됐을 때 --- (※3)
22 | cmChanged (e) {
23 | const cmValue = e.value
24 | const inchValue = cmValue / 2.54
25 | this.setState({
26 | inch: inchValue,
27 | cm: cmValue
28 | })
29 | }
30 | // 화면 렌더링 --- (※4)
31 | render () {
32 | return (
33 |
34 | this.inchChanged(e)}
36 | value={this.state.inch} />
37 | this.cmChanged(e)}
39 | value={this.state.cm} />
40 |
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ch3/inch_to_cm/src/ValueInput.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | // 숫자 입력 컴포넌트
3 | export default class ValueInput extends Component {
4 | constructor (props) {
5 | super(props)
6 | // 프로퍼티로 초깃값을 설정합니다. --- (※5)
7 | this.state = {
8 | value: this.props.value
9 | }
10 | }
11 | // 값이 사용자에 의해 변경됐을 때 --- (※6)
12 | handleChange (e) {
13 | const v = e.target.value
14 | // 숫자 이외의 값을 제거합니다.
15 | const newValue = v.replace(/[^0-9.]+/g, '')
16 | // 상태에 설정합니다. --- (※7)
17 | this.setState({value: newValue})
18 | // 이벤트를 실행합니다. --- (※8)
19 | if (this.props.onChange) {
20 | this.props.onChange({
21 | target: this,
22 | value: newValue
23 | })
24 | }
25 | }
26 | // 프로퍼티가 변경됐을 때 --- (※9)
27 | componentWillReceiveProps (nextProps) {
28 | this.setState({value: nextProps.value})
29 | }
30 | // 렌더링 --- (※10)
31 | render () {
32 | return (
33 |
38 |
)
39 | }
40 | }
--------------------------------------------------------------------------------
/ch3/inch_to_cm/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import InchToCm from './InchToCm'
4 |
5 | ReactDOM.render(
6 |
,
7 | document.getElementById('root')
8 | )
9 |
--------------------------------------------------------------------------------
/ch3/inch_to_cm/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: path.join(basedir, 'src/index.js'),
6 | output: {
7 | path: path.join(basedir, 'out'),
8 | filename: 'bundle.js'
9 | },
10 | devtool: 'inline-source-map',
11 | module: {
12 | rules: [
13 | {
14 | test: /.js$/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['es2015', 'react']
18 | }
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch3/input_compo/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/input_compo/.DS_Store
--------------------------------------------------------------------------------
/ch3/input_compo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch3/input_compo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "src",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "build": "webpack",
8 | "start": "serve -p 3000"
9 | },
10 | "dependencies": {
11 | "react-scripts": "^0.9.5",
12 | "serve": "^5.2.2",
13 | "standard": "^10.0.2",
14 | "superagent": "^3.5.2"
15 | },
16 | "devDependencies": {
17 | "babel-core": "^6.24.1",
18 | "babel-loader": "^6.4.1",
19 | "babel-preset-es2015": "^6.24.1",
20 | "babel-preset-react": "^6.24.1",
21 | "css-loader": "^0.28.0",
22 | "react": "^15.5.4",
23 | "react-dom": "^15.5.4",
24 | "standard": "^10.0.2",
25 | "style-loader": "^0.16.1",
26 | "webpack": "^2.3.3"
27 | },
28 | "keywords": [],
29 | "author": "",
30 | "license": "ISC"
31 | }
32 |
--------------------------------------------------------------------------------
/ch3/input_compo/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/input_compo/src/.DS_Store
--------------------------------------------------------------------------------
/ch3/input_compo/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import FormInput from './FormInput'
4 | class CustomForm extends React.Component {
5 | constructor (props) {
6 | super(props)
7 | this.state = {
8 | email: '',
9 | tel: '',
10 | allok: false
11 | }
12 | this.oks = {}
13 | }
14 | handleChange (e) {
15 | // 모든 항목이 OK인지 확인합니다.
16 | this.oks[e.name] = e.isOK
17 | this.setState({
18 | [e.name]: e.value,
19 | allok: (this.oks['email'] && this.oks['tel'])
20 | })
21 | }
22 | handleSubmit (e) {
23 | window.alert(JSON.stringify(this.state))
24 | e.preventDefault()
25 | }
26 | render () {
27 | const doChange = e => this.handleChange(e)
28 | const doSubmit = e => this.handleSubmit(e)
29 | // 이메일 패턴
30 | const emailPat = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
31 | // ASCII 문자 이외 전부
32 | const asciiFilter = /[^\u0020-\u007e]+/g
33 | return (
34 |
48 | )
49 | }
50 | }
51 | // DOM을 변경합니다.
52 | ReactDOM.render(
53 |
,
54 | document.getElementById('root')
55 | )
--------------------------------------------------------------------------------
/ch3/input_compo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: path.join(basedir, 'src/index.js'),
6 | output: {
7 | path: path.join(basedir, 'out'),
8 | filename: 'bundle.js'
9 | },
10 | devtool: 'inline-source-map',
11 | module: {
12 | rules: [
13 | {
14 | test: /.js$/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['es2015', 'react']
18 | }
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch3/input_zip/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/input_zip/.DS_Store
--------------------------------------------------------------------------------
/ch3/input_zip/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | config=$(cd $(dirname $0) &&pwd)/webpack.config.js
3 | cd ../.. && webpack --config $config $*
4 |
5 |
--------------------------------------------------------------------------------
/ch3/input_zip/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch3/input_zip/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "src",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "dependencies": {
7 | "react-scripts": "^0.9.5",
8 | "standard": "^10.0.2",
9 | "superagent": "^3.5.2"
10 | },
11 | "devDependencies": {
12 | "babel-core": "^6.24.1",
13 | "babel-loader": "^6.4.1",
14 | "babel-preset-es2015": "^6.24.1",
15 | "babel-preset-react": "^6.24.1",
16 | "css-loader": "^0.28.0",
17 | "react": "^15.5.4",
18 | "react-dom": "^15.5.4",
19 | "standard": "^10.0.2",
20 | "style-loader": "^0.16.1",
21 | "webpack": "^2.3.3"
22 | },
23 | "scripts": {
24 | "test": "echo \"Error: no test specified\" && exit 1"
25 | },
26 | "keywords": [],
27 | "author": "",
28 | "license": "ISC"
29 | }
30 |
--------------------------------------------------------------------------------
/ch3/input_zip/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/input_zip/src/.DS_Store
--------------------------------------------------------------------------------
/ch3/input_zip/src/ZipInput.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | // 우편 번호 입력 컴포넌트
3 | export default class ZipInput extends Component {
4 | constructor (props) {
5 | super(props)
6 | const v = (this.props.value)
7 | ? this.props.value : ''
8 | // 상태를 초기화합니다. --- (※1)
9 | this.state = {
10 | value: v,
11 | isOK: this.checkValue(v)
12 | }
13 | }
14 | // 패턴에 맞는지 확인하기--- (※2)
15 | checkValue (s) {
16 | const zipPattern = /^\d{5}$/
17 | return zipPattern.test(s)
18 | }
19 | // 값이 사용자에 의해 변경됐을 때 --- (※3)
20 | handleChange (e) {
21 | const v = e.target.value
22 | // 숫자 이외의 값을 제거합니다.
23 | const newValue = v.replace(/[^0-9]+/g, '')
24 | const newIsOK = this.checkValue(newValue)
25 | // 상태에 설정합니다.
26 | this.setState({
27 | value: newValue,
28 | isOK: newIsOK
29 | })
30 | // 이벤트를 실행합니다. --- (※4)
31 | if (this.props.onChange) {
32 | this.props.onChange({
33 | target: this,
34 | value: newValue,
35 | isOK: newIsOK
36 | })
37 | }
38 | }
39 | // 프로퍼티가 변경됐을 때 --- (※5)
40 | componentWillReceiveProps (nextProps) {
41 | this.setState({
42 | value: nextProps.value,
43 | isOK: this.checkValue(nextProps.value)
44 | })
45 | }
46 | // 렌더링 --- (※6)
47 | render () {
48 | const msg = this.renderStatusMessage()
49 | return (
50 |
57 |
)
58 | }
59 | // 입력이 제대로 됐는지 출력하는 메시지 --- (※7)
60 | renderStatusMessage () {
61 | // 메시지에 적용할 스타일
62 | const so = {
63 | margin: '8px',
64 | padding: '8px',
65 | color: 'white'
66 | }
67 | let msg = null
68 | if (this.state.isOK) { // OK 때
69 | so.backgroundColor = 'green'
70 | msg =
OK
71 | } else { // NG 때(빈 문자열이라면 출력하지 않습니다)
72 | if (this.state.value !== '') {
73 | so.backgroundColor = 'red'
74 | msg =
NG
75 | }
76 | }
77 | return msg
78 | }
79 | }
--------------------------------------------------------------------------------
/ch3/input_zip/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import ZipInput from './ZipInput'
4 | ReactDOM.render(
5 |
,
6 | document.getElementById('root')
7 | )
--------------------------------------------------------------------------------
/ch3/input_zip/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: path.join(basedir, 'src/index.js'),
6 | output: {
7 | path: path.join(basedir, 'out'),
8 | filename: 'bundle.js'
9 | },
10 | devtool: 'inline-source-map',
11 | module: {
12 | rules: [
13 | {
14 | test: /.js$/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['es2015', 'react']
18 | }
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch3/parts/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/parts/.DS_Store
--------------------------------------------------------------------------------
/ch3/parts/build.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: {
6 | 'text': path.join(basedir, 'src/text.js'),
7 | 'cbox': path.join(basedir, 'src/cbox.js'),
8 | 'textarea': path.join(basedir, 'src/textarea.js'),
9 | 'radio': path.join(basedir, 'src/radio.js'),
10 | 'select': path.join(basedir, 'src/select.js')
11 | },
12 | output: {
13 | path: path.join(basedir, 'out'),
14 | filename: '[name].js'
15 | },
16 | devtool: 'inline-source-map',
17 | module: {
18 | rules: [
19 | {
20 | test: /.js$/,
21 | loader: 'babel-loader',
22 | options: {
23 | presets: ['es2015', 'react']
24 | }
25 | },
26 | {
27 | test: /.css$/,
28 | use: [
29 | {
30 | loader: 'style-loader'
31 | },
32 | {
33 | loader:'css-loader',
34 | options: {
35 | modules: true
36 | }
37 | }
38 | ]
39 | }
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ch3/parts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | config=$(cd $(dirname $0) &&pwd)/build.config.js
3 | cd ../.. && webpack --config $config $*
4 |
5 |
--------------------------------------------------------------------------------
/ch3/parts/cbox.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/parts/out/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/parts/out/.DS_Store
--------------------------------------------------------------------------------
/ch3/parts/radio.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/parts/select.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/parts/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/parts/src/.DS_Store
--------------------------------------------------------------------------------
/ch3/parts/src/cbox.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | class CBoxForm extends React.Component {
4 | constructor (props) {
5 | super(props)
6 | this.state = { check: true }
7 | }
8 | render () {
9 | // 입력 양식에 체크 상태를 지정합니다.
10 | return (
11 |
20 |
)
21 | }
22 | // 체크박스를 클릭했을 때
23 | doChange (e) {
24 | this.setState({ check: !this.state.check })
25 | }
26 | // 입력 양식을 전송했을 때
27 | doSubmit (e) {
28 | e.preventDefault()
29 | window.alert(this.state.check ? '먹기' : '먹지 않기')
30 | }
31 | }
32 | ReactDOM.render(
33 |
,
34 | document.getElementById('root')
35 | )
--------------------------------------------------------------------------------
/ch3/parts/src/radio.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | class RadioForm extends React.Component {
4 | constructor (props) {
5 | super(props)
6 | this.state = {
7 | items: props.items,
8 | value: ''
9 | }
10 | }
11 | render () {
12 | // 라디오버튼을 생성합니다.
13 | const radiolist = this.state.items.map(i => {
14 | return (
15 |
21 |
)
22 | })
23 | // 입력 양식에 라디오버튼 목록을 지정합니다.
24 | return (
25 |
29 |
)
30 | }
31 | // 라디오버튼을 변경했을 때
32 | doChange (e) {
33 | this.setState({ value: e.target.value })
34 | }
35 | // 입력 양식을 전송했을 때
36 | doSubmit (e) {
37 | e.preventDefault()
38 | window.alert(this.state.value)
39 | }
40 | }
41 | ReactDOM.render(
42 |
,
43 | document.getElementById('root'))
--------------------------------------------------------------------------------
/ch3/parts/src/select.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | class SelectForm extends React.Component {
4 | constructor (props) {
5 | super(props)
6 | this.state = {
7 | items: props.items,
8 | value: props.value
9 | }
10 | }
11 | render () {
12 | // 선택 박스를 생성합니다.
13 | const options = this.state.items.map(i => {
14 | return (
)
17 | })
18 | // 입력 양식으로 선택 박스를 지정합니다.
19 | return (
20 |
28 |
)
29 | }
30 | // 선택 박스를 변경했을 때
31 | doChange (e) {
32 | this.setState({ value: e.target.value })
33 | }
34 | // 입력 양식을 전송했을 때
35 | doSubmit (e) {
36 | e.preventDefault()
37 | window.alert(this.state.value)
38 | }
39 | }
40 | ReactDOM.render(
41 |
,
44 | document.getElementById('root')
45 | )
--------------------------------------------------------------------------------
/ch3/parts/src/text.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | class TextForm extends React.Component {
5 | constructor (props) {
6 | super(props)
7 | this.state = { value: '' }
8 | }
9 | render () {
10 | // フォームにテキストボックスを指定
11 | return (
12 |
18 |
)
19 | }
20 | // テキストボックスを変更したとき
21 | doChange (e) {
22 | this.setState({ value: e.target.value })
23 | }
24 | // フォームを送信したとき
25 | doSubmit (e) {
26 | e.preventDefault()
27 | window.alert(this.state.value)
28 | }
29 | }
30 |
31 | ReactDOM.render(
32 |
,
33 | document.getElementById('root')
34 | )
35 |
--------------------------------------------------------------------------------
/ch3/parts/src/textarea.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | class TextAreaForm extends React.Component {
4 | constructor (props) {
5 | super(props)
6 | this.state = { value: 'Hello' }
7 | }
8 | render () {
9 | // 입력 양식으로 텍스트에리어를 지정합니다.
10 | return (
11 |
18 |
)
19 | }
20 | // 텍스트에리어를 변경했을 때
21 | doChange (e) {
22 | this.setState({ value: e.target.value })
23 | }
24 | // 입력 양식을 전송했을 때
25 | doSubmit (e) {
26 | e.preventDefault()
27 | window.alert(this.state.value)
28 | }
29 | }
30 | ReactDOM.render(
31 |
,
32 | document.getElementById('root')
33 | )
--------------------------------------------------------------------------------
/ch3/parts/text.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/parts/textarea.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch3/refs-instance-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
38 |
--------------------------------------------------------------------------------
/ch3/sagent/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/ch3/sagent/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sagent",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4",
8 | "superagent": "^3.5.2"
9 | },
10 | "devDependencies": {
11 | "react-scripts": "0.9.5"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ch3/sagent/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/sagent/public/favicon.ico
--------------------------------------------------------------------------------
/ch3/sagent/public/fruits.json:
--------------------------------------------------------------------------------
1 | [
2 | {"name": "Apple", "price": 300},
3 | {"name": "Orange", "price": 280},
4 | {"name": "Banana", "price": 130},
5 | {"name": "Mango", "price": 250}
6 | ]
7 |
8 |
--------------------------------------------------------------------------------
/ch3/sagent/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ch3/sagent/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/ch3/sagent/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import './App.css'
3 | // SuperAgent 사용 선언 --- (※1)
4 | import request from 'superagent'
5 | class App extends Component {
6 | constructor (props) {
7 | super(props)
8 | // 상태를 초기화합니다.
9 | this.state = {
10 | items: null // 읽어 들인 데이터 저장 전용
11 | }
12 | }
13 | // 마운트됐을 때
14 | componentWillMount () {
15 | // JSON 데이터 읽어 들이기 --- (※2)
16 | request.get('./fruits.json')
17 | .accept('application/json')
18 | .end((err, res) => {
19 | this.loadedJSON(err, res)
20 | })
21 | }
22 | // 데이터를 읽어 들였을 때 --- (※3)
23 | loadedJSON (err, res) {
24 | if (err) {
25 | console.log('JSON을 읽어 들이는 동안 오류가 발생했습니다')
26 | return
27 | }
28 | // 상태를 변경합니다. --- (※4)
29 | this.setState({
30 | items: res.body
31 | })
32 | }
33 | render () {
34 | // JSON 데이터를 제대로 읽어 들였는지 확인 --- (※5)
35 | if (!this.state.items) {
36 | return
37 | 읽어 들이는 중입니다.
38 | }
39 | // 읽어 들인 데이터를 기반으로 select 요소를 생성합니다. --- (※6)
40 | const options = this.state.items.map(e => {
41 | return
44 | })
45 | return (
46 |
47 | 과일:
48 |
49 | )
50 | }
51 | }
52 | export default App
--------------------------------------------------------------------------------
/ch3/sagent/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div')
7 | ReactDOM.render(
, div)
8 | })
9 |
--------------------------------------------------------------------------------
/ch3/sagent/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ch3/sagent/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.render(
7 |
,
8 | document.getElementById('root')
9 | )
10 |
--------------------------------------------------------------------------------
/ch3/sagent/src/test-sagent.js:
--------------------------------------------------------------------------------
1 | // 모듈을 읽어 들입니다. --- (※1)
2 | const request = require('superagent')
3 | // 지정한 URL에서 데이터를 추출합니다. --- (※2)
4 | const URL = 'http://localhost:3000/fruits.json'
5 | request.get(URL)
6 | .end(callbackGet)
7 | // 데이터를 추출했을 때의 처리 --- (※3)
8 | function callbackGet (err, res) {
9 | if (err) {
10 | // 추출하지 못 했을 때의 처리
11 | return
12 | }
13 | console.log(res.body)
14 | }
--------------------------------------------------------------------------------
/ch3/stopwatch/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/ch3/stopwatch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stopwatch",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4"
8 | },
9 | "devDependencies": {
10 | "react-scripts": "0.9.5"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
--------------------------------------------------------------------------------
/ch3/stopwatch/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch3/stopwatch/public/favicon.ico
--------------------------------------------------------------------------------
/ch3/stopwatch/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ch3/stopwatch/src/Stopwatch.css:
--------------------------------------------------------------------------------
1 | .Stopwatch {
2 | text-align: center;
3 | }
4 |
5 | .disp {
6 | font-size: 70px;
7 | }
8 |
9 | button {
10 | font-size: 24px;
11 | width: 280px;
12 | }
13 |
--------------------------------------------------------------------------------
/ch3/stopwatch/src/Stopwatch.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import './Stopwatch.css'
3 | // Stopwatch 컴포넌트를 정의합니다.
4 | class Stopwatch extends Component {
5 | constructor (props) {
6 | super(props)
7 | this.state = { // 초깃값을 설정합니다. --- (※1)
8 | isLive: false,
9 | curTime: 0,
10 | startTime: 0
11 | }
12 | this.timerId = 0
13 | }
14 | // 마운트했을 때 --- (※2)
15 | componentWillMount () {
16 | this.timerId = setInterval(e => {
17 | this.tick()
18 | }, 1000)
19 | }
20 | // 언마운트했을 때 --- (※3)
21 | componentWillUnmount () {
22 | clearInterval(this.timerId)
23 | }
24 | // 매 초 실행됩니다. --- (※4)
25 | tick () {
26 | if (this.state.isLive) {
27 | const v = new Date().getTime()
28 | this.setState({curTime: v})
29 | }
30 | }
31 | // 시작/중지 버튼을 클릭했을 때 --- (※5)
32 | clickHandler (e) {
33 | // 중지할 때
34 | if (this.state.isLive) {
35 | this.setState({isLive: false})
36 | return
37 | }
38 | // 시작할 때
39 | const v = new Date().getTime()
40 | this.setState({
41 | curTime: v,
42 | startTime: v,
43 | isLive: true})
44 | };
45 | // 출력할 시계를 생성합니다. --- (※6)
46 | getDisp () {
47 | const s = this.state
48 | const delta = s.curTime - s.startTime
49 | const t = Math.floor(delta / 1000)
50 | const ss = t % 60
51 | const m = Math.floor(t / 60)
52 | const mm = m % 60
53 | const hh = Math.floor(mm / 60)
54 | const z = (num) => {
55 | const s = '00' + String(num)
56 | return s.substr(s.length - 2, 2)
57 | }
58 | return
59 | {z(hh)}:{z(mm)}:{z(ss)}
60 |
61 | }
62 | // 화면 렌더링 --- (※7)
63 | render () {
64 | let label = 'START'
65 | if (this.state.isLive) {
66 | label = 'STOP'
67 | }
68 | const disp = this.getDisp()
69 | const fclick = (e) => this.clickHandler(e)
70 | return (
71 |
{disp}
72 |
73 |
)
74 | }
75 | }
76 | export default Stopwatch
--------------------------------------------------------------------------------
/ch3/stopwatch/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Stopwatch from './Stopwatch'
4 |
5 | ReactDOM.render(
6 |
,
7 | document.getElementById('root')
8 | )
9 |
--------------------------------------------------------------------------------
/ch4/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch4/.DS_Store
--------------------------------------------------------------------------------
/ch4/TestNative/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/ch4/TestNative/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/ch4/TestNative/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 |
/\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 | .*/Libraries/react-native/ReactNative.js
16 |
17 | [include]
18 |
19 | [libs]
20 | node_modules/react-native/Libraries/react-native/react-native-interface.js
21 | node_modules/react-native/flow
22 | flow/
23 |
24 | [options]
25 | emoji=true
26 |
27 | module.system=haste
28 |
29 | experimental.strict_type_args=true
30 |
31 | munge_underscores=true
32 |
33 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
34 |
35 | suppress_type=$FlowIssue
36 | suppress_type=$FlowFixMe
37 | suppress_type=$FixMe
38 |
39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
40 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
41 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
42 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
43 |
44 | unsafe.enable_getters_and_setters=true
45 |
46 | [version]
47 | ^0.42.0
48 |
--------------------------------------------------------------------------------
/ch4/TestNative/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/ch4/TestNative/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 |
39 | # BUCK
40 | buck-out/
41 | \.buckd/
42 | *.keystore
43 |
44 | # fastlane
45 | #
46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47 | # screenshots whenever they are needed.
48 | # For more information about the recommended setup visit:
49 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
50 |
51 | fastlane/report.xml
52 | fastlane/Preview.html
53 | fastlane/screenshots
54 |
--------------------------------------------------------------------------------
/ch4/TestNative/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/ch4/TestNative/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TestNative",
3 | "displayName": "TestNative"
4 | }
--------------------------------------------------------------------------------
/ch4/TestNative/index.android.js:
--------------------------------------------------------------------------------
1 | // 필요한 모듈을 읽어 들입니다. --- (※1)
2 | import React, { Component } from 'react'
3 | import {
4 | AppRegistry, StyleSheet, Text, View
5 | } from 'react-native'
6 | // 메인 컴포넌트 정의 --- (※2)
7 | export default class TestNative extends Component {
8 | render() {
9 | const msg =
10 | 'React Native를 사용해\n' +
11 | '애플리케이션 만들기'
12 | return (
13 |
14 | {msg}
15 |
16 | )
17 | }
18 | }
19 | // 스타일 설정 --- (※3)
20 | const styles = StyleSheet.create({
21 | base: {
22 | flex: 1,
23 | justifyContent: 'center',
24 | alignItems: 'center',
25 | backgroundColor: '#F0F0FF'
26 | },
27 | title: {
28 | fontSize: 46
29 | }
30 | })
31 | // 애플리케이션에 컴포넌트를 등록 --- (※4)
32 | AppRegistry.registerComponent('TestNative', () => TestNative)
--------------------------------------------------------------------------------
/ch4/TestNative/index.ios.js:
--------------------------------------------------------------------------------
1 | // 필요한 모듈을 선언합니다.
2 | import React, { Component } from 'react'
3 | import {
4 | AppRegistry, StyleSheet, Text, View
5 | } from 'react-native'
6 | // 메인 컴포넌트 선언 --- (※1)
7 | export default class TestNative extends Component {
8 | render () {
9 | // 배열 데이터를 정의합니다. --- (※2)
10 | const lines = [
11 | '태어난 때가 있으면', '죽을 때도 있다.', '---',
12 | '울 때가 있으면', '웃을 때도 있다.', '---',
13 | '침묵을 지켜야 할 때가 있고', '침묵을 깨야할 때가 있다'
14 | ]
15 | // 배열 데이터를 기반으로 컴포넌트를 생성합니다. --- (※3)
16 | const textLines = lines.map((e, i) => {
17 | return
20 | })
21 | return (
22 |
23 | {textLines}
24 |
25 | )
26 | }
27 | }
28 | // 스타일시트를 선언합니다.
29 | const styles = StyleSheet.create({
30 | container: {
31 | flex: 1,
32 | justifyContent: 'center',
33 | alignItems: 'center',
34 | backgroundColor: '#F5FCFF'
35 | },
36 | line: {
37 | fontSize: 20,
38 | textAlign: 'center',
39 | margin: 10
40 | }
41 | })
42 | // 메인 컴포넌트를 등록합니다. --- (※4)
43 | AppRegistry.registerComponent('TestNative', () => TestNative)
--------------------------------------------------------------------------------
/ch4/TestNative/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TestNative",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start",
7 | "test": "jest"
8 | },
9 | "dependencies": {
10 | "react": "16.0.0-alpha.6",
11 | "react-native": "0.44.0"
12 | },
13 | "devDependencies": {
14 | "babel-jest": "20.0.0",
15 | "babel-preset-react-native": "1.9.1",
16 | "jest": "20.0.0",
17 | "react-test-renderer": "16.0.0-alpha.6"
18 | },
19 | "jest": {
20 | "preset": "react-native"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch4/electron_clipfmt/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch4/electron_clipfmt/.DS_Store
--------------------------------------------------------------------------------
/ch4/electron_clipfmt/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch4/electron_clipfmt/main.js:
--------------------------------------------------------------------------------
1 | // Electronの実行に必要なモジュールを取り込む
2 | const electron = require('electron')
3 | const path = require('path')
4 | const url = require('url')
5 | const app = electron.app
6 | const BrowserWindow = electron.BrowserWindow
7 |
8 | // Electronのライフサイクルを定義
9 | let mainWindow // メインウィンドウを表す変数
10 | app.on('ready', createWindow)
11 | app.on('window-all-closed', function () {
12 | if (process.platform !== 'darwin') app.quit()
13 | })
14 | app.on('activate', function () {
15 | if (mainWindow === null) createWindow()
16 | })
17 |
18 | // ウィンドウを作成してコンテンツを読み込む
19 | function createWindow () {
20 | mainWindow = new BrowserWindow({width: 600, height: 400})
21 | mainWindow.loadURL(url.format({ // 読み込むコンテンツを指定 --- (※1)
22 | pathname: path.join(__dirname, 'index.html'),
23 | protocol: 'file:',
24 | slashes: true
25 | }))
26 | // mainWindow.webContents.openDevTools()
27 | // ウィンドウが閉じるときの処理
28 | mainWindow.on('closed', function () {
29 | mainWindow = null
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/ch4/electron_clipfmt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron_clipfmt",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "build": "webpack",
9 | "watch": "webpack --watch"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "babel-core": "^6.24.1",
16 | "babel-loader": "^7.0.0",
17 | "babel-preset-es2015": "^6.24.1",
18 | "babel-preset-react": "^6.24.1",
19 | "electron": "^1.6.6",
20 | "react": "^15.5.4",
21 | "react-dom": "^15.5.4",
22 | "webpack": "^2.4.1"
23 | },
24 | "dependencies": {
25 | "photon": "git+https://github.com/connors/photon.git"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ch4/electron_clipfmt/webpack.config.js:
--------------------------------------------------------------------------------
1 | // Reactを変換するためのWebpackの設定ファイル
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | // 変換対象から除外するモジュール --- (※1)
5 | const externalPlugins = new webpack.ExternalsPlugin('commonjs', [
6 | 'app',
7 | 'auto-updater',
8 | 'browser-window',
9 | 'content-tracing',
10 | 'dialog',
11 | 'electron',
12 | 'global-shortcut',
13 | 'ipc',
14 | 'menu',
15 | 'menu-item',
16 | 'power-monitor',
17 | 'protocol',
18 | 'tray',
19 | 'remote',
20 | 'web-frame',
21 | 'clipboard',
22 | 'crash-reporter',
23 | 'screen',
24 | 'shell'
25 | ])
26 |
27 | module.exports = {
28 | entry: {
29 | index: path.join(__dirname, 'src', 'index.js')
30 | },
31 | output: {
32 | path: path.join(__dirname, 'out'),
33 | filename: '[name].js'
34 | },
35 | devtool: 'cheap-module-eval-source-map',
36 | target: 'node',
37 | module: {
38 | rules: [
39 | {
40 | test: /.js$/,
41 | exclude: /node_modules/,
42 | loader: 'babel-loader',
43 | options: {
44 | presets: ['es2015', 'react']
45 | }
46 | }
47 | ]
48 | },
49 | plugins: [
50 | externalPlugins
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/ch4/electron_hello/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch4/electron_hello/.DS_Store
--------------------------------------------------------------------------------
/ch4/electron_hello/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch4/electron_hello/main.js:
--------------------------------------------------------------------------------
1 | // 일렉트론 실행에 필요한 모듈을 읽어 들입니다.
2 | const electron = require('electron')
3 | const path = require('path')
4 | const url = require('url')
5 | const app = electron.app
6 | const BrowserWindow = electron.BrowserWindow
7 | // 일렉트론의 라이프사이클을 정의합니다.
8 | let mainWindow // 메인 화면을 출력할 변수입니다.
9 | app.on('ready', createWindow)
10 | app.on('window-all-closed', function () {
11 | if (process.platform !== 'darwin') app.quit()
12 | })
13 | app.on('activate', function () {
14 | if (mainWindow === null) createWindow()
15 | })
16 | // 화면을 생성하고 콘텐츠를 읽어 들입니다.
17 | function createWindow () {
18 | mainWindow = new BrowserWindow({width: 800, height: 600})
19 | mainWindow.loadURL(url.format({ // 읽어 들일 콘텐츠를 지정합니다. --- (※1)
20 | pathname: path.join(__dirname, 'index.html'),
21 | protocol: 'file:',
22 | slashes: true
23 | }))
24 | // 화면이 닫혔을 때의 처리
25 | mainWindow.on('closed', function () {
26 | mainWindow = null
27 | })
28 | }
--------------------------------------------------------------------------------
/ch4/electron_hello/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron_hello",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "build": "webpack"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "babel-core": "^6.24.1",
15 | "babel-loader": "^7.0.0",
16 | "babel-preset-es2015": "^6.24.1",
17 | "babel-preset-react": "^6.24.1",
18 | "electron": "^1.6.6",
19 | "react": "^15.5.4",
20 | "react-dom": "^15.5.4",
21 | "webpack": "^2.4.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ch4/electron_hello/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactDOM from 'react-dom'
3 | // 컴포넌트를 정의합니다.
4 | export default class App extends Component {
5 | render () {
6 | return (
7 |
Hello
8 | )
9 | }
10 | }
11 | // DOM의 내용을 변경합니다.
12 | ReactDOM.render(
13 | ,
14 | document.getElementById('root'))
--------------------------------------------------------------------------------
/ch4/electron_hello/webpack.config.js:
--------------------------------------------------------------------------------
1 | // 리액트 변환을 위한 웹팩 설정 파일
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | // 변환 대상에서 제외할 모듈 --- (※1)
5 | const externalPlugins = new webpack.ExternalsPlugin('commonjs', [
6 | 'app',
7 | 'auto-updater',
8 | 'browser-window',
9 | 'content-tracing',
10 | 'dialog',
11 | 'electron',
12 | 'global-shortcut',
13 | 'ipc',
14 | 'menu',
15 | 'menu-item',
16 | 'power-monitor',
17 | 'protocol',
18 | 'tray',
19 | 'remote',
20 | 'web-frame',
21 | 'clipboard',
22 | 'crash-reporter',
23 | 'screen',
24 | 'shell'
25 | ])
26 | module.exports = {
27 | entry: {
28 | index: path.join(__dirname, 'src', 'index.js')
29 | },
30 | output: {
31 | path: path.join(__dirname, 'out'),
32 | filename: '[name].js'
33 | },
34 | devtool: 'cheap-module-eval-source-map',
35 | target: 'node',
36 | module: {
37 | rules: [
38 | {
39 | test: /.js$/,
40 | loader: 'babel-loader',
41 | options: {
42 | presets: ['es2015', 'react']
43 | }
44 | }
45 | ]
46 | },
47 | plugins: [
48 | externalPlugins
49 | ]
50 | }
--------------------------------------------------------------------------------
/ch4/mstdn_cli/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch4/mstdn_cli/.DS_Store
--------------------------------------------------------------------------------
/ch4/mstdn_cli/1_create_app.js:
--------------------------------------------------------------------------------
1 | // 웹 API을 통해 애플리케이션을 서버에 등록합니다.
2 | const Mastodon = require('mastodon-api')
3 | const fs = require('fs')
4 | const path = require('path')
5 | const instanceUri = 'https://pawoo.net'
6 | const clientName = 'MasdonCli'
7 | const savefile = path.join(__dirname, 'cli-app.json')
8 | // 웹 API를 호출합니다.
9 | Mastodon.createOAuthApp(instanceUri+'/api/v1/apps', clientName)
10 | .catch(err => console.error(err))
11 | .then(res => {
12 | console.log(res)
13 | fs.writeFileSync(savefile, JSON.stringify(res))
14 | })
--------------------------------------------------------------------------------
/ch4/mstdn_cli/2_auth.js:
--------------------------------------------------------------------------------
1 | const Mastodon = require('mastodon-api')
2 | const fs = require('fs')
3 | const path = require('path')
4 | const readlineSync = require('readline-sync')
5 | const file_cli_app = path.join(__dirname, 'cli-app.json')
6 | const file_user = path.join(__dirname, 'token.json')
7 | const instanceUri = 'https://pawoo.net'
8 | // 파일에서 클라이언트의 정보를 읽어 들입니다.
9 | const info = JSON.parse(fs.readFileSync(file_cli_app))
10 | // 인증 전용 URL을 추출합니다.
11 | Mastodon.getAuthorizationUrl(
12 | info.client_id,
13 | info.client_secret,
14 | instanceUri)
15 | .then(url => {
16 | console.log("다음 URL에 접근해서 출력되는 코드를 입력해주세요.")
17 | console.log(url)
18 | // 명령줄에 코드를 추출합니다.
19 | const code = readlineSync.question('Code: ')
20 | // 접근 토큰을 추출합니다.
21 | return Mastodon.getAccessToken(
22 | info.client_id,
23 | info.client_secret,
24 | code,
25 | instanceUri)
26 | })
27 | .then(token => {
28 | console.log('Access Token: ', token)
29 | fs.writeFileSync(file_user, token)
30 | })
--------------------------------------------------------------------------------
/ch4/mstdn_cli/3_get_timeline.js:
--------------------------------------------------------------------------------
1 | const Mastodon = require('mastodon-api')
2 | const fs = require('fs')
3 | const path = require('path')
4 | const instanceUri = 'https://pawoo.net'
5 | // 파일에서 정보를 읽어 들입니다.
6 | const token = fs.readFileSync(path.join(__dirname, 'token.json'))
7 | // 마스토돈 API 클라이언트를 생성합니다.
8 | const M = new Mastodon({
9 | access_token: token,
10 | timeout_ms: 60 * 1000,
11 | api_url: instanceUri + '/api/v1/'
12 | })
13 | // 타임라인을 읽어 들입니다. --- (※1)
14 | M.get('timelines/home', {})
15 | .then(res => {
16 | const data = res.data
17 | console.log(data)
18 | })
--------------------------------------------------------------------------------
/ch4/mstdn_cli/4_toot.js:
--------------------------------------------------------------------------------
1 | const Mastodon = require('mastodon-api')
2 | const fs = require('fs')
3 | const path = require('path')
4 | const instanceUri = 'https://pawoo.net'
5 | // 파일에서 정보를 읽어 들입니다.
6 | const token = fs.readFileSync(path.join(__dirname, 'token.json'))
7 | // 마스토돈 API 클라이언트를 생성합니다.
8 | const M = new Mastodon({
9 | access_token: token,
10 | timeout_ms: 60 * 1000,
11 | api_url: instanceUri + '/api/v1/'
12 | })
13 | // Toot합니다.
14 | M.post('statuses',
15 | {status: 'TEST TEST TEST by cli'},
16 | (err, data, res) => {
17 | if (err) {
18 | console.error(err)
19 | return
20 | }
21 | console.log(res)
22 | })
--------------------------------------------------------------------------------
/ch4/mstdn_cli/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch4/mstdn_cli/main.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron')
2 | const path = require('path')
3 | const url = require('url')
4 | const app = electron.app
5 | const BrowserWindow = electron.BrowserWindow
6 | // 일렉트론의 라이프사이클
7 | let mainWindow
8 | app.on('ready', createWindow)
9 | app.on('window-all-closed', () => app.quit())
10 | app.on('activate', () => {
11 | if (mainWindow === null) createWindow()
12 | })
13 | // 화면을 생성합니다.
14 | function createWindow () {
15 | mainWindow = new BrowserWindow({width: 600, height: 800})
16 | mainWindow.loadURL(url.format({
17 | pathname: path.join(__dirname, 'index.html'),
18 | protocol: 'file:',
19 | slashes: true
20 | }))
21 | mainWindow.on('closed', function () {
22 | mainWindow = null
23 | })
24 | }
--------------------------------------------------------------------------------
/ch4/mstdn_cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mstdn_cli",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "watch": "webpack -w",
9 | "build": "webpack"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "mastodon-api": "^1.2.0",
16 | "readline-sync": "^1.4.7"
17 | },
18 | "devDependencies": {
19 | "babel-core": "^6.24.1",
20 | "babel-loader": "^7.0.0",
21 | "babel-preset-es2015": "^6.24.1",
22 | "babel-preset-react": "^6.24.1",
23 | "electron": "^1.6.7",
24 | "react": "^15.5.4",
25 | "react-dom": "^15.5.4",
26 | "webpack": "^2.5.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ch4/mstdn_cli/src/styles.js:
--------------------------------------------------------------------------------
1 | const mainColor = '#89C9FA'
2 | export const styles = {
3 | title: {
4 | borderBottom: '1px solid silver',
5 | backgroundColor: mainColor,
6 | color: 'white',
7 | fontSize: '1em',
8 | padding: 4
9 | },
10 | editor: {
11 | width: 600 - 24,
12 | padding: 4,
13 | font: '1em',
14 | backgroundColor: '#F0F0FF'
15 | },
16 | editorPad: {
17 | position: 'fixed',
18 | top: 0,
19 | width: 600 - 16,
20 | height: 120,
21 | backgroundColor: 'white'
22 | },
23 | content: {
24 | margin: 8,
25 | borderBottom: '1px solid silver'
26 | },
27 | avatar: {
28 | float: 'left',
29 | width: 120
30 | },
31 | ctext: {
32 | float: 'left',
33 | width: 430,
34 | padding: 8
35 | },
36 | reblog: {
37 | backgroundColor: mainColor
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/ch4/mstdn_cli/webpack.config.js:
--------------------------------------------------------------------------------
1 | // Reactを変換するためのWebpackの設定ファイル
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | // 変換対象から除外するモジュール --- (※1)
5 | const externalPlugins = new webpack.ExternalsPlugin('commonjs', [
6 | 'app',
7 | 'auto-updater',
8 | 'browser-window',
9 | 'content-tracing',
10 | 'dialog',
11 | 'electron',
12 | 'global-shortcut',
13 | 'ipc',
14 | 'menu',
15 | 'menu-item',
16 | 'power-monitor',
17 | 'protocol',
18 | 'tray',
19 | 'remote',
20 | 'web-frame',
21 | 'clipboard',
22 | 'crash-reporter',
23 | 'screen',
24 | 'shell'
25 | ])
26 |
27 | module.exports = {
28 | entry: {
29 | index: path.join(__dirname, 'src', 'index.js')
30 | },
31 | output: {
32 | path: path.join(__dirname, 'out'),
33 | filename: '[name].js'
34 | },
35 | devtool: 'cheap-module-eval-source-map',
36 | target: 'node',
37 | module: {
38 | rules: [
39 | {
40 | test: /.js$/,
41 | exclude: /node_modules/,
42 | loader: 'babel-loader',
43 | options: {
44 | presets: ['es2015', 'react']
45 | }
46 | }
47 | ]
48 | },
49 | plugins: [
50 | externalPlugins
51 | ]
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 | .*/Libraries/react-native/ReactNative.js
16 |
17 | [include]
18 |
19 | [libs]
20 | node_modules/react-native/Libraries/react-native/react-native-interface.js
21 | node_modules/react-native/flow
22 | flow/
23 |
24 | [options]
25 | emoji=true
26 |
27 | module.system=haste
28 |
29 | experimental.strict_type_args=true
30 |
31 | munge_underscores=true
32 |
33 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
34 |
35 | suppress_type=$FlowIssue
36 | suppress_type=$FlowFixMe
37 | suppress_type=$FixMe
38 |
39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
40 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
41 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
42 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
43 |
44 | unsafe.enable_getters_and_setters=true
45 |
46 | [version]
47 | ^0.42.0
48 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 |
39 | # BUCK
40 | buck-out/
41 | \.buckd/
42 | *.keystore
43 |
44 | # fastlane
45 | #
46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47 | # screenshots whenever they are needed.
48 | # For more information about the recommended setup visit:
49 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
50 |
51 | fastlane/report.xml
52 | fastlane/Preview.html
53 | fastlane/screenshots
54 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/ch4/native_mstdn/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "native_mstdn",
3 | "displayName": "native_mstdn"
4 | }
--------------------------------------------------------------------------------
/ch4/native_mstdn/index.android.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from 'react-native'
2 | import MastodonClient from './MastodonClient.js'
3 | AppRegistry.registerComponent('native_mstdn', () => MastodonClient)
4 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/index.ios.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from 'react-native'
2 | import MastodonClient from './MastodonClient.js'
3 | AppRegistry.registerComponent('native_mstdn', () => MastodonClient)
4 |
--------------------------------------------------------------------------------
/ch4/native_mstdn/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "native_mstdn",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start",
7 | "test": "jest"
8 | },
9 | "dependencies": {
10 | "react": "16.0.0-alpha.6",
11 | "react-native": "0.44.0"
12 | },
13 | "devDependencies": {
14 | "babel-jest": "20.0.3",
15 | "babel-preset-react-native": "1.9.2",
16 | "jest": "20.0.3",
17 | "react-test-renderer": "16.0.0-alpha.6"
18 | },
19 | "jest": {
20 | "preset": "react-native"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch5/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/.DS_Store
--------------------------------------------------------------------------------
/ch5/bbs/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/bbs/.DS_Store
--------------------------------------------------------------------------------
/ch5/bbs/bbs-server.js:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------
2 | // 게시판 애플리케이션의 웹 서버
3 | // --------------------------------------------------------
4 | // 데이터베이스에 접속합니다. --- (※1)
5 | const NeDB = require('nedb')
6 | const path = require('path')
7 | const db = new NeDB({
8 | filename: path.join(__dirname, 'bbs.db'),
9 | autoload: true
10 | })
11 | // 서버를 실행합니다. --- (※2)
12 | const express = require('express')
13 | const app = express()
14 | const portNo = 3001
15 | app.listen(portNo, () => {
16 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
17 | })
18 | // public 디렉터리의 내용을 자동으로 응답합니다. --- (※3)
19 | app.use('/public', express.static('./public'))
20 | // 최상위 페이지에 접속하면 /public으로 리다이렉트합니다.
21 | app.get('/', (req, res) => {
22 | res.redirect(302, '/public')
23 | })
24 | // API를 정의합니다.
25 | // 로그 추출 API --- (※4)
26 | app.get('/api/getItems', (req, res) => {
27 | // 데이터베이스에 저장돼 있는 데이터를 시간 순서로 정렬해서 응답합니다.
28 | db.find({}).sort({stime: 1}).exec((err, data) => {
29 | if (err) {
30 | sendJSON(res, false, {logs: [], msg: err})
31 | return
32 | }
33 | console.log(data)
34 | sendJSON(res, true, {logs: data})
35 | })
36 | })
37 | // 로그 작성 API --- (※5)
38 | app.get('/api/write', (req, res) => {
39 | const q = req.query
40 | // URL 매개변수로 받은 값을 DB에 저장합니다.
41 | db.insert({
42 | name: q.name,
43 | body: q.body,
44 | stime: (new Date()).getTime()
45 | }, (err, doc) => {
46 | if (err) {
47 | console.error(err)
48 | sendJSON(res, false, {msg: err})
49 | return
50 | }
51 | sendJSON(res, true, {id: doc._id})
52 | })
53 | })
54 | function sendJSON (res, result, obj) {
55 | obj['result'] = result
56 | res.json(obj)
57 | }
--------------------------------------------------------------------------------
/ch5/bbs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bbs",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node bbs-server.js",
8 | "build": "webpack",
9 | "watch": "webpack -w"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "express": "^4.15.3",
16 | "nedb": "^1.8.0",
17 | "superagent": "^3.5.2"
18 | },
19 | "devDependencies": {
20 | "babel-core": "^6.24.1",
21 | "babel-loader": "^7.0.0",
22 | "babel-preset-es2015": "^6.24.1",
23 | "babel-preset-react": "^6.24.1",
24 | "react": "^15.5.4",
25 | "react-dom": "^15.5.4",
26 | "webpack": "^2.6.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ch5/bbs/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch5/bbs/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | module.exports = {
3 | entry: path.join(__dirname, 'src/index.js'),
4 | output: {
5 | path: path.join(__dirname, 'public'),
6 | filename: 'bundle.js'
7 | },
8 | devtool: 'inline-source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /.js$/,
13 | loader: 'babel-loader',
14 | options: {
15 | presets: ['es2015', 'react']
16 | }
17 | }
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ch5/chat/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/chat/.DS_Store
--------------------------------------------------------------------------------
/ch5/chat/chat-server.js:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------
2 | // 실시간 채팅 서버
3 | // --------------------------------------------------------
4 | // HTTP 서버 생성(애플리케이션 전송 전용) --- (※1)
5 | const express = require('express')
6 | const app = express()
7 | const server = require('http').createServer(app)
8 | const portNo = 3001
9 | server.listen(portNo, () => {
10 | console.log('서버 실행 완료:', 'http://localhost:' + portNo)
11 | })
12 | // public 디렉터리를 공개합니다. --- (※2)
13 | app.use('/public', express.static('./public'))
14 | app.get('/', (req, res) => { // 루트에 접근하면 /public로 리다이렉트
15 | res.redirect(302, '/public')
16 | })
17 | // 웹 소켓 서버를 실행합니다. --- (※3)
18 | const socketio = require('socket.io')
19 | const io = socketio.listen(server)
20 | // 클라이언트가 접속했을 때의 이벤트 설정 --- (※4)
21 | io.on('connection', (socket) => {
22 | console.log('사용자 접속:', socket.client.id)
23 | // 메시지를 받으면 --- (※5)
24 | socket.on('chat-msg', (msg) => {
25 | console.log('message:', msg)
26 | // 모든 클라이언트에게 전송 --- (※6)
27 | io.emit('chat-msg', msg)
28 | })
29 | })
--------------------------------------------------------------------------------
/ch5/chat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "chat-server.js",
6 | "scripts": {
7 | "start": "node chat-server.js",
8 | "build": "webpack",
9 | "watch": "webpack -w"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "express": "^4.15.3",
16 | "react": "^15.5.4",
17 | "react-dom": "^15.5.4",
18 | "socket.io-client": "^2.0.2",
19 | "socketio": "^1.0.0"
20 | },
21 | "devDependencies": {
22 | "babel-core": "^6.24.1",
23 | "babel-loader": "^7.0.0",
24 | "babel-preset-es2015": "^6.24.1",
25 | "babel-preset-react": "^6.24.1",
26 | "webpack": "^2.6.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ch5/chat/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | チャット
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ch5/chat/src/styles.js:
--------------------------------------------------------------------------------
1 | const styles = {
2 | h1: {
3 | backgroundColor: 'red',
4 | color: 'white',
5 | padding: 12
6 | },
7 | form: {
8 | border: '1px solid gray',
9 | padding: 12
10 | },
11 | log: {
12 | borderBottom: '1px solid silver',
13 | padding: 6,
14 | margin: 6
15 | },
16 | name: {
17 | float: 'left',
18 | width: 100,
19 | color: 'blue',
20 | textAlign: 'right'
21 | },
22 | msg: {
23 | float: 'left'
24 | }
25 | }
26 |
27 | export default styles
28 |
29 |
--------------------------------------------------------------------------------
/ch5/chat/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | module.exports = {
3 | entry: path.join(__dirname, 'src/index.js'),
4 | output: {
5 | path: path.join(__dirname, 'public'),
6 | filename: 'bundle.js'
7 | },
8 | devtool: 'inline-source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /.js$/,
13 | loader: 'babel-loader',
14 | options: {
15 | presets: ['es2015', 'react']
16 | }
17 | }
18 | ]
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ch5/express_test/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/express_test/.DS_Store
--------------------------------------------------------------------------------
/ch5/express_test/dice-ex.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const portNo = 3000
4 | // 루트에 접근할 때
5 | app.get('/', (req, res) => {
6 | res.send(
7 | '6면체 주사위
' +
8 | '12면체 주사위
')
9 | })
10 | // 주사위 페이지에 접근할 때 --- (※1)
11 | app.get('/dice/:num', (req, res) => {
12 | res.send('주사위의 값은...' + dice(req.params.num))
13 | })
14 | function dice(n) {
15 | return Math.floor(Math.random() * n) + 1
16 | }
17 | app.listen(portNo, () => {
18 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
19 | })
--------------------------------------------------------------------------------
/ch5/express_test/dice-q.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const portNo = 3000
4 | // 루트에 접근할 때
5 | app.get('/', (req, res) => {
6 | if (!req.query.q) {
7 | res.send(
8 | '6면체 주사위
' +
9 | '12면체 주사위
')
10 | } else {
11 | const q = parseInt(req.query.q, 10)
12 | res.send(
13 | '주사위의 값은...' + dice(q))
14 | }
15 | })
16 | function dice(n) {
17 | return Math.floor(Math.random() * n) + 1
18 | }
19 | app.listen(portNo, () => {
20 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
21 | })
--------------------------------------------------------------------------------
/ch5/express_test/dice.js:
--------------------------------------------------------------------------------
1 | // 익스프레스 모듈을 읽어 들입니다.
2 | const express = require('express')
3 | const app = express()
4 | const portNo = 3000
5 | // URL에 따라 처리 분기하기
6 | // 루트에 접근할 때
7 | app.get('/', (req, res) => {
8 | res.send(
9 | '6면체 주사위
' +
10 | '12면체 주사위
')
11 | })
12 | // 주사위 페이지에 겁근할 때
13 | app.get('/dice/6', (req, res) => {
14 | res.send('주사위의 값은...' + dice(6))
15 | })
16 | app.get('/dice/12', (req, res) => {
17 | res.send('주사위의 값은...' + dice(12))
18 | })
19 | function dice(n) {
20 | return Math.floor(Math.random() * n) + 1
21 | }
22 | // 서버를 실행합니다.
23 | app.listen(portNo, () => {
24 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
25 | })
--------------------------------------------------------------------------------
/ch5/express_test/hello.js:
--------------------------------------------------------------------------------
1 | // 익스프레스 모듈을 읽어 들입니다. --- (※1)
2 | const express = require('express')
3 | const app = express()
4 | const portNo = 3000
5 | // 접근이 있을 때 --- (※2)
6 | app.get('/', (req, res, next) => {
7 | res.send('Hello World!')
8 | })
9 | // 서버를 실행합니다. --- (※3)
10 | app.listen(portNo, () => {
11 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
12 | })
--------------------------------------------------------------------------------
/ch5/express_test/html/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/express_test/html/.DS_Store
--------------------------------------------------------------------------------
/ch5/express_test/html/fuga/index.html:
--------------------------------------------------------------------------------
1 | fuga
2 |
3 |
--------------------------------------------------------------------------------
/ch5/express_test/html/hoge.html:
--------------------------------------------------------------------------------
1 | hoge
2 |
3 |
--------------------------------------------------------------------------------
/ch5/express_test/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | This is static file.
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch5/express_test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "express_test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "body-parser": "^1.17.1",
14 | "express": "^4.15.2",
15 | "multer": "^1.3.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ch5/express_test/post-show.js:
--------------------------------------------------------------------------------
1 | // 익스프레스를 실행합니다.
2 | const express = require('express')
3 | const app = express()
4 | // body-parser를 사용합니다.
5 | const bodyParser = require('body-parser')
6 | app.use(bodyParser.urlencoded({extended: true}))
7 | app.listen(3000, () => {
8 | console.log('서버 실행 완료 - http://localhost:3000')
9 | })
10 | // GET 메서드라면 입력 양식을 응답합니다.
11 | app.get('/', (req, res) => {
12 | res.send('')
16 | })
17 | // POST 메서드를 받습니다.
18 | app.post('/', (req, res) => {
19 | const s = JSON.stringify(req.body)
20 | res.send('POST: ' + s)
21 | })
--------------------------------------------------------------------------------
/ch5/express_test/post-test.js:
--------------------------------------------------------------------------------
1 | // 익스프레스를 실행합니다.
2 | const express = require('express')
3 | const app = express()
4 | app.listen(3000, () => {
5 | console.log('서버 실행 완료 - http://localhost:3000')
6 | })
7 | // GET 메서드라면 입력 양식을 응답합니다.
8 | app.get('/', (req, res) => {
9 | res.send('')
13 | })
14 | // POST 메서드를 받습니다.
15 | app.post('/', (req, res) => {
16 | res.send('POST되었습니다.')
17 | })
--------------------------------------------------------------------------------
/ch5/express_test/post-upload.js:
--------------------------------------------------------------------------------
1 | // 익스프레스를 실행합니다.
2 | const express = require('express')
3 | const app = express()
4 | // multer 사용 준비 --- (※1)
5 | const multer = require('multer')
6 | const path = require('path')
7 | // 업로드 대상 디렉터리를 지정합니다. --- (※2)
8 | const tmpDir = path.join(__dirname, 'tmp')
9 | const pubDir = path.join(__dirname, 'pub')
10 | const uploader = multer({dest: tmpDir})
11 | // 서버를 실행합니다.
12 | app.listen(3000, () => {
13 | console.log('서버 실행 완료 - http://localhost:3000')
14 | })
15 | // 업로드 입력 양식을 출력합니다. --- (※3)
16 | app.get('/', (req, res) => {
17 | res.send(
18 | '')
22 | })
23 | // 정적 파일을 제공합니다. --- (※4)
24 | app.use('/pub', express.static(pubDir))
25 | // 업로드를 받습니다. --- (※5)
26 | app.post('/', uploader.single('aFile'), (req, res) => {
27 | console.log('파일을 받았습니다.')
28 | console.log('원본 파일 이름:', req.file.originalname)
29 | console.log('저장된 경로:', req.file.path)
30 | // MIME으로 파일의 형식을 확인합니다. --- (※6)
31 | if (req.file.mimetype !== 'image/png') {
32 | res.send('PNG 이미지만 업로드할 수 있습니다.')
33 | return
34 | }
35 | // TODO: 진짜 PNG인지 확인하게 해보세요. --- (※7)
36 | // 파일을 이동합니다. --- (※8)
37 | const fname = req.file.filename + '.png'
38 | const des = pubDir + '/' + fname
39 | const fs = require('fs')
40 | fs.rename(req.file.path, des)
41 | // HTML을 출력합니다.
42 | res.send('다음과 같은 파일이 업로드 됐습니다.
' +
43 | `
`)
44 | })
--------------------------------------------------------------------------------
/ch5/express_test/pub/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/express_test/pub/.DS_Store
--------------------------------------------------------------------------------
/ch5/express_test/static.js:
--------------------------------------------------------------------------------
1 | // 익스프레스를 실행합니다.
2 | const express = require('express')
3 | const app = express()
4 | // 서버를 실행합니다.
5 | app.listen(3000, () => {
6 | console.log('서버 실행 완료 - http://localhost:3000')
7 | })
8 | // 정적 파일을 제공합니다.
9 | app.use('/', express.static('./html'))
--------------------------------------------------------------------------------
/ch5/flux_test/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/flux_test/.DS_Store
--------------------------------------------------------------------------------
/ch5/flux_test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch5/flux_test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flux_test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "serve .",
7 | "watch": "webpack -w",
8 | "build": "webpack"
9 | },
10 | "dependencies": {
11 | "flux": "^3.1.2",
12 | "react": "^15.5.4",
13 | "react-dom": "^15.5.4"
14 | },
15 | "devDependencies": {
16 | "babel-core": "^6.24.1",
17 | "babel-loader": "^6.4.1",
18 | "babel-preset-es2015": "^6.24.1",
19 | "babel-preset-react": "^6.24.1",
20 | "css-loader": "^0.28.0",
21 | "serve": "^5.1.5",
22 | "style-loader": "^0.16.1",
23 | "webpack": "^2.4.1"
24 | },
25 | "keywords": [],
26 | "author": "",
27 | "license": "ISC"
28 | }
29 |
--------------------------------------------------------------------------------
/ch5/flux_test/src/actions.js:
--------------------------------------------------------------------------------
1 | import {appDispatcher} from './appDispatcher.js'
2 | // 이번에 사용할 Action
3 | export const ActionType = {
4 | CHANGE_NAME: 'CHANGE_NAME',
5 | SUBMIT_NAME: 'SUBMIT_NAME'
6 | }
7 | // Action을 생성하고 ... Dispatcher에 정보를 전달합니다.
8 | export const Actions = {
9 | changeName: (name) => {
10 | if (name === null) return
11 | appDispatcher.dispatch({
12 | actionType: ActionType.CHANGE_NAME,
13 | value: name})
14 | },
15 | submitName: () => {
16 | appDispatcher.dispatch({
17 | actionType: ActionType.SUBMIT_NAME})
18 | }
19 | }
--------------------------------------------------------------------------------
/ch5/flux_test/src/appDispatcher.js:
--------------------------------------------------------------------------------
1 | import {Dispatcher} from 'flux'
2 | // Dispatcher를 생성합니다.
3 | export const appDispatcher = new Dispatcher()
--------------------------------------------------------------------------------
/ch5/flux_test/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import {Actions} from './actions.js'
4 | import {nameStore, messageStore} from './stores.js'
5 | // View를 정의합니다.
6 | class AppView extends React.Component {
7 | constructor (props) {
8 | super(props)
9 | this.state = {name: '', message: ''}
10 | // View와 Store를 연결합니다.
11 | nameStore.onChange = () => {
12 | this.setState({name: nameStore.name})
13 | }
14 | messageStore.onChange = () => {
15 | this.setState({message: messageStore.message})
16 | }
17 | }
18 | // View에서는 Action을 던집니다.
19 | render () {
20 | console.log('View.render')
21 | return (
22 |
23 | Actions.changeName(e.target.value)} />
26 |
27 |
28 |
{this.state.message}
29 |
)
30 | }
31 | }
32 | // DOM의 내용을 변경합니다.
33 | ReactDOM.render(
34 | ,
35 | document.getElementById('root')
36 | )
--------------------------------------------------------------------------------
/ch5/flux_test/src/stores.js:
--------------------------------------------------------------------------------
1 | import {appDispatcher} from './appDispatcher.js'
2 | import {ActionType} from './actions.js'
3 | // 이번에 사용할 Store
4 | export const nameStore = {name: '', onChange: null}
5 | export const messageStore = {message: '', onChange: null}
6 | // Action와 Store를 연결합니다.
7 | appDispatcher.register(payload => {
8 | if (payload.actionType === ActionType.CHANGE_NAME) {
9 | nameStore.name = payload.value
10 | nameStore.onChange()
11 | }
12 | })
13 | appDispatcher.register(payload => {
14 | if (payload.actionType === ActionType.SUBMIT_NAME) {
15 | messageStore.message = nameStore.name + '님 안녕하세요.'
16 | messageStore.onChange()
17 | }
18 | })
--------------------------------------------------------------------------------
/ch5/flux_test/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const basedir = __dirname
3 |
4 | module.exports = {
5 | entry: path.join(basedir, 'src/index.js'),
6 | output: {
7 | path: path.join(basedir, 'out'),
8 | filename: 'bundle.js'
9 | },
10 | devtool: 'inline-source-map',
11 | module: {
12 | rules: [
13 | {
14 | test: /.js$/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['es2015', 'react']
18 | }
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ch5/nedb_test/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/nedb_test/.DS_Store
--------------------------------------------------------------------------------
/ch5/nedb_test/a.js:
--------------------------------------------------------------------------------
1 | const Datastore = require('nedb')
2 | const db = new Datastore({
3 | filename: 'test.db',
4 | autoload: true
5 | })
6 | db.loadDatabase((err) => {
7 | // const doc = { name: 'Taro', age: 30 }
8 | // db.insert(doc)
9 | //db.update({name: 'Taro'}, {age: 25}, {}, (err, num) => {
10 | // console.log(err, num)
11 | //})
12 | db.find({name: 'Taro'}, (err, docs) => {
13 | console.log(docs)
14 | })
15 | })
16 |
17 |
--------------------------------------------------------------------------------
/ch5/nedb_test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nedb_test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "nedb": "^1.8.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ch5/nedb_test/test.db:
--------------------------------------------------------------------------------
1 | {"age":25,"_id":"ZnSK8lyvULvsprHr"}
2 | {"age":25,"_id":"j4jPW1GlQ4CCL5tD"}
3 |
--------------------------------------------------------------------------------
/ch5/router_params/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/ch5/router_params/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "router_params",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4",
8 | "react-router-dom": "^4.1.1"
9 | },
10 | "devDependencies": {
11 | "react-scripts": "1.0.7"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ch5/router_params/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/router_params/public/favicon.ico
--------------------------------------------------------------------------------
/ch5/router_params/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ch5/router_params/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ch5/router_params/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/ch5/router_params/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | class App extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |

11 |
Welcome to React
12 |
13 |
14 | To get started, edit src/App.js
and save to reload.
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/ch5/router_params/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/ch5/router_params/src/CustomerApp.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | BrowserRouter as Router,
4 | Route,
5 | Link,
6 | Switch
7 | } from 'react-router-dom'
8 | // 사용자 정보
9 | const users = [
10 | {id: 1, name: '윤인성', info: '웹 개발 부서 개발자'},
11 | {id: 2, name: '연하진', info: '웹 개발 부서 기획자'},
12 | {id: 3, name: '윤명월', info: '웹 개발 부서 디자이너'}
13 | ]
14 | // 메인 컴포넌트 정의 --- (※1)
15 | const CustomerApp = () => (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | // 사용자 목록을 출력하는 컴포넌트 --- (※2)
28 | class UserList extends React.Component {
29 | render () {
30 | const ulist = users.map(e => (
31 |
32 | {e.name}
33 |
34 | ))
35 | return ()
36 | }
37 | }
38 | // 사용자 상세 정보를 출력하는 컴포넌트 --- (※3)
39 | class UserCard extends React.Component {
40 | render () {
41 | const {params} = this.props.match
42 | const id = parseInt(params.id, 10)
43 | const user = users.filter(e => e.id === id)[0]
44 | return (
45 |
46 |
{id}: {user.name} - {user.info}
47 |
→뒤로가기
48 |
49 | )
50 | }
51 | }
52 | export default CustomerApp
--------------------------------------------------------------------------------
/ch5/router_params/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ch5/router_params/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import CustomerApp from './CustomerApp';
4 | import registerServiceWorker from './registerServiceWorker';
5 | import './index.css';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/ch5/router_test/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/ch5/router_test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "router_test",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4"
8 | },
9 | "devDependencies": {
10 | "react-scripts": "1.0.7"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
--------------------------------------------------------------------------------
/ch5/router_test/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/router_test/public/favicon.ico
--------------------------------------------------------------------------------
/ch5/router_test/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ch5/router_test/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ch5/router_test/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/ch5/router_test/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | class App extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |

11 |
Welcome to React
12 |
13 |
14 | To get started, edit src/App.js
and save to reload.
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/ch5/router_test/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/ch5/router_test/src/HelloApp.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | BrowserRouter as Router,
4 | Route,
5 | Link
6 | } from 'react-router-dom'
7 | // 리액트 라우터를 사용해 메인 컴포넌트를 정의합니다. --- (※1)
8 | const HelloApp = () => (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | )
18 | // 홈 화면에 출력할 컴포넌트를 정의합니다. --- (※2)
19 | const Home = () => (
20 |
21 |
Hello App
22 |
언어를 선택해주세요.
23 |
28 |
29 | )
30 | // 한국어 출력 컴포넌트를 정의합니다. --- (※3)
31 | const HelloKorean = () => (
32 |
33 |
안녕하세요
34 |
뒤로가기
35 |
36 | )
37 | // 일본어 출력 컴포넌트를 정의합니다. --- (※4)
38 | const HelloJapanese = () => (
39 |
43 | )
44 | // 영어 출력 컴포넌트를 정의합니다. --- (※5)
45 | const HelloEnglish = () => (
46 |
47 |
Hello
48 |
Back
49 |
50 | )
51 | export default HelloApp
--------------------------------------------------------------------------------
/ch5/router_test/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ch5/router_test/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import HelloApp from './HelloApp';
4 | import registerServiceWorker from './registerServiceWorker';
5 | import './index.css';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/ch5/router_test2/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/ch5/router_test2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "router_test2",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4",
8 | "react-router-dom": "^4.1.1"
9 | },
10 | "devDependencies": {
11 | "react-scripts": "1.0.7"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ch5/router_test2/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch5/router_test2/public/favicon.ico
--------------------------------------------------------------------------------
/ch5/router_test2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ch5/router_test2/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ch5/router_test2/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/ch5/router_test2/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | class App extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |

11 |
Welcome to React
12 |
13 |
14 | To get started, edit src/App.js
and save to reload.
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/ch5/router_test2/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/ch5/router_test2/src/HelloApp2.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | BrowserRouter as Router,
4 | Route,
5 | Link
6 | } from 'react-router-dom'
7 | // 리액트 라우터를 사용하는 메인 컴포넌트를 정의합니다. --- (※1)
8 | const HelloApp2 = () => (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | // 고정 헤더를 정의합니다. --- (※2)
23 | const HelloHeader = () => (
24 |
25 |
HelloApp v2
26 |
27 | [한국어]
28 | [일본어]
29 | [영어]
30 |
31 |
32 | )
33 | // 고정 푸터를 정의합니다.
34 | const HelloFooter = () => (
35 |
36 | 인사를 다양한 언어로 출력하는 애플리케이션입니다.
37 |
38 | )
39 | // 한국어 출력 컴포넌트를 정의합니다. --- (※3)
40 | const HelloKorean = () => (
41 | 안녕하세요
42 | )
43 | // 일본어 출력 컴포넌트를 정의합니다. --- (※4)
44 | const HelloJapanese = () => (
45 | こんにちは
46 | )
47 | // 영어 출력 컴포넌트를 정의합니다. --- (※5)
48 | const HelloEnglish = () => (
49 | Hello
50 | )
51 | // 스타일을 정의합니다.
52 | const styleHeader = {
53 | backgroundColor: 'orange',
54 | color: 'white',
55 | padding: 8
56 | }
57 | export default HelloApp2
--------------------------------------------------------------------------------
/ch5/router_test2/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ch5/router_test2/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import HelloApp2 from './HelloApp2';
4 | import registerServiceWorker from './registerServiceWorker';
5 | import './index.css';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/ch6/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/.DS_Store
--------------------------------------------------------------------------------
/ch6/handwriting/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/handwriting/.DS_Store
--------------------------------------------------------------------------------
/ch6/handwriting/1-download.js:
--------------------------------------------------------------------------------
1 | // 메인 처리(다운로드) --- (※1)
2 | (async () => {
3 | const path = require('path')
4 | const base = 'http://yann.lecun.com/exdb/mnist'
5 | await download(
6 | base + '/t10k-images-idx3-ubyte.gz',
7 | path.join(__dirname, 'database', 'images-idx3'))
8 | await download(
9 | base + '/t10k-labels-idx1-ubyte.gz',
10 | path.join(__dirname, 'database', 'labels-idx1'))
11 | })()
12 | // 다운로드하고 압축 풀기 --- (※2)
13 | async function download (url, savepath) {
14 | console.log('다운로드 시작:', url)
15 | const tmp = savepath + '.gz'
16 | await downloadPromise(url, tmp)
17 | await gunzip(tmp, savepath)
18 | console.log('ok:', savepath)
19 | }
20 | // 파일 다운로드하기 --- (※3)
21 | function downloadPromise (url, savepath) {
22 | return new Promise((resolve, reject) => {
23 | const http = require('http')
24 | const fs = require('fs')
25 | if (fs.existsSync(savepath)) return resolve()
26 | const outfile = fs.createWriteStream(savepath)
27 | http.get(url, (res) => {
28 | res.pipe(outfile)
29 | res.on('end', () => {
30 | outfile.close()
31 | resolve()
32 | })
33 | })
34 | .on('error', (err) => reject(err))
35 | })
36 | }
37 | // 파일 압축 해제하기 --- (※4)
38 | function gunzip (infile, outfile) {
39 | return new Promise((resolve, reject) => {
40 | const zlib = require('zlib')
41 | const fs = require('fs')
42 | const rd = fs.readFileSync(infile)
43 | zlib.gunzip(rd, (err, bin) => {
44 | if (err) reject(err)
45 | fs.writeFileSync(outfile, bin)
46 | resolve()
47 | })
48 | })
49 | }
--------------------------------------------------------------------------------
/ch6/handwriting/2-conv2csv.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | // 변환
4 | convertToCSV(path.join(__dirname, 'database'))
5 | function convertToCSV (dbdir) {
6 | // 파일 이름 지정
7 | const imgFile = path.join(dbdir, 'images-idx3')
8 | const lblFile = path.join(dbdir, 'labels-idx1')
9 | const csvFile = path.join(dbdir, 'images.csv')
10 | // 파일 열기 --- (※1)
11 | const imgF = fs.openSync(imgFile, 'r')
12 | const lblF = fs.openSync(lblFile, 'r')
13 | const outF = fs.openSync(csvFile, 'w+')
14 | // 이미지 데이터베이스의 헤더 읽기 --- (※2)
15 | const ibuf = Buffer.alloc(16)
16 | fs.readSync(imgF, ibuf, 0, ibuf.length)
17 | const magic = ibuf.readUInt32BE(0)
18 | const numImages = ibuf.readUInt32BE(4)
19 | const numRows = ibuf.readUInt32BE(8)
20 | const numCols = ibuf.readUInt32BE(12)
21 | const numPixels = numRows * numCols
22 | // 헤더 검증
23 | if (magic !== 2051) throw new Error('파일에 문제가 있습니다.')
24 | console.log('이미지 수 =', numImages, numRows, 'x', numCols)
25 | // 레이블 데이터의 헤더 검증 --- (※3)
26 | const lbuf = Buffer.alloc(8)
27 | fs.readSync(lblF, lbuf, 0, lbuf.length)
28 | const magicl = lbuf.readUInt32BE(0)
29 | if (magicl !== 2049) throw new Error('파일에 문제가 있습니다.')
30 | // 이미지 추출하기 --- (※4)
31 | const pixels = Buffer.alloc(numPixels)
32 | const labelb = Buffer.alloc(1)
33 | for (let i = 0; i < numImages; i++) {
34 | // 진행률 출력하기
35 | if (i % 1000 === 0) console.log(i, '/', numImages)
36 | // 이미지 읽기 --- (※5)
37 | fs.readSync(imgF, pixels, 0, numPixels)
38 | fs.readSync(lblF, labelb, 0, 1)
39 | const line = new Uint8Array(pixels)
40 | const label = labelb.readInt8(0)
41 | // PGM 형식으로 저장 --- (※6)
42 | if (i < 20) {
43 | let s = 'P2 28 28 255\n'
44 | for (let j = 0; j < numPixels; j++) {
45 | s += line[j].toString()
46 | s += (j % 28 === 27) ? '\n' : ' '
47 | }
48 | const savefile = path.join(dbdir, label + '_test_' + i + '.pgm')
49 | fs.writeFileSync(savefile, s, 'utf-8')
50 | }
51 | // CSV 한 줄 만들기 --- (※7)
52 | const csvline = label + ',' + line.join(',') + '\n'
53 | fs.writeSync(outF, csvline, 'utf-8')
54 | }
55 | console.log('ok')
56 | }
--------------------------------------------------------------------------------
/ch6/handwriting/3-split.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | // CSV 파일 열기
4 | const csv = fs.readFileSync(
5 | path.join(__dirname, 'database', 'images.csv'),
6 | 'utf-8')
7 | // 줄바꿈으로 자르고 임의 숫자로 셔플 --- (※1)
8 | const a = csv.split('\n')
9 | const shuffle = () => Math.random() - 0.5
10 | const b = a.sort(shuffle)
11 | // 2000개, 500개로 분류하기
12 | const c1 = b.slice(0, 2000)
13 | const c2 = b.slice(2000, 2500)
14 | // 파일로 저장
15 | fs.writeFileSync('image-train.csv', c1.join('\n'))
16 | fs.writeFileSync('image-test.csv', c2.join('\n'))
17 | console.log('ok')
--------------------------------------------------------------------------------
/ch6/handwriting/4-train.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const svm = require('node-svm')
4 | // CSV 파일 읽어 들이기 --- (※1)
5 | const data = loadCSV('image-train.csv')
6 | // node-svm으로 데이터 학습시키기 --- (※2)
7 | const clf = new svm.CSVC()
8 | clf
9 | .train(data)
10 | .progress(progress => {
11 | console.log('학습: %d%', Math.round(progress * 100))
12 | })
13 | .spread((model, report) => {
14 | // 학습 데이터 저장 --- (※3)
15 | const json = JSON.stringify(model)
16 | fs.writeFileSync(
17 | path.join(__dirname, 'database', 'image-model.svm'),
18 | json)
19 | console.log('완료')
20 | })
21 | // CSV 파일을 읽어 들이고 node-svm 형식으로 변환하기 --- (※4)
22 | function loadCSV (fname) {
23 | const csv = fs.readFileSync(fname, 'utf-8')
24 | const lines = csv.split('\n')
25 | const data = lines.map(line => {
26 | const cells = line.split(',')
27 | const x = cells.map(v => parseInt(v))
28 | const label = x.shift()
29 | return [x, label]
30 | })
31 | return data
32 | }
--------------------------------------------------------------------------------
/ch6/handwriting/5-test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const svm = require('node-svm')
4 | // 학습된 데이터 읽어 들이기 --- (※1)
5 | const json = fs.readFileSync(
6 | path.join(__dirname, 'database', 'image-model.svm'),
7 | 'utf-8')
8 | const model = JSON.parse(json)
9 | const clf = svm.restore(model)
10 | // 테스트 데이터 읽어 들이기 --- (※2)
11 | const testData = loadCSV('image-test.csv')
12 | // 테스트해서 오류 비율 구하기 --- (※3)
13 | let count = 0
14 | let ng = 0
15 | testData.forEach(ex => {
16 | const x = ex[0]
17 | const label = ex[1]
18 | const pre = clf.predictSync(x)
19 | if (label !== pre) {
20 | ng++
21 | console.log('ng=', label, pre)
22 | }
23 | count++
24 | })
25 | console.log('오류 비율 =', (ng / count) * 100)
26 | // CSV 파일 읽어 들이기 --- (※4)
27 | function loadCSV (fname) {
28 | const csv = fs.readFileSync(fname, 'utf-8')
29 | const lines = csv.split('\n')
30 | const data = lines.map(line => {
31 | const cells = line.split(',')
32 | const x = cells.map(v => parseInt(v))
33 | const label = x.shift()
34 | return [x, label]
35 | })
36 | return data
37 | }
--------------------------------------------------------------------------------
/ch6/handwriting/6-server.js:
--------------------------------------------------------------------------------
1 | // 문자 인식 서버
2 | const path = require('path')
3 | const fs = require('fs')
4 | // 상수 정의
5 | const SVM_MODEL = path.join(__dirname, 'database', 'image-model.svm')
6 | const portNo = 3001 // 서버의 포트
7 |
8 | // 웹 서버 실행
9 | const express = require('express')
10 | const app = express()
11 | app.listen(portNo, () => {
12 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
13 | })
14 | // 학습 모델 읽어 들이기 --- (※1)
15 | const svm = require('node-svm')
16 | const modelJSON = fs.readFileSync(SVM_MODEL, 'utf-8')
17 | const model = JSON.parse(modelJSON)
18 | const clf = svm.restore(model)
19 | // API를 정의합니다. --- (※2)
20 | app.get('/api/predict', (req, res) => {
21 | const px = req.query.px
22 | if (!px) {
23 | res.json({status: false})
24 | return
25 | }
26 | const pxa = px.split('').map(v => parseInt('0x' + v) * 16)
27 | console.log('데이터:', pxa.join(':'))
28 | clf.predict(pxa).then((label) => {
29 | res.json({status: true, label})
30 | console.log('분류:', label)
31 | })
32 | })
33 | // 정적 파일 제공
34 | app.use('/', express.static('./public'))
--------------------------------------------------------------------------------
/ch6/handwriting/database/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/handwriting/database/.DS_Store
--------------------------------------------------------------------------------
/ch6/handwriting/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tegaki",
3 | "version": "1.0.0",
4 | "main": "6-server.js",
5 | "scripts": {
6 | "start": "node 6-server.js",
7 | "watch": "webpack -w",
8 | "build": "webpack"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "express": "^4.15.3",
15 | "node-svm": "^2.1.8",
16 | "react": "^15.5.4",
17 | "react-dom": "^15.5.4",
18 | "superagent": "^3.5.2"
19 | },
20 | "devDependencies": {
21 | "babel-core": "^6.25.0",
22 | "babel-loader": "^7.0.0",
23 | "babel-preset-es2015": "^6.24.1",
24 | "babel-preset-react": "^6.24.1",
25 | "webpack": "^2.6.1"
26 | },
27 | "description": ""
28 | }
29 |
--------------------------------------------------------------------------------
/ch6/handwriting/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 文字認識
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ch6/handwriting/src/styles.js:
--------------------------------------------------------------------------------
1 | // styles.js
2 | const styles = {
3 | app: {
4 | textAlign: 'center'
5 | },
6 | canvas: {
7 | width: 280,
8 | height: 280,
9 | border: '1px solid silver'
10 | },
11 | predict: {
12 | fontSize: 24,
13 | backgroundColor: 'yellow'
14 | }
15 | }
16 | export default styles
17 |
18 |
--------------------------------------------------------------------------------
/ch6/handwriting/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | module.exports = {
3 | entry: path.join(__dirname, 'src/index.js'),
4 | output: {
5 | path: path.join(__dirname, 'public'),
6 | filename: 'bundle.js'
7 | },
8 | devtool: 'inline-source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /.js$/,
13 | loader: 'babel-loader',
14 | options: {
15 | presets: ['es2015', 'react']
16 | }
17 | }
18 | ]
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ch6/sns/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/sns/.DS_Store
--------------------------------------------------------------------------------
/ch6/sns/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sns",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node sns-server.js",
8 | "watch": "webpack -w",
9 | "build": "webpack"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "express": "^4.15.3",
16 | "nedb": "^1.8.0",
17 | "react": "^15.5.4",
18 | "react-dom": "^15.5.4",
19 | "react-router-dom": "^4.1.1",
20 | "superagent": "^3.5.2"
21 | },
22 | "devDependencies": {
23 | "babel-core": "^6.24.1",
24 | "babel-loader": "^7.0.0",
25 | "babel-preset-es2015": "^6.24.1",
26 | "babel-preset-react": "^6.24.1",
27 | "webpack": "^2.6.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ch6/sns/public/default.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | border-bottom: 2px solid #c0c0ff;
3 | padding: 8px;
4 | background-color: #f0f0ff;
5 | }
6 |
7 | h2 {
8 | border-bottom: 2px solid #c0c0ff;
9 | padding: 8px;
10 | background-color: #f0f0ff;
11 | }
12 |
13 | input {
14 | width: 280px;
15 | background-color: #fff0f0;
16 | }
17 |
18 | button {
19 | }
20 |
21 | blockquote {
22 | background-color: #f0f0ff;
23 | border-left: 2px dotted #c0c0ff;
24 | margin-left: 2em;
25 | padding: 8px;
26 | }
27 |
28 | textarea {
29 | font-size: 1em;
30 | padding: 8px;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/ch6/sns/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WIKI
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ch6/sns/public/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/sns/public/user.png
--------------------------------------------------------------------------------
/ch6/sns/server/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/sns/server/.DS_Store
--------------------------------------------------------------------------------
/ch6/sns/server/timeline.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/sns/server/timeline.db
--------------------------------------------------------------------------------
/ch6/sns/server/user.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/sns/server/user.db
--------------------------------------------------------------------------------
/ch6/sns/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import {
4 | BrowserRouter as Router,
5 | Route, Switch
6 | } from 'react-router-dom'
7 | import SNSUsers from './sns_users'
8 | import SNSTimeline from './sns_timeline'
9 | import SNSLogin from './sns_login'
10 | const SNSApp = () => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | // DOM의 내용을 메인 컴포넌트로 변경합니다.
23 | ReactDOM.render(
24 | ,
25 | document.getElementById('root'))
--------------------------------------------------------------------------------
/ch6/sns/src/sns_login.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import request from 'superagent'
3 | import {Redirect} from 'react-router-dom'
4 | import styles from './styles'
5 | // 로그인 화면 컴포넌트
6 | export default class SNSLogin extends Component {
7 | constructor (props) {
8 | super(props)
9 | this.state = { userid: '', passwd: '', jump: '', msg: '' }
10 | }
11 | // API를 호출하고 응답받은 토큰을 localStorage에 저장하기 --- (※1)
12 | api (command) {
13 | request
14 | .get('/api/' + command)
15 | .query({
16 | userid: this.state.userid,
17 | passwd: this.state.passwd
18 | })
19 | .end((err, res) => {
20 | if (err) return
21 | const r = res.body
22 | console.log(r)
23 | if (r.status && r.token) {
24 | // 인증 토큰을 localStorage에 저장하기
25 | window.localStorage['sns_id'] = this.state.userid
26 | window.localStorage['sns_auth_token'] = r.token
27 | this.setState({jump: '/timeline'})
28 | return
29 | }
30 | this.setState({msg: r.msg})
31 | })
32 | }
33 | render () {
34 | if (this.state.jump) {
35 | return
36 | }
37 | const changed = (name, e) => this.setState({[name]: e.target.value})
38 | return (
39 |
40 |
로그인
41 |
42 | 사용자 ID:
43 |
changed('userid', e)} />
45 | 비밀번호:
46 |
changed('passwd', e)} />
48 |
49 |
{this.state.msg}
50 |
52 |
53 |
54 | )
55 | }
56 | }
--------------------------------------------------------------------------------
/ch6/sns/src/sns_timeline.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import request from 'superagent'
3 | import styles from './styles'
4 | // 타임라인 화면을 정의하는 컴포넌트
5 | export default class SNSTimeline extends Component {
6 | constructor (props) {
7 | super(props)
8 | this.state = { timelines: [], comment: '' }
9 | }
10 | componentWillMount () {
11 | this.loadTimelines()
12 | }
13 | loadTimelines () { // 타임라인 추출 --- (※1)
14 | request
15 | .get('/api/get_friends_timeline')
16 | .query({
17 | userid: window.localStorage.sns_id,
18 | token: window.localStorage.sns_auth_token
19 | })
20 | .end((err, res) => {
21 | if (err) return
22 | this.setState({timelines: res.body.timelines})
23 | })
24 | }
25 | post () { // 자신의 타임라인에 글쓰기 --- (※2)
26 | request
27 | .get('/api/add_timeline')
28 | .query({
29 | userid: window.localStorage.sns_id,
30 | token: window.localStorage.sns_auth_token,
31 | comment: this.state.comment
32 | })
33 | .end((err, res) => {
34 | if (err) return
35 | this.setState({comment: ''})
36 | this.loadTimelines()
37 | })
38 | }
39 | render () {
40 | // 타임라인 데이터를 하나씩 생성 --- (※3)
41 | const timelines = this.state.timelines.map(e => {
42 | return (
43 |
44 |

45 |
{e.userid}:
46 |
{e.comment}
47 |
48 |
49 | )
50 | })
51 | return (
52 |
53 |
타임라인
54 |
55 | this.setState({comment: e.target.value})} />
57 |
58 |
59 |
{timelines}
60 |
61 |
→친구 추가하기
62 |
→다른 사용자로 로그인
63 |
64 | )
65 | }
66 | }
--------------------------------------------------------------------------------
/ch6/sns/src/styles.js:
--------------------------------------------------------------------------------
1 | const styles = {
2 | error: {
3 | color: 'red'
4 | },
5 | login: {
6 | width: 300,
7 | textAlign: 'left',
8 | marginLeft: 30,
9 | padding: 15
10 | },
11 | timeline: {
12 | borderBottom: '1px solid silver',
13 | padding: 10,
14 | margin: 10
15 | },
16 | tl_img: {
17 | float: 'left',
18 | width: 32
19 | },
20 | userid: {
21 | float: 'left',
22 | color: 'gray',
23 | padding: 5,
24 | margin: 5,
25 | width: 70,
26 | textAlign: 'right'
27 | },
28 | comment: {
29 | float: 'left',
30 | padding: 5,
31 | margin: 5,
32 | width: 300
33 | },
34 | friend: {
35 | padding: 10,
36 | borderBottom: '1px solid silver'
37 | }
38 | }
39 | export default styles
40 |
--------------------------------------------------------------------------------
/ch6/sns/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | module.exports = {
3 | entry: path.join(__dirname, 'src/index.js'),
4 | output: {
5 | path: path.join(__dirname, 'public'),
6 | filename: 'bundle.js'
7 | },
8 | devtool: 'inline-source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /.js$/,
13 | loader: 'babel-loader',
14 | options: {
15 | presets: ['es2015', 'react']
16 | }
17 | }
18 | ]
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ch6/wiki/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/nodejs-react/3359e8b4ea8256db2f6fe0df6d994eb4413a9351/ch6/wiki/.DS_Store
--------------------------------------------------------------------------------
/ch6/wiki/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wiki",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node wiki-server.js",
8 | "watch": "webpack -w",
9 | "build": "webpack",
10 | "build:parser": "pegjs -o src/wiki_parser.js src/wiki_parser.pegjs"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "body-parser": "^1.17.2",
17 | "express": "^4.15.3",
18 | "nedb": "^1.8.0",
19 | "react": "^15.5.4",
20 | "react-dom": "^15.5.4",
21 | "react-router-dom": "^4.1.1",
22 | "superagent": "^3.5.2"
23 | },
24 | "devDependencies": {
25 | "babel-core": "^6.24.1",
26 | "babel-loader": "^7.0.0",
27 | "babel-preset-es2015": "^6.24.1",
28 | "babel-preset-react": "^6.24.1",
29 | "pegjs": "^0.10.0",
30 | "webpack": "^2.6.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ch6/wiki/public/default.css:
--------------------------------------------------------------------------------
1 | h2 {
2 | border-bottom: 2px solid #c0c0ff;
3 | padding: 8px;
4 | background-color: #f0f0ff;
5 | }
6 |
7 | blockquote {
8 | background-color: #f0f0ff;
9 | border-left: 2px dotted #c0c0ff;
10 | margin-left: 2em;
11 | padding: 8px;
12 | }
13 |
14 | textarea {
15 | font-size: 1em;
16 | padding: 8px;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/ch6/wiki/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WIKI
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ch6/wiki/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import {
4 | BrowserRouter as Router,
5 | Route, Switch
6 | } from 'react-router-dom'
7 | import WikiEdit from './wiki_edit'
8 | import WikiShow from './wiki_show'
9 | const WikiApp = () => (
10 |
11 |
12 |
13 |
14 |
15 |
16 | )
17 | // DOM의 내용을 메인 컴포넌트로 변경합니다.
18 | ReactDOM.render(
19 | ,
20 | document.getElementById('root'))
--------------------------------------------------------------------------------
/ch6/wiki/src/styles.js:
--------------------------------------------------------------------------------
1 | const styles = {
2 | show: {
3 | border: '1px solid gray',
4 | padding: 12
5 | },
6 | edit: {
7 | padding: 12,
8 | backgroundColor: 'silver'
9 | },
10 | right: {
11 | textAlign: 'right'
12 | }
13 | }
14 | export default styles
15 |
--------------------------------------------------------------------------------
/ch6/wiki/src/wiki_edit.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import request from 'superagent'
3 | import {Redirect} from 'react-router-dom'
4 | import styles from './styles'
5 | // 편집 화면 컴포넌트 --- (※1)
6 | export default class WikiEdit extends Component {
7 | // 컴포넌트 초기화 --- (※2)
8 | constructor (props) {
9 | super(props)
10 | const {params} = this.props.match
11 | const name = params.name // --- (※3)
12 | this.state = {
13 | name, body: '', loaded: false, jump: ''
14 | }
15 | }
16 | // 위키 내용 읽어 들이기 --- (※4)
17 | componentWillMount () {
18 | request
19 | .get(`/api/get/${this.state.name}`)
20 | .end((err, res) => {
21 | if (err) return
22 | this.setState({
23 | body: res.body.data.body,
24 | loaded: true
25 | })
26 | })
27 | }
28 | // 본문을 서버에 전송 --- (※5)
29 | save () {
30 | const wikiname = this.state.name
31 | request
32 | .post('/api/put/' + wikiname)
33 | .type('form')
34 | .send({
35 | name: wikiname,
36 | body: this.state.body
37 | })
38 | .end((err, data) => {
39 | if (err) {
40 | console.log(err)
41 | return
42 | }
43 | this.setState({jump: '/wiki/' + wikiname})
44 | })
45 | }
46 | bodyChanged (e) {
47 | this.setState({body: e.target.value})
48 | }
49 | // 편집 화면 출력 --- (※6)
50 | render () {
51 | if (!this.state.loaded) { // --- (※7)
52 | return (읽어 들이는 중
)
53 | }
54 | if (this.state.jump !== '') {
55 | // 메인 화면으로 리다이렉트 --- (※8)
56 | return
57 | }
58 | const name = this.state.name
59 | return (
60 |
61 |
62 |
67 | )
68 | }
69 | }
--------------------------------------------------------------------------------
/ch6/wiki/src/wiki_parser.pegjs:
--------------------------------------------------------------------------------
1 | start
2 | = sentence*
3 |
4 | sentence
5 | = title
6 | / list
7 | / blockquote
8 | / link
9 | / nop
10 | / text
11 | / eos
12 |
13 | title
14 | = "*" label:$(!EOL .)+ EOL
15 | { return {tag: "h2", label} }
16 |
17 | list
18 | = items:list_item+
19 | { return {tag:"ul", items} }
20 |
21 | list_item
22 | = "-" label:$([^\n]*) "\n"
23 | { return label }
24 |
25 | blockquote
26 | = ">" label:$(!EOL .)+ EOL
27 | { return {tag:"blockquote", label} }
28 |
29 | link
30 | = "@" label:$([^\n]+) "\n"
31 | { return {tag: "a", label} }
32 |
33 | text
34 | = label:$(!EOL .)+ EOL
35 | { return {tag: "p", label} }
36 |
37 | nop
38 | = "\n"+ { return {tag: "p", label:''} }
39 |
40 | eos
41 | = label:$(.+)
42 | { return {tag: "p", label} }
43 |
44 | EOL = "\n" "\n"
45 |
--------------------------------------------------------------------------------
/ch6/wiki/src/wiki_show.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import request from 'superagent'
3 | import WikiParser from './wiki_parser'
4 | import styles from './styles'
5 | // 위키 메인 화면 컴포넌트
6 | class WikiShow extends React.Component {
7 | constructor (props) {
8 | super(props)
9 | const {params} = this.props.match
10 | this.state = {
11 | name: params.name, body: '', loaded: false}
12 | }
13 | // 위키 내용을 읽어 들입니다. ---(※1)
14 | componentWillMount () {
15 | request
16 | .get(`/api/get/${this.state.name}`)
17 | .end((err, res) => {
18 | if (err) return
19 | this.setState({
20 | body: res.body.data.body,
21 | loaded: true
22 | })
23 | })
24 | }
25 | // 화면 출력 처리
26 | render () {
27 | if (!this.state.loaded) return (읽어 들이는 중
)
28 | const name = this.state.name
29 | const body = this.state.body
30 | const html = this.convertText(body)
31 | return (
32 |
33 |
{this.state.name}
34 |
{html}
35 |
36 | →페이지 수정하기
37 |
38 |
39 | )
40 | }
41 | // 위키 문법을 리액트 객체로 변환합니다. --- (※2)
42 | convertText (src) {
43 | // 위키 문법을 파서로 파싱합니다.
44 | const nodes = WikiParser.parse(src)
45 | // 각 요소를 React 객체로 변환합니다.
46 | const lines = nodes.map((e, i) => {
47 | if (e.tag === 'ul') { // 리스트
48 | const lis = e.items.map(
49 | (s, j) => {s}
50 | )
51 | return
52 | }
53 | if (e.tag === 'a') {
54 | return ()
57 | }
58 | return React.createElement(
59 | e.tag, {key: 'node' + i}, e.label)
60 | })
61 | return lines
62 | }
63 | }
64 | export default WikiShow
--------------------------------------------------------------------------------
/ch6/wiki/test-parser.js:
--------------------------------------------------------------------------------
1 | // 파서 읽어 들이기
2 | const WikiParser = require('./src/wiki_parser.js')
3 | // 파싱해보기
4 | const src = '*title\n\n-list1\n-list2\n\nfoo'
5 | const nodes = WikiParser.parse(src)
6 | console.log(nodes)
--------------------------------------------------------------------------------
/ch6/wiki/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | module.exports = {
3 | entry: path.join(__dirname, 'src/index.js'),
4 | output: {
5 | path: path.join(__dirname, 'public'),
6 | filename: 'bundle.js'
7 | },
8 | devtool: 'inline-source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /.js$/,
13 | loader: 'babel-loader',
14 | options: {
15 | presets: ['es2015', 'react']
16 | }
17 | }
18 | ]
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ch6/wiki/wiki-server.js:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------
2 | // 위키 웹 서버
3 | // --------------------------------------------------------
4 | // 데이터베이스에 접속합니다. --- (※1)
5 | const path = require('path')
6 | const NeDB = require('nedb')
7 | const db = new NeDB({
8 | filename: path.join(__dirname, 'wiki.db'),
9 | autoload: true
10 | })
11 | // 웹 서버를 실행합니다. --- (※2)
12 | const express = require('express')
13 | const app = express()
14 | const portNo = 3001
15 | // body-parser를 사용합니다.
16 | const bodyParser = require('body-parser')
17 | app.use(bodyParser.urlencoded({extended: true}))
18 | app.listen(portNo, () => {
19 | console.log('서버 실행 완료:', `http://localhost:${portNo}`)
20 | })
21 | // API를 정의합니다.
22 | // 위키 데이터를 응답하는 API --- (※3)
23 | app.get('/api/get/:wikiname', (req, res) => {
24 | const wikiname = req.params.wikiname
25 | db.find({name: wikiname}, (err, docs) => {
26 | if (err) {
27 | res.json({status: false, msg: err})
28 | return
29 | }
30 | if (docs.length === 0) {
31 | docs = [{name: wikiname, body: ''}]
32 | }
33 | res.json({status: true, data: docs[0]})
34 | })
35 | })
36 | // 위키 데이터를 작성하는 API --- (※4)
37 | app.post('/api/put/:wikiname', (req, res) => {
38 | const wikiname = req.params.wikiname
39 | console.log('/api/put/' + wikiname, req.body)
40 | // 기존에 존재하는 엔트리인지 확인합니다.
41 | db.find({'name': wikiname}, (err, docs) => {
42 | if (err) {
43 | res.json({status: false, msg: err})
44 | return
45 | }
46 | const body = req.body.body
47 | if (docs.length === 0) { // 기존에 엔트리가 없다면 삽입
48 | db.insert({name: wikiname, body})
49 | } else { // 기존에 엔트리가 있다면 수정
50 | db.update({name: wikiname}, {name: wikiname, body})
51 | }
52 | res.json({status: true})
53 | })
54 | })
55 | // public 디렉터리를 공개합니다. --- (※5)
56 | app.use('/wiki/:wikiname', express.static('./public'))
57 | app.use('/edit/:wikiname', express.static('./public'))
58 | app.get('/', (req, res) => {
59 | res.redirect(302, '/wiki/FrontPage')
60 | })
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "src",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "dependencies": {
7 | "react-scripts": "^0.9.5",
8 | "standard": "^10.0.2",
9 | "superagent": "^3.5.2"
10 | },
11 | "devDependencies": {
12 | "babel-core": "^6.24.1",
13 | "babel-loader": "^6.4.1",
14 | "babel-preset-es2015": "^6.24.1",
15 | "babel-preset-react": "^6.24.1",
16 | "css-loader": "^0.28.0",
17 | "react": "^15.5.4",
18 | "react-dom": "^15.5.4",
19 | "standard": "^10.0.2",
20 | "style-loader": "^0.16.1",
21 | "webpack": "^2.3.3"
22 | },
23 | "scripts": {
24 | "test": "echo \"Error: no test specified\" && exit 1"
25 | },
26 | "keywords": [],
27 | "author": "",
28 | "license": "ISC"
29 | }
30 |
--------------------------------------------------------------------------------