Next.js踩坑入门系列(七) —— 其余相关知识

Next.js踩坑入门系列

获取数据&&getInitialProps

获取数据,依然是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之处。前端

使用方法

  • 在React.Component使用
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>
       )
     }
   }

复制代码
  • 在stateless组件内使用
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

能够看到,这个生命周期我触发了action获取数据,而这个action在控制台被打印出来了,说明能够运行在服务端~

减小抓取数据的次数

  • React老生命周期内获取数据git

    以抓取用户列表为例,咱们能够在组件里的componentDidMount生命周期内获取github

// /components/user/userList.js
 ...
 componentDidMount() {
    this.props.fetchUserList();
 }
复制代码

从上图咱们能够看出来,每次进入用户列表页,都会从新抓取用户数据。有人可能会说,这不废话吗,react不就这样吗,路由都切换了啊。没错,正常就是应该这样,因此才说Next.js的这个新生命周期牛逼啊。

  • 使用getInitialProps生命周期
// /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

从截图咱们能够很清楚地看到,页面数据经过redux-saga获取,在pages的getIntialProps()里面,代码以下:

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项目也是在生命周期里控制一些数据的获取。可能更习惯吧,不过我认可第二种更牛逼一些,性能也可能更好吧~各取所需吧。

Document

这个组件从我使用的角度来看,做用跟我前几章有个地方的目的是同样的,就是咱们在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里的。

Dynamic Import

其实之前在写服务端渲染项目的时候会遇到不少坑,最多见的就是好比我想引入一些外部组件,这些组件里有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为咱们提供了更多的方法,感兴趣的能够去官网看文档,有四种异步引入的方法,其中还包含只在服务端引入~文档地址

error handling

错误处理,目前不少优秀的脚手架都为咱们提供了错误处理,好比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>
    )
  }
}

复制代码

ok,能够看到,很明显的生效了。虽然效果差很少,可是你若是按照本身的来写,确定是没问题的。哈哈~

Static HTML export

又一个高级功能,它支持咱们把各类路由导出成静态页面,不过你细想其实也没啥大用,毕竟咱们项目都是有逻辑的,导出静态页面也不能操做,哈哈。不过既然是挺牛逼的一个功能,就拿来试试。

  • 第一步,在config文件夹里配置一下页面和路由
exportPathMap: async (defaultPathMap) => {
    return {
      '/home': { page: '/' },
      '/userList': { page: '/user/userList' },
    }
  },
复制代码
  • 第二步,package.json添加export命令
"scripts": {
    ...
    // 新增导出命令
    "export": "yarn build && next export"
  },
复制代码
  • 第三步,运行yarn export命令

    运行完命令以后,根目录下会出现一个out文件夹,真的是很是神奇,里面有页面文件夹和必要的静态资源。

而后咱们打开index.html访问一下应该就是咱们的首页了,首页就是下面这个样子。
emm...这个首页有点奇怪,静态资源和css都不太对劲儿,至于为何我就不去追究了,确定有办法的。不过我只是试试功能,时间有限准备休息了,哈哈。感兴趣的你们本身研究研究。

这里还有一个高级的Next.js项目推送到github page的功能,依赖的也是这个export,不过期间问题我就没写,你们感兴趣的去看看官方demo,应该能够解决的~

总结

写到这里,Next.js踩坑入门系列就写完了。很是感谢有不少小伙伴一直在看,还有一些可爱的小伙伴催更,水平有限,彻底是踩坑集锦,若是能帮助到你们真的很开心。谢谢你们的阅读。接下来准备用Next.js搭一个网站。完成后可能会再写一篇Next.js的建站文章,其余的就不写了,再写就是其余内容啦~
本章节代码地址

项目代码地址,喜欢的给个Star,谢谢米娜桑

相关文章
相关标签/搜索