Woongjae Lee
NHN Dooray - Frontend Team
2woongjae@gmail.com
모든 정적 JavaScript 파일을 자동으로 gzip 압축
특히 클라우드 환경에 배포된 서버의 CPU 절약
작은 기본 번들 사이즈
동적 라우팅 처리
Link 컴포넌트의 prefatch 기능
이뮤터블 캐싱
css 솔루션 분리
react => preact 가능
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.
{
"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"
}
}
export default () => (
<div>
<h2>index.js</h2>
</div>
);
// functional 컴포넌트는 react 를 임포트 할 필요 없다.
// export default 에 리액트 컴포넌트를 매핑해야 한다.
import React from 'react';
export default class Hello extends React.Component {
render() {
return (
<div>
<h2>hello.js</h2>
</div>
);
}
}
{
"compilerOptions": {
"jsx": "react",
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
}
}
{
"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"
}
}
import * as React from 'react';
export default () => (
<div>
<h2>index.tsx</h2>
</div>
);
// ts 에서는 functional 컴포넌트에서도 react 를 임포트 해야 한다.
import * as React from 'react';
export default class Hello extends React.Component {
render() {
return (
<div>
<h2>hello.tsx</h2>
</div>
);
}
}
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>
);
}
}
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 이 단순 문자열이면 안된다.
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>
);
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 어트리뷰트가 제대로 정의되지 않음.
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
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>
);
{
"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"
}
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))
}
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)
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>
)
})
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
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)
{
"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"
}
}
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
}
}
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')
})
})
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>
)
}
}
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
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
By Woongjae Lee
타입스크립트 한국 유저 그룹 리액트 스터디 201706