获取数据,依然是Next与普通的React SPA应用不一样的地方,React应用基本都有本身的路由组件(固然大部分是react-router),咱们能够经过路由组件为咱们提供的方法,好比react-router的onEnter()方法或者universal-router的beforeEnter()方法。css
这里给你们推荐一个区别于react-router的路由组件universal-routerhtml
而Next.js没有路由组件,因此具体方式确定不一样于路由组件的方式,具体不一样就体如今Next.js为咱们提供了一个区别于React的新生命周期——getIntialProps(),下面来讲说这个API的牛X之处。前端
import React from 'react'
export default class extends React.Component {
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
render() {
return (
<div>
Hello World {this.props.userAgent}
</div>
)
}
}
复制代码
const Page = ({ stars }) =>
<div>
Next stars: {stars}
</div>
Page.getInitialProps = async ({ req }) => {
const res = await fetch('https://api.github.com/repos/zeit/next.js');
const json = await res.json();
return { stars: json.stargazers_count };
}
export default Page;
复制代码
这个生命周期是脱离于React的正常生命周期的,不过咱们依然能够在组件里正常使用react组件的各类生命周期函数。react
这真是getInitialProps这个生命周期的过人之处了,他能够在服务端运行,这样作有什么好处呢?说实话,我真不太清楚,我只知道一点,下面会讲,哈哈。若是有大牛知道的话,能够在留言给我讲讲~话很少说,上图:webpack
React老生命周期内获取数据git
以抓取用户列表为例,咱们能够在组件里的componentDidMount生命周期内获取github
// /components/user/userList.js
...
componentDidMount() {
this.props.fetchUserList();
}
复制代码
// /pages/user/userList.js
import UserList from '../../containers/user/UserList';
import { fetchUserListData } from '../../redux/actions/user';
UserList.getInitialProps = async (props) => {
const { store, isServer } = props.ctx;
if (store.getState().user.list.list.length === 0) {
store.dispatch(fetchUserListData());
}
return { isServer };
};
export default UserList;
复制代码
具体缘由就是由于static getInitialProps()这个生命周期是能够在服务端运行的,当页面第一次加载时,服务器收到请求,getInitialProps()会执行,getInitialProps()返回的数据,会序列化后添加到
window.__NEXT_DATA__.props
上,写入HTML源码里,相似于。这样服务端的getInitialProps()就实现了把数据传送给了客户端。当咱们经过Next.js的路由Link来进行页面跳转的时候,客户端就会从window.__NEXT_DATA__里获取数据渲染页面,就无需从新获取数据,算是提高性能的话一种方式吧~以下图所示:web
这里其实还真遇到一个坑,可能有不少人遇到过了,也可能没人遇到过。具体问题描述起来大概是这个样子,咱们在getInitialProps里面预获取数据,以用户列表为例,在首次加载的时候都是没有问题的包括各类客户端跳转。不过当咱们在用户列表页面进行刷新的时候,其实他就没有再走getInitialProps这个生命周期了,所以页面会没有能够渲染的数据,就会出现空页面,由于他认为这个应该从window.__Next_DATA__里面获取,而不是从新获取数据~那么为何刷新页面以后没有走这个getIntialProps,讲道理,我还真没太弄清楚,不过确实刷新页面next.js会给咱们在props里返回一个isServer:true,可是控制台并无获取数据。具体问题见下面截图:json
import { fetchUserListData } from '../../redux/actions/user';
UserList.getInitialProps = async (props) => {
const { store, isServer } = props.ctx;
if (store.getState().user.list.list.length === 0) {
store.dispatch(fetchUserListData());
}
return { isServer };
};
复制代码
上面fetchUserListData()就是抓取数据的action,返回值就会存入state,渲染数据列表。很明显,在第一次加载的时候是抓取成功的。可是刷新页面后,没有dispatch这个action,也就是代表,刷新页面没有走这个getIntialProps这个生命周期!!!redux
上面才是关键问题所在,不刷新页面的状况下是正常的,刷新页面没有走这个生命周期,而咱们不少数据都是须要预获取的,因此说还挺坑的,事实上,不少人遇到这个问题,并且我在next官方给出的reudx-demo里面也发现这个问题,也就是说他们官方的demo刷新也会出现这个问题。
既然是踩坑,固然有解决办法啦~并且仍是两种:
第一种:在组件生命周期里判断isServer
刚刚问题描述过了,也就是正常加载和经过路由跳转页面,数据会正常渲染且会从浏览器的window.__NEXT_DATA__获取来减小没必要要的网络请求~,而在页面进行刷新的时候不会从新请求数据而且window.__NEXT_DATA__里也找不到咱们想要的数据。不过经过控制台信息咱们能够发现问题所在以及解决办法。那就是,第一次启动系统的时候返回的isServer是false,而浏览器刷新页面的时候isServer返回的是true,咱们能够在组件里进行这个变量的判断,若是是true,就从新进行一次数据抓取。
// /components/user/UserList.js
...
componentDidMount() {
if(this.props.isServer) {
// 须要从新抓取数据
this.props.fetchUserListData();
}
}
...
复制代码
第二种:换一种方式预获取数据
另外一种方法就比较高级了,原理我依然不知道,可是就是好用,哈哈,这东西真是邪门,为何这么说呢,其实本质没改变什么,就是换了种写法就能够。具体就是,上面的写法我在getInitalProps里面写了dispatch了一个获取数据的action,从上一节或者代码里大家能够看到,其实这个action就是fetch一个api获取数据返回state。这就是redux一个获取数据的基本过程,这种方法在刷新时行不通,而行得通的方法是:不经过dispatch action的方式获取数据,而是直接在getIntialProps里面经过fetch api的方式获取数据,这样每次刷新页面也均可以获取到数据了。。。就是这么神奇,我也真不知道为啥。
// /pages/user/userList
import fetch from 'isomorphic-unfetch';
import UserList from '../../containers/user/UserList';
import { fetchUserListDataSuccess } from '../../redux/actions/user';
UserList.getInitialProps = async (props) => {
const { store, isServer } = props.ctx;
let userData;
if (store.getState().user.list.list.length === 0) {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
userData = await res.json();
store.dispatch(fetchUserListDataSuccess(userData));
}
return { isServer };
};
export default UserList;
复制代码
就是很神奇有木有,说实话我是真不知道为啥,有大牛的话真心给我讲讲万分感谢了~ 不过这两种写法我仍是比较喜欢上面第一种的,由于以为第一种在本身可控范围内,由于之前写react项目也是在生命周期里控制一些数据的获取。可能更习惯吧,不过我认可第二种更牛逼一些,性能也可能更好吧~各取所需吧。
这个组件从我使用的角度来看,做用跟我前几章有个地方的目的是同样的,就是咱们在Next.js里没有相似create-react-app里面的index.html。所以咱们没有办法定义最后渲染的html的结构,好比title,meta等标签。我最开始是经过next/head的Head组件来实现的,可是head组件其实最后生成的就是html的head标签。而Document组件是彻底帮助咱们构造html结构。
// 除去Layout的Head结构
// pages文件夹新增_document.js文件
// ./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>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta charSet='utf-8' />
<title>Next-Antd-Scafflod</title>
<link rel='shortcut icon' href='/static/favicon.ico' type='image/ico'/>
<link rel='stylesheet' href='/_next/static/style.css' />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
复制代码
_document.js是只在Next.js的服务端来进行渲染的,客户端只是拿到服务端渲染事后的html字符串渲染前端页面,上面提到的window.__NEXT_DATA__就是存放在NextScript里的。
其实之前在写服务端渲染项目的时候会遇到不少坑,最多见的就是好比我想引入一些外部组件,这些组件里有window,document等这种客户端变量,而这些变量在服务端是不存在的,所以在服务端渲染的时候就会报错,因此就很麻烦,须要webpack各类配置而后在异步引入。好比:富文本编辑器。而next直接为咱们封装了动态引入的import,不出意外用的应该就是webpack的import方法,管他呢,好用就行。下面就给你们简单是演示一下其中一个功能,就是动态引入一个富文本编辑器,而后空白期loading另外一个组件~用法很是简单,就是下面这样:
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(import('braft-editor'), {
loading: () => <p>正在加载组件...</p>
});
render() {
return (
<Fragment>
<h1>用户信息:{this.state.username}</h1>
<div style={{ width: '50%', height: '400px', }}>
<DynamicComponent />
</div>
</Fragment>
);
}
复制代码
详细的Next为咱们提供了更多的方法,感兴趣的能够去官网看文档,有四种异步引入的方法,其中还包含只在服务端引入~文档地址
错误处理,目前不少优秀的脚手架都为咱们提供了错误处理,好比404和500的时候的页面渲染,Next.js一样,内部自动为咱们封装了errorPage。也就是咱们其实什么都不用干,就能够享受这个服务。好比我在系统里随便输入一个网址,会出现下面的结果:
而后你还能够本身定义你的errorPage页面,方法很是的简单,就是在pages文件夹下面新建一个_error.js的文件,里面写上你的errorPage代码就能够了,下面就简单写一个,其实就是从官网扒下来的~
// /pages/_error.js
import React from 'react'
export default class Error extends React.Component {
static getInitialProps({ res, err }) {
const statusCode = res ? res.statusCode : err ? err.statusCode : null;
return { statusCode }
}
render() {
return (
<p>
{this.props.statusCode
? `An error ${this.props.statusCode} occurred on server`
: 'An error occurred on client'}
</p>
)
}
}
复制代码
又一个高级功能,它支持咱们把各类路由导出成静态页面,不过你细想其实也没啥大用,毕竟咱们项目都是有逻辑的,导出静态页面也不能操做,哈哈。不过既然是挺牛逼的一个功能,就拿来试试。
exportPathMap: async (defaultPathMap) => {
return {
'/home': { page: '/' },
'/userList': { page: '/user/userList' },
}
},
复制代码
"scripts": {
...
// 新增导出命令
"export": "yarn build && next export"
},
复制代码
第三步,运行yarn export命令
运行完命令以后,根目录下会出现一个out文件夹,真的是很是神奇,里面有页面文件夹和必要的静态资源。
这里还有一个高级的Next.js项目推送到github page的功能,依赖的也是这个export,不过期间问题我就没写,你们感兴趣的去看看官方demo,应该能够解决的~
写到这里,Next.js踩坑入门系列就写完了。很是感谢有不少小伙伴一直在看,还有一些可爱的小伙伴催更,水平有限,彻底是踩坑集锦,若是能帮助到你们真的很开心。谢谢你们的阅读。接下来准备用Next.js搭一个网站。完成后可能会再写一篇Next.js的建站文章,其余的就不写了,再写就是其余内容啦~
本章节代码地址
项目代码地址,喜欢的给个Star,谢谢米娜桑