next.js
是一个轻量级的 React 服务端渲染应用框架
目录结构
|-- .gitignore
|-- package.json
|-- yarn.lock
|-- layouts //布局组件
| |-- layout.js
|-- components //组件文件夹
| |-- nav.js
|-- pages
| |-- index.js
| |-- _error.js //错误文件
| |-- _app.js //全局布局组件入口
|-- static //静态资源文件夹,编译打包不被执行
|-- public
|-- favicon.ico
一、脚手架工具
yarn create next-app <项目名称>
安装样式解析包
yarn add @zeit/next-css@1.0.1 @zeit/next-less@1.0.1 less@3.8.1 -S
修改next.config.js文件内容
const withCSS = require('@zeit/next-css')
const withLess = require('@zeit/next-less')
// 解决ant/lib/style/index.css 不认识@font-face 异常
if (typeof require !== 'undefined') {
require.extensions['.css'] = (file) => {
}
}
module.exports = Object.assign({},
withCSS(withLess({
// 开启Less模块化,但是同时也会开启css的模块化,这样会使得antd的样式加载不成功
cssModules: true,
webpack(config, {isServer}) {
if (!isServer) {
// 关闭css模块化,使antd样式生效
config.module.rules[2].use[2].options.modules = false;
}
// 添加actions和reducers快速引用
// config.resolve.alias.actions = path.resolve(__dirname, 'actions');
// config.resolve.alias.reducers = path.resolve(__dirname, 'reducers');
return config;
}
}))
)
二、路由
1.基本路由
Link
//1.不能直接在Link组件中写字符串,应该用一个标签包裹起来,这个标签可以是任意一个标签,但是Link组件中只能有一个子元素,不能包含多个
//2.不能直接给Link组件设置样式,因为它是一个HOC(高阶组件),给他设置样式无效,可以给Link组件的子元素设置样式
import Link from 'next/link'
<Link href="/next-route/teacher">
<a style={{color:'red'}}>teacher</a>
</Link>
<Link href={{pathname:'/next-route/teacher',query:{id:1}}}>
<a style={{color:'red'}}>teacher</a>
</Link>
2.命名式路由
import Router from 'next/router'
<span onClick={()=> Router.push('/next-route/teacher')}>student</span>
3.路由传参
传参
<ul>
{
teacherList.map(item => (
<li key={item.id}>
{/* 可以通过Link组件的as属性给路径取别名 */}
<Link href={`/next-route/teacher/detail?id=${item.id}`}>
<a>{item.name}</a>
</Link>
</li>
))
}
</ul>
接参
import { withRouter } from 'next/router'
// withRouter这个高阶组件会将当前的路由对象注入到组件中去,并将路由对象绑定到组件的props上
const Detail = withRouter((props) => (
<div>
这是{props.router.query.id}号老师的详情
</div>
))
export default Detail
4.浅层路由
//可以通过Link组件的as属性给路径取别名
<ul>
{
teacherList.map(item => (
<li key={item.id}>
{/* 可以通过Link组件的as属性给路径取别名 */}
<Link as={`/next-route/teacher/${item.id}`} href={`/next-route/teacher/detail?id=${item.id}`}>
<a>{item.name}</a>
</Link>
</li>
))
}
</ul>
解决浅层路由刷新页面404的问题
//1.安装 express
cnpm install -S express
//2.在项目的根目录 创建server.js
const express = require('express')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
server.get('/next-route/teacher/:id', (req, res) => {
const actualPage = '/next-route/teacher/detail'
const queryParams = { id: req.params.id }
app.render(req, res, actualPage, queryParams)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})
//3.修改package.json文件中script字段
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
//4.创建自定义路由
server.get('/next-route/teacher/:id', (req, res) => {
const actualPage = '/next-route/teacher/detail'
const queryParams = { id: req.params.id }
app.render(req, res, actualPage, queryParams)
})
5.错误页面
_error.js
const ErrorPage = () => (
<div>
<h1>不好意思,你访问的页面去火星了!</h1>
</div>
)
export default ErrorPage
三、布局组件
mylayout.js
<div>
<Mynav></Mynav>
{props.children}
<Myfooter></Myfooter>
</div>
全局布局组件
_app.js
import React from 'react'
import App, { Container } from 'next/app'
import MovieLayout from '../layouts/MovieLayout'
class Layout extends React.Component {
render () {
const {children} = this.props
return (
<MovieLayout>
{children}
</MovieLayout>
)
}
}
export default class MyApp extends App {
render () {
const {Component, pageProps} = this.props
return <Container>
<Layout>
<Component {...pageProps} />
</Layout>
</Container>
}
}
四、请求数据
Isomorphic Unfetch
使用异步静态方法 getInitialProps 获取数据,此静态方法能够获取所有的数据,并将其解析成一个 JavaScript 对象,然后将其作为属性附加到 props 对象上
当页面初次加载时,getInitialProps 只会在服务端执行一次。getInitialProps 只有在路由切换的时候(如Link组件跳转或命名式路由跳转)时,客户端的才会被执行。
注意:getInitialProps 不能 在子组件上使用,只能使用在 pages 页面中。
1.安装
//安装
yarn add isomorphic-unfetch@2.2.1 es6-promise -S
//or
cnpm i -S isomorphic-unfetch
//引入
import fetch from 'isomorphic-unfetch'
// Index是一个组件
Index.getInitialProps = async function() {
const res = await fetch('http://localhost:3301/in_theaters')
const data = await res.json()
// 这段数据会在服务器端打印,客户端连请求都不会发
console.log(data)
return {
// 组件中通过props.shows可以访问到数据
movieList: data
}
}
如果你的组件是一个类组件,你需要这样写:
export default class extends React.Component {
static async getInitialProps() {
const res = await fetch('http://localhost:3301/in_theaters')
const data = await res.json()
console.log(data);
return {movieList: data}
}
render() {
return (
<div>
{this.props.movieList.map(item => (
<p key={item.id}>{item.title}</p>
))}
</div>
)
}
}
getInitialProps: 接收的上下文对象包含以下属性:
pathname:URL的path部分query:URL的query string部分,并且其已经被解析成了一个对象asPath: 在浏览器上展示的实际路径(包括query字符串)req:HTTP request对象 (只存在于服务器端)res:HTTP response对象 (只存在于服务器端)jsonPageRes: 获取的响应数据对象 Fetch Response (只存在于客户端)err: 渲染时发生错误抛出的错误对象
// 在另外一个组件中,可以使用context参数(即上下文对象)来获取页面中的query
Post.getInitialProps = async function (context) {
const { id } = context.query
const res = await fetch(`http://localhost:3301/in_theaters/${id}?_embed=details`)
const data = await res.json()
console.log(data)
return {movieDetail: data}
}
isomorphic-fetch
1.安装
yarn add isomorphic-fetch@2.2.1 es6-promise -S
isomorphic-fetch是基于Fetch语法封装好的用于同构应用开发库
基于标准 Promise 实现,支持 async/await
注:同构就是使前后端运行同一套代码的意思,后端一般是指 NodeJS 环境。
require('es6-promise').polyfill(); //保证使低版本浏览器(IE<9)中可以正常使用promise
import 'isomorphic-fetch'
2.参数说明
method: 表示当前请求 GET/POST/PUT/DELETE
headers: 自定义请求头对象
Content-Type: application/json;charset=UTF-8 #表示POST请求报文体中的数据格式为JSON
body: POST请求报文对象
cache: 设置cache模式 no-cache 请求不被缓存
credentials: 设置cookies是否随请求一起发送,可以设置 include 允许跨域请求携带cookies
3.封装fetch请求
//1.定义数据服务基础url
const baseUrl = 'http://157.122.54.189:9092'
//2.基本的fetchHelper请求
function fetchHelper(urlPath,options){
return fetch(baseUrl+urlPath,options)
}
//3.get请求
fetchHelper.get = (urlPath)=>{
return fetch(baseUrl+urlPath,{
method:'GET',
cache:'no-cache',
credentials:'include'
}).then(response=>{
if (response.status === 200) {
return response.json()
}else{
throw new Error('请求状态异常')
}
}).catch(error=>{
throw new Error('请求错误')
})
}
//4.post请求
fetchHelper.post = (urlPath,body)=>{
return fetch(baseUrl+urlPath,{
method:'POST',
headers:{
'Content-Type':'application/json;charset=UTF-8'
},
body:JSON.stringify(body),
cache:'no-cache',
credentials:'include'
}).then(response=>{
if (response.status === 200) {
return response.json()
}else{
throw new Error('请求状态异常')
}
}).catch(error=>{
throw new Error('请求错误')
})
}
//导出
export default fetchHelper
4.使用fetch请求
在_app.js中导入
五、样式
组件样式
- css样式文件
- css in js
- styled-jsx
-
scoped
如果添加了
jsx属性,只作用于当前组件,不包括子组件
<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>
-
global
作用于当前组件,包括子组件
<style jsx global>{``}</style>
全局样式
# 在pages目录下新建 _document.js 文件
import Document, { Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<html>
<Head>
<link rel="stylesheet" href="./../static/css/antd.css"/>
</Head>
<body className="custom_class">
<Main />
<NextScript />
</body>
</html>
)
}
}
六、redux(状态管理器)
1.安装react中redux相关的包
yarn add redux@4.0.0 react-redux@5.0.7 next-redux-wrapper@2.0.0 -S
- redux : 数据流框架
- react-redux:数据流在react中的实现包装
- next-redux-wrapper:在next.js中对redux进行包装,实现store全局共享,可以跨组件访问
2.初步使用
在根目录创建reducer文件夹在里面创建一个testReducer.js 业务逻辑
//参数1:state 全局对象,将来可以被store中的dispatch修改,同时会触发依赖于次对象中的属性的组件中的内容的相关业务改变
//参数2:action 当store的dispatch被调用的时候,会传入此action,来告诉store应用做什么操作
export default function testReducer(state={color:'red'},action){
switch (action.type) {
case 'CHANGE_COLOR':
return {
color:action.color
}
default:
return state;
}
}
在根目录创建store文件夹,创建index.js文件,状态管理仓库
//导入redux中的两个函数createStore,combineReducers
import {createStore,combineReducers} from 'redux'
//导入具体的业务reducer
import testReducer from './../reducers/testReducer'
// 将多个reducer编译成统一的rootReducer
const rootReducer = combineReducers({
testReducer
})
//调用createStore创建一个store对象
const store = createStore(rootReducer)
// 构建初始化Store的函数,交给 next-redux-wrapper的withRedux 使用
export const initStore = () => store
/* const initStore = (initialState,options) => {
return createStore(rootReducer,initialState);
}
export default initStore */
调用方法改变状态
import {connect} from 'react-redux'
class head extends React.Component {
render() {
return (
<a onClick={()=>{this.props.onChangeColor('blue')}}>蓝色</a>
<a onClick={()=>{this.props.onChangeColor('red')}}>红色</a>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onChangeColor:(color)=>{
dispatch({type:'CHANGE_COLOR',color:color})
}
}
}
export default connect(null,mapDispatchToProps)(head)
使用状态
import React from 'react'
import {connect} from 'react-redux'
class Home extends React.Component {
render() {
return (
<div className="container">
<main>
<h1 style={{color:this.props.testReducer.color}}>哈哈哈</h1>
</main>
</div>
);
}
};
const mapStateToProps = (state) => {
return {...state}
}
export default connect(mapStateToProps)(Home);
3.自动保存到localStorage中
安装redux-persist
yarn add redux-persist