next.js做爲一款輕量級的應用框架,主要用於構建靜態網站和後端渲染網站。javascript
Next.js 可與 Windows,Mac 和 Linux 一塊兒使用.您只須要在系統上安裝 Node.js 便可開始構建 Next.js 應用程序.若是有個編輯器就更好了css
mkdir next-demo //建立項目 cd next-demo //進入項目 npm init -y // 快速建立package.json而不用進行一些選擇 npm install --save react react-dom next // 安裝依賴 mkdir pages //建立pages
mkdir pages 這一步是必須建立一個叫 pages 的文件夾,由於 next 是根據 pages 下面的 js jsx tsx 文件來進行路由生成,且文件夾名字必須是pagesjava
而後打開 package.json 目錄中的 next-demo 文件並替換 scripts 爲如下內容:react
"scripts": { "dev": "next", "build": "next build", "start": "next start" }
運行如下命令以啓動開發服務器:webpack
npm run dev
如今能夠打開 localhost:3000 來查看頁面效果,若是不喜歡 3000 或者端口衝突,執行下面命令git
npm run dev -p 6688(你喜歡的端口)
這時候就能夠在 localhost:6688 上看到頁面效果了github
此時咱們在 pages 文件夾下建立一個 index.js 做爲首頁web
const Index = () => ( <div> <p>Hello Next.js</p> </div> ); export default Index;
再次查看 localhost:6688 就能夠看到當前頁面顯示出 hello worldnpm
next 中實現路由很是的簡便,新建 pages/about.jsjson
export default function About() { return ( <div> <p>This is the about page</p> </div> ); }
此時訪問 localhost:6688/about,就能夠看到頁面相應的效果(路由與 pages 下的文件名稱徹底匹配)
頁面間的導航,咱們能夠 a 標籤來進行導航.可是,它不會執行客戶端導航.而且,每次點擊瀏覽器將向服務器請求下一頁,同時刷新頁面.所以,爲了支持客戶端導航,,咱們須要使用 Next.js 的 Link API,該 API 經過導出 next/link. Link 將預取頁面,而且導航將在不刷新頁面的狀況下進行.
修改 pages/index.js
import Link from 'next/link'; const Index = () => ( <div> <Link href="/about"> <a>About Page</a> </Link> <p>Hello Next.js</p> </div> ); export default Index;
再次訪問 localhost:6688,而後點擊 About Page 跳轉到 about 頁面.以後點擊瀏覽器的後退按鈕,頁面可以回到 index.
Link組件默認是將路由push進入瀏覽器記錄,因此點擊後退按鈕是返回上一頁.這一默認形式能夠替換爲replace,更改成
<Link href="/about" replace>
由於 next/link 只是一個更高階的組件(高階組件) , next/link 組件上的設置 props 無效.只接受 href 和相似的 props.若是須要向其添加 props,則須要對下級組件進行添加. next/link 組件不會將那些 props 傳遞給子組件,而且還會給你一個錯誤警告.在這種狀況下,next/link 組件的子組件/元素是接受樣式和其餘 props 最好對象.它能夠是任何組件或標籤,惟一要求是可以接受 onClick 事件.
若是將功能組件做爲子組件進行傳遞,則須要將功能組件包裝
React.forwardRef
才能在<Link>
使用
<Link href="/about"> <a className="redLink">About Page</a> </Link> <Link href="/show"> <div>Show Page</div> </Link>
這是客戶端導航;該操做在瀏覽器中進行,而無需向服務器發出請求.打開開發者工具 networks 進行查看
另外的客戶端導航是Router
import Router from 'next/router' function Index() { return ( <div> Click <span onClick={() => Router.push('/about')}>here</span> to read more </div> ) } export default Index
儘管實現代碼的過程當中以及官方案例中在Link組件裏將a標籤做爲子元素傳進去,可是實際使用中,a標籤會形成路由切換失效的狀況,酌情使用其餘標籤代替.
目前 Next.js 代碼都是關於頁面的.咱們能夠經過導出 React 組件並將該組件放入 pages 目錄來建立頁面.而後,它將具備基於文件名的固定 URL. 但同時一些共享組件也是項目中必須的,咱們將建立一個公共的 Header 組件並將其用於多個頁面.
新建 components/Header.js
import Link from "next/link"; const linkStyle = { marginRight: 15 }; const Header = () => ( <div> <Link href="/"> <a style={linkStyle}>Home</a> </Link> <Link href="/about"> <a style={linkStyle}>About</a> </Link> <Link href="/show"> <a style={linkStyle}>Show</a> </Link> </div> ); export default Header;
而後修改 pages 目錄下的 index.js / about.js / show.js
import Header from '../components/Header'; export default function Show() { return ( <div> <Header /> <p>Hello Next.js</p> </div> ); }
打開 localhost:6688 點擊 3 個 link 按鈕就能夠進行頁面間的來回跳轉了
當前所使用的components這個名字並非必須的,你能夠將這個文件夾命名爲任何名稱.next中固定且不能改變的文件夾只有兩個'pages'和'static'.next也並不限制將公共組件存放在pages裏面,但最好不要在 pages 裏面建立共享組件,這樣會生成許多無效的路由.
在咱們的應用中,咱們將在各個頁面上使用通用樣式.爲此,咱們能夠建立一個通用的 Layout 組件並將其用於咱們的每一個頁面.
components/MyLayout.js
import Header from './Header'; const layoutStyle = { margin: 20, padding: 20, border: '1px solid #DDD' }; const Layout = props => ( <div style={layoutStyle}> <Header /> {props.children} </div> ); export default Layout;
而後修改 pages 目錄下的 index.js / about.js / show.js
import Layout from '../components/MyLayout'; export default function Show() { return ( <Layout> <p>Hello Next.js</p> </Layout> ); }
此外還可使用 hoc 組件進行內容傳遞獲取使用 props 屬性進行傳遞.最終實現的是佈局組件實現了多頁面共用.
在實際應用中,咱們須要建立動態頁面來顯示動態內容.
首先修改 pages/about.js 文件
import Layout from "../components/MyLayout"; import Link from "next/link"; const PostLink = props => ( <li> <Link href={`/post?title=${props.title}`}> <a>{props.title}</a> </Link> </li> ); export default function About() { return ( <Layout> <h1>My Blog</h1> <ul> <PostLink title="Hello Next.js" /> <PostLink title="Learn Next.js is awesome" /> <PostLink title="Deploy apps with Zeit" /> </ul> </Layout> ); }
一樣的效果Link使用對象形式
<Link href={{ pathname: '/post', query: { title: 'this is title' } }}>
依然一樣的效果使用Router
import Router from 'next/router' const handler = () => { Router.push({ pathname: '/about', query: { name: 'this is title }, }) }
Router.push(url, as)與Link組件使用了相同的參數,第一個參數就是url,若是有第二參數,就是對應as
建立 pages/post.js
import { useRouter } from 'next/router'; import Layout from '../components/MyLayout'; const Page = () => { const router = useRouter(); return ( <Layout> <h1>{router.query.title}</h1> <p>This is the blog post content.</p> </Layout> ); }; export default Page;
打開 localhost:6688 查看頁面效果,點擊 about 下面的 3 個帖子,會出現對應的 title 頁面
post 頁面也能夠添加通用 header
import { useRouter } from "next/router"; import Layout from "../components/MyLayout"; const Content = () => { const router = useRouter(); return ( <Layout> <h1>{router.query.title}</h1> <p>This is the blog post content.</p> </Layout> ); }; const Page = () => ( <Layout> <Content /> </Layout> ); export default Page;
再次查看 localhost:6688 看看不一樣,如今的頁面也具備一套完整的佈局.
能夠經過Router監聽路由器內部發生的不一樣事件
// 監聽 Router.events.on('routeChangeStart', handleRouteChange) // 關閉 Router.events.off('routeChangeStart', handleRouteChange)
官方表示getInitialProps狀況下不建議使用路由器事件.若是須要最好是在組件加載後或者某些事件後進行監聽.
當前咱們的路由是這樣的 http://localhost:6688/post?title=Hello%20Next.js , 如今須要更乾淨的路由 http://localhost:6688/p/10. 添加新頁面來建立咱們的第一個動態路由 p/[id].js
新建 pages/p/[id].js
import { useRouter } from 'next/router'; import Layout from '../../components/MyLayout'; export default function Post() { const router = useRouter(); return ( <Layout> <h1>{router.query.id}</h1> <p>This is the blog post content.</p> </Layout> ); }
useRouter
是一個React鉤子函數,它不能與類一塊兒使用.類組件可使用withRouter
高階組件或將類包裝在功能組件中.同時withRouter也能夠直接用於功能組件中.
在連接多個頁面,新建 pages/page.js
import Layout from '../components/MyLayout'; import Link from 'next/link'; const PostLink = props => ( <li> <Link href="/p/[id]" as={`/p/${props.id}`}> <a>{props.id}</a> </Link> </li> ); export default function Blog() { return ( <Layout> <h1>My Blog</h1> <ul> <PostLink id="hello-nextjs" /> <PostLink id="learn-nextjs" /> <PostLink id="deploy-nextjs" /> </ul> </Layout> ); }
<Link href={
/p?id=${item.id}} as={
/p/${item.id}}>
,訪問路徑就是http://localhost:6688/p/975
<Link href={
/p?id=${item.id}}>
,訪問路徑就是http://localhost:6688/p?id=975
<Link href={
/p?id=${item.id}} as={
/post/${item.id}}>
,起別名http://localhost:6688/post/975
在該頁面中咱們看一下 元素,其中 href 屬性 p 文件夾中頁面的路徑, as 是要在瀏覽器的 URL 欄中顯示的 URL,這是next官方提供的一個路由遮擋功能,用來隱藏本來複雜的路由,顯示出來簡潔的路由.使網站路徑更簡潔.as 一般是用來與瀏覽器歷史記錄配合使用.
實際上,咱們一般須要從遠程數據源獲取數據.Next.js 本身有標準 API 來獲取頁面數據.咱們一般使用異步函數 getInitialProps 來完成此操做 .這樣,咱們能夠經過遠程數據源獲取數據到頁面上,並將其做爲 props 傳遞給咱們的頁面.getInitialProps 在服務器和客戶端上都可使用.
首先須要一個獲取數據的庫
npm install --save isomorphic-unfetch
而後修改 pages/index.js
import Layout from '../components/MyLayout'; import Link from 'next/link'; import fetch from 'isomorphic-unfetch'; const Index = props => ( <Layout> <h1>Batman TV Shows</h1> <ul> {props.shows.map(show => ( <li key={show.id}> <Link href="/detail/[id]" as={`/detail/${show.id}`}> <a>{show.name}</a> </Link> </li> ))} </ul> </Layout> ); Index.getInitialProps = async function() { const res = await fetch('https://api.tvmaze.com/search/shows?q=batman'); const data = await res.json(); return { shows: data.map(entry => entry.show) }; }; export default Index;
如今這種狀況下,咱們只會在服務器上獲取數據,由於咱們是在服務端進行渲染.
再建立一個詳情頁,這裏用到了動態路由
新建 pages/detail/[id].js
import Layout from "../../components/MyLayout"; import fetch from "isomorphic-unfetch"; import Markdown from "react-markdown"; const Post = props => ( <Layout> <h1>{props.show.name}</h1> <div className="markdown"> <Markdown source={props.show.summary.replace(/<[/]?p>/g, "")} /> </div> <img src={props.show.image.medium} /> <style jsx global>{` .markdown { font-family: "Arial"; } .markdown a { text-decoration: none; color: blue; } .markdown a:hover { opacity: 0.6; } .markdown h3 { margin: 0; padding: 0; text-transform: uppercase; } `}</style> </Layout> ); Post.getInitialProps = async function(context) { const { id } = context.query; const res = await fetch(`https://api.tvmaze.com/shows/${id}`); const show = await res.json(); return { show }; }; export default Post;
點擊 list 中的隨便一個,而後打開控制檯和瀏覽器的 networks,會發現此次是在瀏覽器端進行接口請求.
getInitialProps 上下文對象context具備如下屬性
Next.js 在 JS 框架中預加載了一個稱爲 styled-jsx 的 CSS,該 CSS 使你的代碼編寫更輕鬆.它容許您爲組件編寫熟悉的 CSS 規則.規則對組件(甚至子組件)之外的任何東西都沒有影響.簡單來講就是帶有做用域的 css.
修改 pages/page.js
import Layout from "../components/MyLayout"; import Link from "next/link"; function getPosts() { return [ { id: "hello-nextjs", title: "Hello Next.js" }, { id: "learn-nextjs", title: "Learn Next.js is awesome" }, { id: "deploy-nextjs", title: "Deploy apps with ZEIT" } ]; } export default function Blog() { return ( <Layout> <h1>My Blog</h1> <ul> {getPosts().map(post => ( <li key={post.id}> <Link href="/p/[id]" as={`/p/${post.id}`}> <a>{post.title}</a> </Link> </li> ))} </ul> <style jsx>{` h1, a { font-family: "Arial"; } ul { padding: 0; } li { list-style: none; margin: 5px 0; } a { text-decoration: none; color: red; } a:hover { opacity: 0.6; } `}</style> </Layout> ); }
在上面的代碼中,咱們直接寫在模板字符串中,並且必須使用模板字符串({``})編寫 CSS .
此時修改一下代碼
import Layout from "../components/MyLayout"; import Link from "next/link"; function getPosts() { return [ { id: "hello-nextjs", title: "Hello Next.js" }, { id: "learn-nextjs", title: "Learn Next.js is awesome" }, { id: "deploy-nextjs", title: "Deploy apps with ZEIT" } ]; } const PostLink = ({ post }) => ( <li> <Link href="/p/[id]" as={`/p/${post.id}`}> <a>{post.title}</a> </Link> </li> ); export default function Blog() { return ( <Layout> <h1>My Blog</h1> <ul> {getPosts().map(post => ( <PostLink key={post.id} post={post} /> ))} </ul> <style jsx>{` h1, a { font-family: "Arial"; } ul { padding: 0; } li { list-style: none; margin: 5px 0; } a { text-decoration: none; color: blue; } a:hover { opacity: 0.6; } `}</style> </Layout> ); }
這時候打開瀏覽器觀察就會發現也是不生效,這是由於 style jsx 這種寫法樣式是有做用域,css 只能在當前做用域下生效.
解決 1 , 給子組件添加上子組件的樣式
const PostLink = ({ post }) => ( <li> <Link href="/p/[id]" as={`/p/${post.id}`}> <a>{post.title}</a> </Link> <style jsx>{` li { list-style: none; margin: 5px 0; } a { text-decoration: none; color: blue; font-family: 'Arial'; } a:hover { opacity: 0.6; } `}</style> </li> );
解決 2 , 全局樣式
<style jsx global>{` ......css `}
通常不使用全局樣式來解決
有時,咱們確實須要更改子組件內部的樣式.尤爲是使用一些第三方庫樣式又有些不滿意的時候.
安裝 react-markdown
npm install --save react-markdown
修改 pages/post.js
import { useRouter } from "next/router"; import Layout from "../components/MyLayout"; import Markdown from "react-markdown"; const Content = () => { const router = useRouter(); return ( <Layout> <h1>{router.query.title}</h1> <div className="markdown"> <Markdown source={` # Live demo Changes are automatically rendered as you type. ## Table of Contents * Implements [GitHub Flavored Markdown](https://github.github.com/gfm/) * Renders actual, "native" React DOM elements * Allows you to escape or skip HTML (try toggling the checkboxes above) * If you escape or skip the HTML, no dangerouslySetInnerHTML is used! Yay! ## HTML block below <blockquote> This blockquote will change based on the HTML settings above. </blockquote>` } /> </div> <style jsx global>{` .markdown { font-family: "Arial"; } .markdown a { text-decoration: none; color: blue; } .markdown a:hover { opacity: 0.6; } .markdown h3 { margin: 0; padding: 0; text-transform: uppercase; } `}</style> </Layout> ); }; const Page = () => ( <Layout> <Content /> </Layout> ); export default Page;
打開 localhost:6688 的 about 頁面點擊查看樣式效果
目前代碼在頁面中呈現的樣式是比較隨意的,秉承着能打開就行的原則開發到這一步,是否應該稍微美化一下下.
首先安裝須要的庫
npm install --save @zeit/next-less less
而後把 mylayout 和 header 裏面的行內樣式去掉
新建 assets/css/styles.less
.header { display: block; z-index: 500; width: 100%; height: 60px; font-size: 14px; background: #fff; color: rgba(0, 0, 0, 0.44); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; letter-spacing: 0; font-weight: 400; font-style: normal; box-sizing: border-box; top: 0; &:after { box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.07); display: block; position: absolute; top: 60px; color: rgba(0, 0, 0, 0.07); content: ""; width: 100%; height: 2px; } .header-inner { width: 1000px; margin: 0 auto; a { height: 60px; line-height: 60px; font-size: 18px; color: #c7c7c7; cursor: pointer; margin-right: 25px; &:hover { font-size: 18px; color: #2d2d2f; } } } } .content { width: 1000px; margin: 0 auto; padding-top: 30px; }
修改 next.config.js
// next.config.js const withLess = require('@zeit/next-less') module.exports = withLess({ /* config options here */ })
在 MyLayout 裏面引入 less
import "../assets/css/styles.less";
在 localhost:6688 查看頁面出現相應的樣式
npm install antd --save npm install babel-plugin-import --save-dev touch.babelrc
.babelrc
{ "presets": ["next/babel"], "plugins": [ [ "import", { "libraryName": "antd", "style": "less" } ] ] }
以後引入 antd 的樣式
assets/css/styles.less
@import "~antd/dist/antd.less";
這時候就是正常引入 antd 的組件進行使用就能夠了
import { Typography, Card, Avatar } from "antd"; const { Title, Paragraph, Text } = Typography;
ValidationError: Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'minimize'. These properties are valid: #541
新版中 css-loader 和 webpack 會出現這樣一個錯誤,這是升級過程當中代碼變動致使了,css-loader 已經沒有 minimize 這一選項.
解決方法,在 next.config.js 添加去除代碼
const withLess = require("@zeit/next-less"); if (typeof require !== "undefined") { require.extensions[".less"] = file => {}; } function HACK_removeMinimizeOptionFromCssLoaders(config) { console.warn( "HACK: Removing `minimize` option from `css-loader` entries in Webpack config" ); config.module.rules.forEach(rule => { if (Array.isArray(rule.use)) { rule.use.forEach(u => { if (u.loader === "css-loader" && u.options) { delete u.options.minimize; } }); } }); } module.exports = withLess({ lessLoaderOptions: { javascriptEnabled: true }, webpack(config) { HACK_removeMinimizeOptionFromCssLoaders(config); return config; } });
先安裝 now,一個靜態資源託管服務器
npm i -g now now
等待一段時間以後會生成一個靜態連接,點擊打開就能夠看到本身網頁的樣子了https://react-next-demo.fuhuodemao.now.sh
查看 package.json 的 script
"dev": "next -p 6688", "build": "next build", "start": "next start -p 6688",
如今執行命令來生成代碼並預覽
npm run build // 構建用於生產的Next.js應用程序 npm start // 在6688端口上啓動Next.js應用程序.該服務器將進行服務器端渲染並提供靜態頁面
在 localhost:6688 上咱們能夠看到一樣的效果
修改 script 命令
"start": "next start -p 6688",
而後執行npm start
,咱們能夠在 localhost:8866 上再次打開一個應用
在 window 下須要額外的工具 cross-env
npm install cross-env --save-dev