優勢:javascript
<title>首頁標題</title>
<meta name="description" content="首頁描述"></meta>
複製代碼
缺點:css
npx create-next-app project-name
複製代碼
查看 package.jsonhtml
{
"name": "next-demo-one",
"version": "0.1.0",
"private": true,
"scripts": {
// 默認端口 3000,想要修改端口用 -p
"dev": "next dev -p 4000",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "9.1.4",
"react": "16.12.0",
"react-dom": "16.12.0"
}
}
複製代碼
<head>
標籤的內容,至關於 react-helmetimport Head from 'next/head'
export default () =>
<div> <Head> <title>My page title</title> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> </Head> <p>Hello world!</p> </div> 複製代碼
getInitialProps
內部,而不是放在組件的生命週期裏,須要遵循它的規範。getInitialProps
入參對象的屬性以下:
pathname
- URL 的 path 部分query
- URL 的 query 部分,並被解析成對象asPath
- 顯示在瀏覽器中的實際路徑(包含查詢部分),爲 String
類型req
- HTTP 請求對象 (只有服務器端有)res
- HTTP 返回對象 (只有服務器端有)jsonPageRes
- 獲取數據響應對象 (只有客戶端有)err
- 渲染過程當中的任何錯誤getInitialProps
只會在服務端被調用。只有當路由跳轉(Link
組件跳轉或 API 方法跳轉)時,客戶端纔會執行 getInitialProps
。在線demogetInitialProps
纔會被調用,子組件使用 getInitialProps
是無效的
getInitialProps
方法,獲取返回的數據做爲 props
傳入到該路由組件中,最後渲染該路由組件。在線demofunction PageA(props){
const {childOneData,childTwoData} = props;
return <div> <ChildOne childOneData/> <ChildTwo childTwoData/> </div>;
}
PageA.getInitialProps = async ()=>{
// 在父組件中的 getInitialProps 方法裏,調用接口獲取子組件所須要的數據
const childOneData = await getPageAChildOneData();
const childTwoData = await getPageAChildTwoData();
return {childOneData, childTwoData}
};
複製代碼
getInitialProps
方法獲取數據pages
目錄下的 .js
文件都是一級路由pages
目錄新建一個文件夾Link
組件,默認不會渲染出任何內容(如 a
標籤),須要指定渲染內容,而且內部必須有一個頂層元素,不能同時出現兩個兄弟元素。它只是監聽了咱們指定內容的 click
事件,而後跳轉到指定的路徑import Link from 'next/link'
const Index = () => {
return (
<> <Link href="/a?id=1"> <div> <Button>AAA</Button> <Button>BBB</Button> </div> </Link> </> ) }; 複製代碼
params
,動態路由只能經過 query
實現import Router from 'next/router'
import Link from 'next/link'
const Index = () => {
// 經過 API 跳轉
function gotoTestB() {
Router.push(
{
pathname: '/test/b',
query: {
id: 2,
},
}
)
}
return (
<> <Link href="/test/b?id=1" > <Button>BBB</Button> </Link> </> ) }; 複製代碼
/test/id
,而不是 /test?id=123456
),能夠用路由映射import Router from 'next/router'
import Link from 'next/link'
const Index = () => {
// 經過 API 跳轉
function gotoTestB() {
Router.push(
{
pathname: '/test/b',
query: {
id: 2,
},
},
'/test/b/2',
)
}
return (
<> <Link href="/test/b?id=1" as="/test/b/1" > <div> <Button>BBB</Button> </div> </Link> </> ) }; 複製代碼
const Koa = require('koa');
const Router = require('koa-router');
const next = require('next');
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
router.get('/a/:id', async ctx => {
const id = ctx.params.id;
await handle(ctx.req, ctx.res, {
pathname: '/a',
query: { id },
});
});
server.listen(3000, () => {
console.log('koa server listening on 3000')
});
}
複製代碼
import Router from 'next/router'
Router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as !== "/" || as !== "/other") {
// Have SSR render bad routes as a 404.
window.location.href = as
// 返回 false,Router 將不會執行 popstate 事件
return false
}
return true
});
複製代碼
routeChangeStart(url)
- 路由開始切換時觸發routeChangeComplete(url)
- 完成路由切換時觸發routeChangeError(err, url)
- 路由切換報錯時觸發beforeHistoryChange(url)
- 瀏覽器 history
模式開始切換時觸發hashChangeStart(url)
- 開始切換 hash
值可是沒有切換頁面路由時觸發hashChangeComplete(url)
- 完成切換 hash
值可是沒有切換頁面路由時觸發url
是指顯示在瀏覽器中的 url
。若是你使用了路由映射,那瀏覽器中的 url
將會顯示 as
的值import React from 'react';
import Router from 'next/router'
class User extends React.Component {
handleRouteChange = url => {
console.log('url=> ', url);
};
componentDidMount() {
Router.events.on('routeChangeStart', (res) => {
console.log(res);
});
Router.events.on('routeChangeComplete', (res) => {
console.log(res);
});
Router.events.on('routeChangeError', (res) => {
console.log(res);
});
}
componentWillUnmount() {
Router.events.off('routeChangeStart', (res) => {
console.log(res);
});
Router.events.off('routeChangeComplete', (res) => {
console.log(res);
});
Router.events.off('routeChangeError', (res) => {
console.log(res);
});
}
render() {
return <div>User </div>;
}
}
複製代碼
const A = ({ router, name}) => {
return (
<> <Link href="#aaa"> <a className="link"> A {router.query.id} {name} </a> </Link> <style jsx>{` a { color: blue; } .link { color: ${color}; } `}</style> </> ) }; 複製代碼
import { withRouter } from 'next/router'
import dynamic from 'next/dynamic'
import Link from 'next/link'
const LazyComp = dynamic(import('../components/lazy-comp'));
const A = ({time }) => {
return (
<> <div>Time:{time}</div> <LazyComp /> </> ) }; A.getInitialProps = async ctx => { // 動態加載 moment,只有到了當前頁面的時候纔去加載它,而不是在頁面初始化的時候去加載 const moment = await import('moment'); const promise = new Promise(resolve => { setTimeout(() => { resolve({ name: 'jokcy', // 默認加載的是 ES6 模塊 time: moment.default(Date.now() - 60 * 1000).fromNow(), }) }, 1000) }); return await promise }; export default A; 複製代碼
./pages/_app.js
文件,自定義 App 模塊componentDidCatch
自定義處理錯誤// lib/my-context
import React from 'react'
export default React.createContext('')
// components/Layout
// 固定佈局
xxx
xxx
xxx
// _app.js
import 'antd/dist/antd.css';
import App, { Container } from 'next/app';
import Layout from '../components/Layout'
import MyContext from '../lib/my-context'
import {Provider} from 'react-redux'
class MyApp extends App {
state = {
context: 'value',
};
/** * 重寫 getInitialProps 方法 */
static async getInitialProps(ctx) {
const {Component} = ctx;
// 每次頁面切換的時候,這個方法都會被執行!!!
console.log('app init');
let pageProps = {};
// 由於若是不加 _app.js,默認狀況下,Next.js 會執行 App.getInitialProps
// 因此重寫 getInitialProps 方法時,路由組件的 getInitialProps 必需要執行
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return {
pageProps
}
}
render() {
const { Component, pageProps, reduxStore } = this.props;
return (
// 在最新的 Next.js 版本中,Container 被移除了,再也不須要 Container 包裹組件
// https://github.com/zeit/next.js/blob/master/errors/app-container-deprecated.md
<Container>
<Layout> <MyContext.Provider value={this.state.context}> <Component {...pageProps} /> </MyContext.Provider> </Layout> </Container> ) } } export default MyApp; 複製代碼
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
// 重寫 getInitialProps 方法
static async getInitialProps(ctx) {
// 由於若是不加 _document.js,默認狀況下,Next.js 會執行 Document.getInitialProps
// 因此自定義的時候,必須執行 Document.getInitialProps
const props = await Document.getInitialProps(ctx);
return {
...props
}
}
// render 要麼不重寫,重寫的話,如下的內容都必須加上
// render() {
// return (
// <Html>
// <Head>
// <style>{`body { background:red;} /* custom! */`}</style>
// </Head>
// <body className="custom_class">
// <Main />
// <NextScript />
// </body>
// </Html>
// )
// }
}
export default MyDocument
複製代碼
在線demo前端
在線demojava
page 表示路由組件node
注意: 當頁面初始化加載時,getInitialProps
只會在服務端被調用。只有當路由跳轉( Link
組件跳轉或 API 方法跳轉)時,客戶端纔會執行 getInitialProps
。react
優勢:webpack
缺點: 必須遵循它的規範(如:必須在 getInitialProps
中獲取數據),寫法固定,不利於拓展。git
看懂 Serverless,這一篇就夠了
理解serverless無服務架構原理(一)
什麼是Serverless無服務器架構?github
ReactDOM.render
方法會根據 data-react-checksum
的標記,複用 ReactDOMServer
的渲染結果,不重複渲染。根據 data-reactid
屬性,找到須要綁定的事件元素,進行事件綁定的處理。ReactDOMServer
渲染的內容再也不帶有 data-react
屬性,ReactDOM.render
可使用可是會報警告。ReactDOM.render
將再也不具備複用 SSR 內容的功能,統一用 hydrate()
來進行服務端渲染。ReactDOM.hydrate
。StaticRouter
(靜態路由容器),而非 BrowserRouter
和 HashRouter
componentDidMount
在服務器端是不執行的,而 componentWillMount
在客戶端和服務端都會執行,因此這就是爲何不建議在 componentWillMount
發送請求的緣由componentDidMount
中,不能放在 componentWillMount
中,由於 服務端是不會執行 componentWillUnmount
的,若是放在 componentWillMount
中,會致使事件重複註冊,發生內存泄漏npm-run-al
l & nodemon
來提升開發 Node 項目的效率npm install npm-run-all nodemon --save-dev
複製代碼
"scripts": {
"dev": "npm-run-all --parallel dev:**",
"dev:start": "nodemon build/server.js",
"dev:build:client": "webpack --config webpack.client.js --watch",
"dev:build:server": "webpack --config webpack.server.js --watch"
}
複製代碼
import React from "react"
,可是若是不引入,在寫組件時,編輯器會發出警告,因此仍是引入下較好request
、response
的封裝方式確定有不一樣之處,它是如何保證 Next.js 導出的 handle
方法能兼容這些框架尼?handle
方法接收到的是 NodeJS 原生的requset
對象以及 response
對象,不是框架基於原生封裝的 request
、response
對象。因此這就是爲何在使用 koa 時,handle
接收的是 ctx.req
、ctx.res
,而不是 ctx.request
、ctx.response
的緣由。cnpm i styled-components babel-plugin-styled-components -D
複製代碼
// .babelrc
{
"presets": ["next/babel"],
"plugins": [
[
"import",
{
"libraryName": "antd"
}
],
["styled-components", { "ssr": true }]
]
}
複製代碼
// _document.js
import Docuemnt, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'
function withLog(Comp) {
return props => {
console.log(props);
return <Comp {...props} />
}
}
class MyDocument extends Docuemnt {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
// 加強 APP 功能
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
// 加強組件功能
// enhanceComponent: Component => withLog(Component)
});
const props = await Docuemnt.getInitialProps(ctx);
return {
...props,
styles: (
<>
{props.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
複製代碼
// pages/a.js
import { withRouter } from 'next/router'
import Link from 'next/link'
import styled from 'styled-components'
const Title = styled.h1` color: yellow; font-size: 40px; `;
const color = '#113366';
const A = ({ router, name}) => {
return (
<> <Title>This is Title</Title> <Comp /> <Link href="#aaa"> <a className="link"> A {router.query.id} {name} </a> </Link> <style jsx>{` a { color: blue; } .link { color: ${color}; } `}</style> </> ) }; export default withRouter(A) 複製代碼
支持用 .css
、 .scss
、 .less
、 .styl
,須要配置默認文件 next.config.js,具體可查看下面連接
在根目錄下新建文件夾叫 static
,代碼能夠經過 /static/
來引入相關的靜態資源。但只能叫static
,由於只有這個名字 Next.js 纔會把它看成靜態資源。
React16.8 + Next.js + Koa2 開發 Github 全棧項目
淘寶先後端分離實踐 !!!!!!