优势: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 全栈项目
淘宝先后端分离实践 !!!!!!