React with TypeScript 5th

2woongjae@gmail.com

Next.js 2.0 (March 27th 2017)

Next.js

  • Framework for server-rendered React apps
    • 하나의 리액트 컴포넌트를 페이지로 표현해준다. 
      • Server-Side 와 Client-Side 둘다 같은 하나의 컴포넌트를 기반으로 함.
      • HTML 을 기반으로 하지 않다는 점에 주의
      • 별도의 스타일 지정 기술이 필요함.
      • 파일 이름이 경로 이름
        • hello.js => /hello
        • jsx 를 인식하지 않음.
      • 에러페이지 제공 및 설정 가능
    • ZEIT !!!!!!!!!
      • now, serve, pkg

Next.js

  • Gzip
    • 모든 정적 JavaScript 파일을 자동으로 gzip 압축

    • 특히 클라우드 환경에 배포된 서버의 CPU 절약

  • 작은 기본 번들 사이즈

  • 동적 라우팅 처리

  • Link 컴포넌트의 prefatch 기능

  • 이뮤터블 캐싱

  • css 솔루션 분리

  • react => preact 가능

Next.js 3.0 Roadmap

Next.js 3.0 Roadmap

  • Improved HMR
  • Faster Dev Compilation
    • 충분히 빠른걸 ?
    • 기본 타입스크립트 컴파일 (희망사항)
  • Seamless Support for Lazy `import()`
  • React Fiber
  • Static Exports

Quick Start !

next-quick-start (1)

  • mkdir next-quick-start
  • cd next-quick-start
  • yarn init -y
  • yarn add next react react-dom
    • ​예전엔 react, react-dom 이 내장
    • preact ?

yarn add next

D:\Project>mkdir next-quick-start


D:\Project>cd next-quick-start


D:\Project\next-quick-start>yarn init -y
yarn init v0.27.5
warning The yes flag has been set. This will automatically answer yes to all questions which may have security implications.
success Saved package.json
Done in 0.89s.


D:\Project\next-quick-start>yarn add next react react-dom
yarn add v0.27.5
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
warning fsevents@1.1.2: The platform "win32" is incompatible with this module.
info "fsevents@1.1.2" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 484 new dependencies.
├─ abab@1.0.3
├─ abbrev@1.1.0
├─ acorn-dynamic-import@2.0.2
├─ acorn-globals@3.1.0
├─ acorn@4.0.13
├─ ajv-keywords@1.5.1
├─ ajv@4.11.8
├─ align-text@0.1.4
├─ amdefine@1.0.1
├─ ansi-html@0.0.7
├─ ansi-regex@2.1.1
├─ ansi-styles@2.2.1
├─ any-promise@1.3.0
├─ anymatch@1.3.0
├─ arr-diff@2.0.0
├─ arr-flatten@1.1.0
├─ array-equal@1.0.0
├─ array-union@1.0.2
├─ array-uniq@1.0.3
├─ array-unique@0.2.1
├─ arrify@1.0.1
├─ asap@2.0.6
├─ asn1.js@4.9.1
├─ asn1@0.2.3
├─ assert-plus@1.0.0
├─ assert@1.4.1
├─ async-each@1.0.1
├─ async@2.5.0
├─ asynckit@0.4.0
├─ aws-sign2@0.6.0
├─ aws4@1.6.0
├─ babel-code-frame@6.22.0
├─ babel-core@6.24.0
├─ babel-generator@6.25.0
├─ babel-helper-builder-binary-assignment-operator-visitor@6.24.1
├─ babel-helper-builder-react-jsx@6.24.1
├─ babel-helper-call-delegate@6.24.1
├─ babel-helper-define-map@6.24.1
├─ babel-helper-explode-assignable-expression@6.24.1
├─ babel-helper-function-name@6.24.1
├─ babel-helper-get-function-arity@6.24.1
├─ babel-helper-hoist-variables@6.24.1
├─ babel-helper-optimise-call-expression@6.24.1
├─ babel-helper-regex@6.24.1
├─ babel-helper-remap-async-to-generator@6.24.1
├─ babel-helper-replace-supers@6.24.1
├─ babel-helpers@6.24.1
├─ babel-loader@7.0.0
├─ babel-messages@6.23.0
├─ babel-plugin-check-es2015-constants@6.22.0
├─ babel-plugin-module-resolver@2.6.2
├─ babel-plugin-react-require@3.0.0
├─ babel-plugin-syntax-async-functions@6.13.0
├─ babel-plugin-syntax-class-properties@6.13.0
├─ babel-plugin-syntax-exponentiation-operator@6.13.0
├─ babel-plugin-syntax-flow@6.18.0
├─ babel-plugin-syntax-jsx@6.18.0
├─ babel-plugin-syntax-object-rest-spread@6.13.0
├─ babel-plugin-syntax-trailing-function-commas@6.22.0
├─ babel-plugin-transform-async-to-generator@6.24.1
├─ babel-plugin-transform-class-properties@6.24.1
├─ babel-plugin-transform-es2015-arrow-functions@6.22.0
├─ babel-plugin-transform-es2015-block-scoped-functions@6.22.0
├─ babel-plugin-transform-es2015-block-scoping@6.24.1
├─ babel-plugin-transform-es2015-classes@6.24.1
├─ babel-plugin-transform-es2015-computed-properties@6.24.1
├─ babel-plugin-transform-es2015-destructuring@6.23.0
├─ babel-plugin-transform-es2015-duplicate-keys@6.24.1
├─ babel-plugin-transform-es2015-for-of@6.23.0
├─ babel-plugin-transform-es2015-function-name@6.24.1
├─ babel-plugin-transform-es2015-literals@6.22.0
├─ babel-plugin-transform-es2015-modules-amd@6.24.1
├─ babel-plugin-transform-es2015-modules-commonjs@6.24.1
├─ babel-plugin-transform-es2015-modules-systemjs@6.24.1
├─ babel-plugin-transform-es2015-modules-umd@6.24.1
├─ babel-plugin-transform-es2015-object-super@6.24.1
├─ babel-plugin-transform-es2015-parameters@6.24.1
├─ babel-plugin-transform-es2015-shorthand-properties@6.24.1
├─ babel-plugin-transform-es2015-spread@6.22.0
├─ babel-plugin-transform-es2015-sticky-regex@6.24.1
├─ babel-plugin-transform-es2015-template-literals@6.22.0
├─ babel-plugin-transform-es2015-typeof-symbol@6.23.0
├─ babel-plugin-transform-es2015-unicode-regex@6.24.1
├─ babel-plugin-transform-exponentiation-operator@6.24.1
├─ babel-plugin-transform-flow-strip-types@6.22.0
├─ babel-plugin-transform-object-rest-spread@6.22.0
├─ babel-plugin-transform-react-display-name@6.25.0
├─ babel-plugin-transform-react-jsx-self@6.22.0
├─ babel-plugin-transform-react-jsx-source@6.22.0
├─ babel-plugin-transform-react-jsx@6.24.1
├─ babel-plugin-transform-react-remove-prop-types@0.4.5
├─ babel-plugin-transform-regenerator@6.24.1
├─ babel-plugin-transform-runtime@6.22.0
├─ babel-plugin-transform-strict-mode@6.24.1
├─ babel-preset-env@1.3.3
├─ babel-preset-flow@6.23.0
├─ babel-preset-react@6.24.1
├─ babel-register@6.24.1
├─ babel-runtime@6.23.0
├─ babel-template@6.25.0
├─ babel-traverse@6.25.0
├─ babel-types@6.25.0
├─ babylon@6.17.4
├─ balanced-match@1.0.0
├─ base64-js@1.2.1
├─ bcrypt-pbkdf@1.0.1
├─ big.js@3.1.3
├─ binary-extensions@1.8.0
├─ bn.js@4.11.7
├─ boom@2.10.1
├─ brace-expansion@1.1.8
├─ braces@1.8.5
├─ brorand@1.1.0
├─ browser-env@2.0.31
├─ browserify-aes@1.0.6
├─ browserify-cipher@1.0.0
├─ browserify-des@1.0.0
├─ browserify-rsa@4.0.1
├─ browserify-sign@4.0.4
├─ browserify-zlib@0.1.4
├─ browserslist@1.7.7
├─ buffer-xor@1.0.3
├─ buffer@4.9.1
├─ builtin-modules@1.1.1
├─ builtin-status-codes@3.0.0
├─ camelcase@3.0.0
├─ caniuse-db@1.0.30000701
├─ case-sensitive-paths-webpack-plugin@2.0.0
├─ caseless@0.12.0
├─ center-align@0.1.3
├─ chalk@1.1.3
├─ chokidar@1.7.0
├─ cipher-base@1.0.4
├─ cliui@3.2.0
├─ co@4.6.0
├─ code-point-at@1.1.0
├─ combined-stream@1.0.5
├─ commondir@1.0.1
├─ concat-map@0.0.1
├─ console-browserify@1.1.0
├─ constants-browserify@1.0.0
├─ content-type-parser@1.0.1
├─ convert-source-map@1.5.0
├─ core-js@2.4.1
├─ core-util-is@1.0.2
├─ create-ecdh@4.0.0
├─ create-hash@1.1.3
├─ create-hmac@1.1.6
├─ create-react-class@15.6.0
├─ cross-spawn@5.1.0
├─ cryptiles@2.0.5
├─ crypto-browserify@3.11.1
├─ css-tree@1.0.0-alpha17
├─ cssom@0.3.2
├─ cssstyle@0.2.37
├─ dashdash@1.14.1
├─ date-now@0.1.4
├─ debug@2.6.8
├─ decamelize@1.2.0
├─ deep-is@0.1.3
├─ del@2.2.2
├─ delayed-stream@1.0.0
├─ depd@1.1.0
├─ des.js@1.0.0
├─ destroy@1.0.4
├─ detect-indent@4.0.0
├─ diffie-hellman@5.0.2
├─ dom-walk@0.1.1
├─ domain-browser@1.1.7
├─ ecc-jsbn@0.1.1
├─ ee-first@1.1.1
├─ electron-to-chromium@1.3.15
├─ elliptic@6.4.0
├─ emojis-list@2.1.0
├─ encodeurl@1.0.1
├─ encoding@0.1.12
├─ enhanced-resolve@3.3.0
├─ errno@0.1.4
├─ error-ex@1.3.1
├─ error-stack-parser@2.0.1
├─ escape-html@1.0.3
├─ escape-string-regexp@1.0.5
├─ escodegen@1.8.1
├─ esprima@2.7.3
├─ estraverse@1.9.3
├─ esutils@2.0.2
├─ etag@1.8.0
├─ events@1.1.1
├─ evp_bytestokey@1.0.0
├─ expand-brackets@0.1.5
├─ expand-range@1.8.2
├─ extend@3.0.1
├─ extglob@0.3.2
├─ extsprintf@1.0.2
├─ fast-levenshtein@2.0.6
├─ fbjs@0.8.12
├─ filename-regex@2.0.1
├─ filesize@3.5.10
├─ fill-range@2.2.3
├─ find-babel-config@1.1.0
├─ find-cache-dir@0.1.1
├─ find-up@1.1.2
├─ for-in@1.0.2
├─ for-own@0.1.5
├─ forever-agent@0.6.1
├─ form-data@2.1.4
├─ fresh@0.5.0
├─ friendly-errors-webpack-plugin@1.5.0
├─ fs.realpath@1.0.0
├─ get-caller-file@1.0.2
├─ getpass@0.1.7
├─ glob-base@0.3.0
├─ glob-parent@2.0.0
├─ glob-promise@3.1.0
├─ glob@7.1.2
├─ global@4.3.2
├─ globals@9.18.0
├─ globby@5.0.0
├─ graceful-fs@4.1.11
├─ har-schema@1.0.5
├─ har-validator@4.2.1
├─ has-ansi@2.0.0
├─ has-flag@1.0.0
├─ hash-base@2.0.2
├─ hash.js@1.1.3
├─ hawk@3.1.3
├─ hmac-drbg@1.0.1
├─ hoek@2.16.3
├─ home-or-tmp@2.0.0
├─ hosted-git-info@2.5.0
├─ html-encoding-sniffer@1.0.1
├─ html-entities@1.2.1
├─ htmlescape@1.1.1
├─ http-errors@1.4.0
├─ http-signature@1.1.1
├─ http-status@1.0.1
├─ https-browserify@0.0.1
├─ iconv-lite@0.4.18
├─ ieee754@1.1.8
├─ indexof@0.0.1
├─ inflight@1.0.6
├─ inherits@2.0.3
├─ interpret@1.0.3
├─ invariant@2.2.2
├─ invert-kv@1.0.0
├─ is-arrayish@0.2.1
├─ is-binary-path@1.0.1
├─ is-buffer@1.1.5
├─ is-builtin-module@1.0.0
├─ is-dotfile@1.0.3
├─ is-equal-shallow@0.1.3
├─ is-extendable@0.1.1
├─ is-extglob@1.0.0
├─ is-finite@1.0.2
├─ is-fullwidth-code-point@1.0.0
├─ is-glob@2.0.1
├─ is-number@2.1.0
├─ is-path-cwd@1.0.0
├─ is-path-in-cwd@1.0.0
├─ is-path-inside@1.0.0
├─ is-posix-bracket@0.1.1
├─ is-primitive@2.0.0
├─ is-stream@1.1.0
├─ is-typedarray@1.0.0
├─ is-utf8@0.2.1
├─ is-windows-bash@1.0.3
├─ isarray@1.0.0
├─ isexe@2.0.0
├─ isobject@2.1.0
├─ isomorphic-fetch@2.2.1
├─ isstream@0.1.2
├─ js-tokens@3.0.2
├─ jsbn@0.1.1
├─ jsdom@9.11.0
├─ jsesc@1.3.0
├─ json-loader@0.5.4
├─ json-schema@0.2.3
├─ json-stable-stringify@1.0.1
├─ json-stringify-safe@5.0.1
├─ json5@0.5.1
├─ jsonify@0.0.0
├─ jsprim@1.4.0
├─ kind-of@3.2.2
├─ lazy-cache@1.0.4
├─ lcid@1.0.0
├─ levn@0.3.0
├─ load-json-file@1.1.0
├─ loader-runner@2.3.0
├─ loader-utils@1.1.0
├─ locate-path@2.0.0
├─ lodash@4.17.4
├─ longest@1.0.1
├─ loose-envify@1.3.1
├─ lru-cache@4.1.1
├─ md5-file@3.1.1
├─ memory-fs@0.4.1
├─ micromatch@2.3.11
├─ miller-rabin@4.0.0
├─ mime-db@1.27.0
├─ mime-types@2.1.15
├─ mime@1.3.4
├─ min-document@2.19.0
├─ minimalistic-assert@1.0.0
├─ minimalistic-crypto-utils@1.0.1
├─ minimatch@3.0.4
├─ minimist@1.2.0
├─ mitt@1.1.1
├─ mkdirp-then@1.2.0
├─ mkdirp@0.5.1
├─ moment@2.18.1
├─ ms@2.0.0
├─ mv@2.1.1
├─ mz@2.6.0
├─ ncp@2.0.0
├─ next@2.4.7
├─ node-fetch@1.7.1
├─ node-libs-browser@2.0.0
├─ nopt@1.0.10
├─ normalize-package-data@2.4.0
├─ normalize-path@2.1.1
├─ number-is-nan@1.0.1
├─ nwmatcher@1.4.1
├─ oauth-sign@0.8.2
├─ object-assign@4.1.1
├─ object.omit@2.0.1
├─ on-finished@2.3.0
├─ once@1.4.0
├─ optionator@0.8.2
├─ os-browserify@0.2.1
├─ os-homedir@1.0.2
├─ os-locale@1.4.0
├─ os-tmpdir@1.0.2
├─ p-limit@1.1.0
├─ p-locate@2.0.0
├─ pako@0.2.9
├─ parse-asn1@5.1.0
├─ parse-glob@3.0.4
├─ parse-json@2.2.0
├─ parse5@1.5.1
├─ path-browserify@0.0.0
├─ path-exists@3.0.0
├─ path-is-absolute@1.0.1
├─ path-is-inside@1.0.2
├─ path-match@1.2.4
├─ path-parse@1.0.5
├─ path-to-regexp@1.7.0
├─ path-type@1.1.0
├─ pbkdf2@3.0.12
├─ performance-now@0.2.0
├─ pify@2.3.0
├─ pinkie-promise@2.0.1
├─ pinkie@2.0.4
├─ pkg-dir@1.0.0
├─ pkg-up@2.0.0
├─ prelude-ls@1.1.2
├─ preserve@0.2.0
├─ private@0.1.7
├─ process-nextick-args@1.0.7
├─ process@0.11.10
├─ promise@7.3.1
├─ prop-types@15.5.10
├─ prr@0.0.0
├─ pseudomap@1.0.2
├─ public-encrypt@4.0.0
├─ punycode@1.4.1
├─ qs@6.4.0
├─ querystring-es3@0.2.1
├─ querystring@0.2.0
├─ randomatic@1.1.7
├─ randombytes@2.0.5
├─ range-parser@1.2.0
├─ react-deep-force-update@2.0.1
├─ react-dom@15.6.1
├─ react-hot-loader@3.0.0-beta.7
├─ react-proxy@3.0.0-alpha.1
├─ react@15.6.1
├─ read-pkg-up@1.0.1
├─ read-pkg@1.1.0
├─ readable-stream@2.3.3
├─ readdirp@2.1.0
├─ redbox-react@1.4.3
├─ regenerate@1.3.2
├─ regenerator-runtime@0.10.5
├─ regenerator-transform@0.9.11
├─ regex-cache@0.4.3
├─ regexpu-core@2.0.0
├─ regjsgen@0.2.0
├─ regjsparser@0.1.5
├─ remove-trailing-separator@1.0.2
├─ repeat-element@1.1.2
├─ repeat-string@1.6.1
├─ repeating@2.0.1
├─ request@2.81.0
├─ require-directory@2.1.1
├─ require-main-filename@1.0.1
├─ resolve@1.3.3
├─ right-align@0.1.3
├─ rimraf@2.6.1
├─ ripemd160@2.0.1
├─ safe-buffer@5.1.1
├─ sax@1.2.4
├─ semver@5.3.0
├─ send@0.15.2
├─ set-blocking@2.0.0
├─ set-immediate-shim@1.0.1
├─ setimmediate@1.0.5
├─ setprototypeof@1.0.3
├─ sha.js@2.4.8
├─ shebang-command@1.2.0
├─ shebang-regex@1.0.0
├─ slash@1.0.0
├─ sntp@1.0.9
├─ source-list-map@1.1.2
├─ source-map-support@0.4.15
├─ source-map@0.5.6
├─ sourcemapped-stacktrace@1.1.6
├─ spdx-correct@1.0.2
├─ spdx-expression-parse@1.0.4
├─ spdx-license-ids@1.2.2
├─ sshpk@1.13.1
├─ stackframe@1.0.3
├─ statuses@1.3.1
├─ stream-browserify@2.0.1
├─ stream-http@2.7.2
├─ string_decoder@0.10.31
├─ string-hash@1.1.1
├─ string-length@1.0.1
├─ string-width@1.0.2
├─ stringstream@0.0.5
├─ strip-ansi@3.0.1
├─ strip-bom@2.0.0
├─ styled-jsx@1.0.6
├─ stylis@3.1.5
├─ supports-color@3.2.3
├─ symbol-tree@3.2.2
├─ tapable@0.2.6
├─ thenify-all@1.6.0
├─ thenify@3.3.0
├─ timers-browserify@2.0.2
├─ to-arraybuffer@1.0.1
├─ to-fast-properties@1.0.3
├─ touch@1.0.0
├─ tough-cookie@2.3.2
├─ tr46@0.0.3
├─ trim-right@1.0.1
├─ tty-browserify@0.0.0
├─ tunnel-agent@0.6.0
├─ tweetnacl@0.14.5
├─ type-check@0.3.2
├─ ua-parser-js@0.7.13
├─ uglify-js@2.8.29
├─ uglify-to-browserify@1.0.2
├─ unfetch@2.1.2
├─ url@0.11.0
├─ util-deprecate@1.0.2
├─ util@0.10.3
├─ uuid@3.0.1
├─ validate-npm-package-license@3.0.1
├─ verror@1.3.6
├─ vm-browserify@0.0.4
├─ watchpack@1.4.0
├─ webidl-conversions@4.0.1
├─ webpack-dev-middleware@1.10.2
├─ webpack-hot-middleware@2.18.0
├─ webpack-sources@0.2.3
├─ webpack@2.5.1
├─ whatwg-encoding@1.0.1
├─ whatwg-fetch@2.0.3
├─ whatwg-url@4.8.0
├─ which-module@1.0.0
├─ which@1.2.14
├─ window-size@0.1.0
├─ window@3.1.5
├─ wordwrap@0.0.2
├─ wrap-ansi@2.1.0
├─ wrappy@1.0.2
├─ write-file-webpack-plugin@4.0.2
├─ xml-name-validator@2.0.1
├─ xss-filters@1.2.7
├─ xtend@4.0.1
├─ y18n@3.2.1
├─ yallist@2.1.2
├─ yargs-parser@4.2.1
└─ yargs@6.6.0
Done in 44.82s.

next-quick-start (2)

  • edit package.json
  • mkdir pages

package.json

{
  "name": "next-quick-start",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "next": "^2.4.7",
    "react": "^15.6.1",
    "react-dom": "^15.6.1"
  },
  "scripts" :{
    "dev": "next",
    "start": "next start", // build 가 된 후, 사용해야 한다.
    "build": "next build"
  }
}

mkdir pages

yarn dev (npm run dev)

next-quick-start (3)

  • pages/index.js
    • / 경로에 해당하는 페이지
  • pages/hello.js
    • /hello 경로에 해당하는 페이지
  • .js (O), .jsx (X)

pages/index.js

export default () => (
    <div>
        <h2>index.js</h2>
    </div>    
);

// functional 컴포넌트는 react 를 임포트 할 필요 없다.
// export default 에 리액트 컴포넌트를 매핑해야 한다.

pages/hello.js

import React from 'react';

export default class Hello extends React.Component {
    render() {
        return (
            <div>
                <h2>hello.js</h2>
            </div>
        );
    }
}

TypeScript Setting

Basic Setting

  • 컴파일을 해서 pages 안에 경로명의 .js
    • 디버깅의 문제 ??
    • sourceMap 을 추출해도 next 의 런타임은 그걸 상관하지 않음.
    • webpack 설정을 next.config.js 를 통해 커스터마이징 가능
      • 하지만, .js 를 페이지 라우팅의 기본으로 하는 문제로 인해
      • ts-loader 를 사용하는 것은 불가능 (현재까지는...)
  • yarn add @types/react
  • yarn add typescript -D
  • tsc -w 를 concurrently 로 함께 돌려서 사용하거나...

tsconfig.json

{
    "compilerOptions": {
        "jsx": "react",
        "target": "es2015",
        "module": "commonjs",
        "moduleResolution": "node",
        "forceConsistentCasingInFileNames": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "suppressImplicitAnyIndexErrors": true,
        "noUnusedLocals": true
    }
}

package.json

{
  "name": "next-quick-start",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@types/react": "^15.0.38",
    "next": "^2.4.7",
    "react": "^15.6.1",
    "react-dom": "^15.6.1"
  },
  "devDependencies": {
    "typescript": "^2.4.1"
  }
  "scripts" :{
    "dev": "next",
    "start": "next start", // build 가 된 후, 사용해야 한다.
    "build": "tsc && next build",
    "transpile": "tsc",
    "devwatch": "tsc -w"
  }
}

pages/index.tsx

import * as React from 'react';

export default () => (
    <div>
        <h2>index.tsx</h2>
    </div>
);

// ts 에서는 functional 컴포넌트에서도 react 를 임포트 해야 한다.

pages/hello.tsx

import * as React from 'react';

export default class Hello extends React.Component {
    render() {
        return (
            <div>
                <h2>hello.tsx</h2>
            </div>
        );
    }
}

static getInitialProps(context)

getInitialProps

  • static 메소드
    • 호출되는 context 객체를 활용
      • 라우팅 정보
      • 서버와 클라이언트를 구분할때 쓰임.
  • async 가능
    • 비동기 처리 선처리 가능
  • 이런 현재는 context 객체에 대한 type definition 이 없음
    • pathname
    • query
    • asPath
    • req, res - 서버 온리
    • jsonPageRes - 클라이언트 온리
    • err

pages/hello.tsx

import * as React from 'react';
import Links from '../components/Links';
import * as request from 'superagent';

interface HelloProps {
    pathname: string;
    isServer: boolean;
    persons: number;
}

interface InitialContext {
    pathname: string;
    query: object;
    asPath: any;
    req?: any;
    res?: any;
    jsonPageRes?: any;
    err: any;
}

export default class Hello extends React.Component<HelloProps, {}> {
    public static async getInitialProps(context: InitialContext): Promise<HelloProps> {
        console.log(context.pathname);
        console.log(context.query);
        console.log(context.asPath);

        const res = await request.get('https://api.github.com/users');

        console.log(res.body.length);

        if (context.req) {
            return {
                pathname: context.pathname,
                isServer: true,
                persons: res.body.length
            };
        } else {
            return {
                pathname: context.pathname,
                isServer: false,
                persons: res.body.length
            }
        }
    }
    render() {
        return (
            <div>
                <h2>hello.tsx</h2>
                <Links />
                <p>{this.props.pathname}</p>
                <p>{(this.props.isServer) ? '이 페이지는 서버로부터 랜더링 되었습니다.' : '이 페이지는 클라이언트에서 랜더링 되었습니다.'}</p>
                <p>{this.props.persons}</p>
            </div>
        );
    }
}

/hello & /hello?test=test

  • pathname
    • /hello
    • /hello
  • query
    • {}
    • { test: 'test' }
  • asPath
    • /hello
    • /hello?test=test

Link from 'next/link'

components/Links.tsx

import * as React from 'react';
import Link from 'next/link';

export default () => (
    <div>
        <ul>
            <li><Link href="/"><a>/</a></Link></li>
            <li><Link href="/hello"><a>/hello</a></Link></li>
            <li><Link href="/hello?test=test"><a>/hello?test=test</a></Link></li>
        </ul>
    </div>
);

// Link 의 children 이 단순 문자열이면 안된다.

새로고침 - 브라우저

새로고침 - 커맨드라인

Link 클릭 - 브라우저

Link prefetch

import * as React from 'react';
import Link from 'next/link';

export default () => (
    <div>
        <ul>
            <li><Link href="/"><a>/</a></Link></li>
            <li><Link prefetch href="/hello"><a>/hello</a></Link></li>
            <li><Link prefetch href="/hello?test=test"><a>/hello?test=test</a></Link></li>
        </ul>
    </div>
);

Style ?

style 처리

  • js, tsx 파일에서 style 을 번들링 해주지 않는다.
  • next/css 를 제공하다가 분리
    • 쓰고 싶은것 쓰라는 뜻?
    • styled-jsx
      • ZEIT 꺼
      • 특별한 설치가 필요하지 않다.
      • 에러 있음
    • styled-components (or glamorous)
      • 서버와 클라이언트가 다르게 인식되는 문제
    • typestyle

pages/styledJsx.tsx

import * as React from 'react';
import Links from '../components/Links';

export default () => (
    <div>
        <h2>index.tsx</h2>
        <style jsx>{`
            h2 {
                color: red;
            }
            li {
                color: blue;
            }
        `}</style>
        <Links />
    </div>
);

// style jsx 어트리뷰트가 제대로 정의되지 않음.

pages/styled-components.tsx

import * as React from 'react';
import Links from '../components/Links';
import styled from 'styled-components';

const StyledH2 = styled.h2`
    color: red;
`;

export default () => (
    <div>
        <StyledH2>index.tsx</StyledH2>
        <Links />
    </div>
);

// type definition 내장
// Next 에서 이슈가 있음. 서버와 클라이언트 체크섬이 상이한 문제
// https://www.styled-components.com/docs/advanced#server-side-rendering

styled-components - 서버 랜더링 시

pages/typeStyle.tsx

import * as React from 'react';
import Links from '../components/Links';
import {style} from 'typestyle';

const h2Style = style({
    color: 'red'
});

export default () => (
    <div>
        <h2 className={h2Style}>typeStyle.tsx</h2>
        <Links />
    </div>
);

https://github.com/2woongjae/next-quick-start

Next with Redux

with-redux

{
  "name": "with-redux",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "latest",
    "next-redux-wrapper": "^1.0.0",
    "react": "^15.4.2",
    "react-dom": "^15.4.2",
    "react-redux": "^5.0.1",
    "redux": "^3.6.0",
    "redux-thunk": "^2.1.0"
  },
  "author": "",
  "license": "ISC"
}

with-redux

with-redux

간단 store.js

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'

// 스토어에 사용할 초기 스테이트 값
const exampleInitialState = {
  lastUpdate: 0,
  light: false,
  count: 0
}

// 액션의 타입 정의 
export const actionTypes = {
  ADD: 'ADD',
  TICK: 'TICK'
}

// 리듀서
export const reducer = (state = exampleInitialState, action) => {
  switch (action.type) {
    case actionTypes.TICK:
      return Object.assign({}, state, { lastUpdate: action.ts, light: !!action.light })
    case actionTypes.ADD:
      return Object.assign({}, state, {
        count: state.count + 1
      })
    default: return state
  }
}

// 액션 크리에이터 함수
export const serverRenderClock = (isServer) => dispatch => {
  return dispatch({ type: actionTypes.TICK, light: !isServer, ts: Date.now() })
}
export const startClock = () => dispatch => {
  return setInterval(() => dispatch({ type: 'TICK', light: true, ts: Date.now() }), 800)
}
export const addCount = () => dispatch => {
  return dispatch({ type: actionTypes.ADD })
}

// 스토어 생성부
export const initStore = (initialState = exampleInitialState) => {
  return createStore(reducer, initialState, applyMiddleware(thunkMiddleware))
}

pages/index.js

import React from 'react'
import { bindActionCreators } from 'redux'
import { initStore, startClock, addCount, serverRenderClock } from '../store'
import withRedux from 'next-redux-wrapper'
import Page from '../components/Page'

class Counter extends React.Component {
  static getInitialProps ({ store, isServer }) {
    store.dispatch(serverRenderClock(isServer))
    store.dispatch(addCount())

    return { isServer }
  }

  componentDidMount () {
    this.timer = this.props.startClock()
  }

  componentWillUnmount () {
    clearInterval(this.timer)
  }

  render () {
    return (
      <Page title='Index Page' linkTo='/other' />
    )
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    addCount: bindActionCreators(addCount, dispatch),
    startClock: bindActionCreators(startClock, dispatch)
  }
}

export default withRedux(initStore, null, mapDispatchToProps)(Counter)

components/Page.js

import Link from 'next/link'
import { connect } from 'react-redux'
import Clock from './Clock'
import AddCount from './AddCount'

export default connect(state => state)(({ title, linkTo, lastUpdate, light }) => {
  return (
    <div>
      <h1>{title}</h1>
      <Clock lastUpdate={lastUpdate} light={light} />
      <AddCount />
      <nav>
        <Link href={linkTo}><a>Navigate</a></Link>
      </nav>
    </div>
  )
})

components/Clock.js - Dumb

export default ({ lastUpdate, light }) => {
  return (
    <div className={light ? 'light' : ''}>
      {format(new Date(lastUpdate))}
      <style jsx>{`
        div {
          padding: 15px;
          display: inline-block;
          color: #82FA58;
          font: 50px menlo, monaco, monospace;
          background-color: #000;
        }

        .light {
          background-color: #999;
        }
      `}</style>
    </div>
  )
}

const format = t => `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}`

const pad = n => n < 10 ? `0${n}` : n

components/AddCount.js

import React, {Component} from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addCount } from '../store'

class AddCount extends Component {
  add = () => {
    this.props.addCount()
  }

  render () {
    const { count } = this.props
    return (
      <div>
        <style jsx>{`
          div {
            padding: 0 0 20px 0;
          }
      `}</style>
        <h1>AddCount: <span>{count}</span></h1>
        <button onClick={this.add}>Add To Count</button>
      </div>
    )
  }
}

const mapStateToProps = ({ count }) => ({ count })

const mapDispatchToProps = (dispatch) => {
  return {
    addCount: bindActionCreators(addCount, dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(AddCount)

절차

  • next-redux-wrapper 패키지를 통해, 페이지의 디폴트 컴퍼넌트를 만든다.

    • 서버사이드 랜더링을 하면, 스토어를 초기값으로 생성

      • 실제 페이지 컴퍼넌트에 연결

    • 클라이언트 랜더링을 하면, 스토어를 유지하여

      • 이동할 페이지 컴퍼넌트에 연결

    • 컨테이너가 필요없다.

      • 페이지에는 next-redux-wrapper 를 연결

      • 하위 컴포넌트에는 connect 함수를 이용 (Smart)

      • props 만 받아서 사용 (Dumb)

Next with MobX

with-mobx

{
  "name": "with-mobx",
  "version": "1.0.0",
  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  },
  "dependencies": {
    "mobx": "^2.7.0",
    "mobx-react": "^4.0.4",
    "next": "latest",
    "react": "^15.4.2",
    "react-dom": "^15.4.2"
  },
  "license": "ISC",
  "devDependencies": {
    "babel-plugin-transform-decorators-legacy": "^1.3.4"
  }
}

store.js

import { action, observable } from 'mobx'

let store = null

class Store {
  @observable lastUpdate = 0
  @observable light = false

  constructor (isServer, lastUpdate) {
    this.lastUpdate = lastUpdate
  }

  @action start = () => {
    this.timer = setInterval(() => {
      this.lastUpdate = Date.now()
      this.light = true
    })
  }

  stop = () => clearInterval(this.timer)
}

export function initStore (isServer, lastUpdate = Date.now()) {
  if (isServer && typeof window === 'undefined') {
    return new Store(isServer, lastUpdate)
  } else {
    if (store === null) {
      store = new Store(isServer, lastUpdate)
    }
    return store
  }
}

useStaticRendering

server.js

const dev = process.env.NODE_ENV !== 'production'

const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const mobxReact = require('mobx-react')
const app = next({ dev })
const handle = app.getRequestHandler()

mobxReact.useStaticRendering(true)

app.prepare().then(() => {
  createServer((req, res) => {
    const parsedUrl = parse(req.url, true)
    handle(req, res, parsedUrl)
  }).listen(3000, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

pages/index.js

import React from 'react'
import { Provider } from 'mobx-react'
import { initStore } from '../store'
import Page from '../components/Page'

export default class Counter extends React.Component {
  static getInitialProps ({ req }) {
    const isServer = !!req
    const store = initStore(isServer)
    return { lastUpdate: store.lastUpdate, isServer }
  }

  constructor (props) {
    super(props)
    this.store = initStore(props.isServer, props.lastUpdate)
  }

  render () {
    return (
      <Provider store={this.store}>
        <Page title='Index Page' linkTo='/other' />
      </Provider>
    )
  }
}

components/Page.js

import React from 'react'
import Link from 'next/link'
import { inject, observer } from 'mobx-react'
import Clock from './Clock'

@inject('store') @observer
class Page extends React.Component {
  componentDidMount () {
    this.props.store.start()
  }

  componentWillUnmount () {
    this.props.store.stop()
  }

  render () {
    return (
      <div>
        <h1>{this.props.title}</h1>
        <Clock lastUpdate={this.props.store.lastUpdate} light={this.props.store.light} />
        <nav>
          <Link href={this.props.linkTo}><a>Navigate</a></Link>
        </nav>
      </div>
    )
  }
}

export default Page

components/Clock.js

export default (props) => {
  return (
    <div className={props.light ? 'light' : ''}>
      {format(new Date(props.lastUpdate))}
      <style jsx>{`
        div {
          padding: 15px;
          color: #82FA58;
          display: inline-block;
          font: 50px menlo, monaco, monospace;
          background-color: #000;
        }
        .light {
          background-color: #999;
        }
      `}</style>
    </div>
  )
}

const format = t => `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}`

const pad = n => n < 10 ? `0${n}` : n

React with TypeScript (5) - nextjs

By Woongjae Lee

React with TypeScript (5) - nextjs

타입스크립트 한국 유저 그룹 리액트 스터디 201706

  • 1,578