React服务端渲染框架——next.js

目录

  • next.js介绍

  • Hello World —— next.js

  • 自定义共享组件

  • 创建动态页面

  • 动态路由及数据获取

  • 路由的钩子函数

  • 懒加载

  • CSS-in-JS:style-jsx

  • 外部CSS加载

  • 项目部署


next.js介绍

next.js是一个React框架,可以用于构建服务端渲染APP,渐进式web APP(PWA),导出静态页面,SEO友好等特点;

  1. 服务端渲染
  2. 静态导出
  3. css-in-js:支持CSS-in-JS,同时支持使用style jsx语法;
  4. 构建方便:next.js自动实现代码分割、基于文件系统的路由、热重载等;
  5. 强大的扩展:支持babelwebpack,自定义的路由、next插件等;
  6. 打包、部署方便;

Hello World —— next.js

  1. 使用以下命令初始化项目,生成package.json文件

    npm init
    
  2. 使用以下命令安装依赖

    npm install --save react react-dom next
    
  3. 添加一下脚本命令到package.json:

    "scripts": {
    	"start": "next start",
    	"dev": "next dev",
    	"build": "next build",
    }
    
  4. 在项目根目录下新建目录publicpagescomponents
    public:静态文件
    pages:页面文件
    components:自定义组件

    pages目录下新建文件index.js,编写以下代码:

    const Index = () => (
    	<div>Hello World! next.js.</div>
    )
    
    export default Index;
    

    demo参考

  5. 打开命令行工具,输入以下命令运行项目:

    npm run dev
    

    项目会启动一个在localhost:3000启动一个服务

至此,我们的第一个项目就做好了

由于next.js会根据文件系统自动构建路由,默认的的根目录是pages,所以启动项目后会自动路由到根目录下的index.js文件。直接访问localhost:3000/即可访问到项目。


页面间导航

此节我们构建两个页面,并利用next.js路由在两个页面之间相互跳转。
首先,需要引入next/link用于页面间的导航,引入方式如下

import Link from 'next/link'

使用方式:link需要接收一个href属性,用于指定导航的目的页面,该地址是根据文件系统中子页面文件相对于index.js文件定义的;Link内部仅可以有一个子元素a,用于包裹页面导航锚点;

 <Link href="/about">
   <a>Go to About</a>
 </Link>

具体代码如下:

// pages/index.js
import Link from 'next/link'

export default () => (
  <>
    <h1>Hello, next.js</h1>
    <Link href="/about">
      <a>Go to About</a>
    </Link>
  </>
)
// pages/about.js
import Link from "next/link";

export default () => (
  <>
    <h1>This is About Page.</h1>
    <Link href="/">
      <a>Go Home</a>
    </Link>
  </>
);

demo参考

tips
如果需要构建多级路径,可以通过在pages文件夹下新建文件夹的方式实现。如我们可以在新建文件pages/blog/article.js,具体内容如下:

export default () => <p>This is an article.</p>;

然后修改pages/about.js

import Link from "next/link";

export default () => (
  <>
    <h1>This is About Page.</h1>
    <Link href="/">
      <a>Go Home</a>
    </Link>
    <div>
      <Link href="/blog/article">
        <a>Go to Article</a>
      </Link>
    </div>
  </>
);

此时通过localhost:3000/blog/article可以访问到新页面


自定义共享组件

自定义共享组件并不是next.js的新特性,而是一种通用的编程思维。通过自定义共享组件,我们可以通过组件化提高代码复用率,减少重复代码,提高编程效率;

我们对上一节的demo进行改编,定义一个公用的Header组件,用于三个页面头部;


// components/Header.js

const headerStyle = {
  paddingLeft: "10px",
  color: "green"
};

const headerCss = {
  color: "orange",
  background: "#bfbfbf",
  fontSize: "40px"
};
const Header = props => (
  <div style={headerStyle}>
    <header style={headerCss}>this is header</header>
    {props.children}
  </div>
);

export default Header;

同时在pages/index.js中引入Header组件


import Link from "next/link";
import Header from "../components/Header";

export default () => (
  <>
    <Header>
      <h1>Hello, next.js</h1>
    </Header>
    <Link href="/about">
      <a>Go to About</a>
    </Link>
  </>
);

这样我们就构建了自己的可复用的公共组件

demo参考codesandbox.io


创建动态页面

所谓的创建动态页面的通用思路就是:在URL中通过查询字符串的方式拼接参数,然后在页面中根据获取的不同参数展现不同的页面;

需要构建的页面结构如下

Blogs

 - React基础
 - React Router
 - Redux

首先,我们构建一个组件,通过传入props传入title并在组件中渲染为列表项,列表项的跳转仍然通过Link实现,href属性通过查询字符串的方式传入;

接收参数的子页面中,需要引入路由import {withRouter} from 'next/router
这时我们可以通过withRouter获取其router对象属性,通过router.query.title可以获取传入的查询字符串;
此时组件导出格式为export default withRouter(Blog);

demo参考codesandbox.io

Talk is cheap, show me the code. 具体代码实现如下


// pages/index.js

import Link from "next/link";

const BlogLink = props => (
  <li>
    <Link href={`/blog?title=${props.title}`}>
      <a>{props.title}</a>
    </Link>
  </li>
);

const Index = () => (
  <>
    <h1>Blogs</h1>
    <ul>
      <BlogLink title="React基础" />
      <BlogLink title="React Router" />
      <BlogLink title="Redux" />
    </ul>
  </>
);

export default Index;

// pages/blog.js

import { withRouter } from "next/router";

const Page = ({ router }) => {
  return <div>{`blog page name: ${router.query.title}`}</div>;
};

export default withRouter(Page);


动态路由及数据获取

在上一节,我们介绍了如何通过动态传参进行动态页面渲染。如果我们访问上节的demo中的React Router子页面,此时页面的URL为https://06vfp.sse.codesandbox.io/blog?title=React%20Router,这样的URL看起来又长又乱。

本节中,我们希望通过动态路由将上述URL改为类似以下的形式:

https://06vfp.sse.codesandbox.io/blog/React-Router

这里首先介绍一个用于查询TV show的API接口:

http://www.tvmaze.com/api

我们使用通过关键字查询可以获取节目列表的API:

https://api.tvmaze.com/search/shows?q=batman

获取到节目id后,我们在子页面通过节目id获取节目的更多详细信息,具体接口如下:

http://api.tvmaze.com/shows/:id

首先编写pages/index.js文件

import Link from "next/link";
import fetch from "isomorphic-fetch";

const liStyle = {
  marginBottom: "15px"
};

const Index = props => (
  <>
    <h1>TV Shows - Batman</h1>
    {props.shows.map(show => (
      <li key={show.id} style={liStyle}>
        <Link href="/tv/[id]" as={`/tv/${show.id}`}>
          <a>{show.name}</a>
        </Link>
      </li>
    ))}
  </>
);

Index.getInitialProps = async () => {
  const res = await fetch("https://api.tvmaze.com/search/shows?q=batman");
  const data = await res.json();

  return {
    shows: data.map(item => item.show)
  };
};

export default Index;


next.js提供了一个钩子函数,用于动态获取页面的初始化数据;使用方法是

ComponentInstance.getInitialProps async () => {
...
return dataObj;		// 返回格式为对象
}

然后在componentInstance组件中可以通过props获取到动态获取的数据props.dataObj
而动态路由通过Link标签的as属性进行传值,href属性则和之前保持一致;需要注意的是:hrefas的属性值应该是相互兼容的,也就是符合一定的正则匹配规则。
另外,为了请求可以兼容客户端与服务端,推荐使用更加轻量级的isomorphic-unfetch;请求都是异步的,推荐使用最新的async...await处理;

然后新建文件pages/tv/[id].js

import fetch from "isomorphic-fetch";

const TVShow = ({ show }) => (
  <>
    <img src={show.image.medium} alt="" />
    <h1>{show.name}</h1>
  </>
);

TVShow.getInitialProps = async context => {
  const { id } = context.query;
  const res = await fetch(`http://api.tvmaze.com/shows/${id}`);
  const show = await res.json();

  return { show };
};

export default TVShow;

这里首先需要注意的是文件名[id].js,需要next.js以动态路由方式解析的文件命名必须像这样以[]进行包裹;

另外,在动态路由页面的getInitialProps函数中为了获取父级页面传入的动态参数,可以通过context对象获取,context.query可以获取到查询字符串;


路由的钩子函数

next/router具有几个钩子函数,可以在路由、historyhash等改变时触发某些动作;

常见的几个钩子函数:

  • routeChangeStart
  • routeChangeComplete
  • beforeHistoryChange
  • routeChangeError (404不属于路由错误)
  • hashChangeStart
  • hashChangeComplete

使用和其他事件监听函数基本一致,需要注意的是搞清楚每个钩子函数的触发时机,才能更好的应用;比如routeChangeStart在离开页面时释放一些可能造成内存泄漏的定时器等;或者hash变化结束后将其写入到本地缓存,一定时间内打开页面直接跳到页面指定位置;或者在routeChangeStartrouteChangeComplete中分别开启和关闭加载动画,使页面平滑过渡,避免白屏

执行顺序:
routeChangeStart > beforeHistoryChange > routeChangeComplete;

import Router from 'next/router';

Router.events.on('routeChangeStart', () => {});
Router.events.on('routeChangeComplete', () => {});
...

懒加载

  1. 外部库
    外部库的加载通过异步方式,如下例
    import { useState } from "react";
    
    const Index = () => {
      const [time, beatifyTime] = useState(Date.now());
      const changeTime = async () => {
        const moment = await import("moment");
        beatifyTime(moment.default(Date.now()).format("lll"));
      };
      return (
        <div>
          <p>{time}</p>
          <button onClick={changeTime}>时间格式</button>
        </div>
      );
    };
    
    export default Index;
    
    
  2. 自定义组件的加载
    自动以组件库的懒加载可以通过next/dynamic实现,具体参考codesandbox.io示例,demo如下:
    import dynamic from "next/dynamic";
    
    const About = dynamic(import("./about"));
    
    const Index = () => (
      <div>
        <h1>Lazy Loading</h1>
        <About />
      </div>
    );
    
    export default Index;
    
    

CSS-in-JS:style-jsx

next.js推荐使用css-in-js的样式引入方式,而不是以模块的方式引入CSS,它提出了一个新的CSS-in-JS框架:style-jsx;具体的样式是组件包裹元素的最顶级子元素,整个样式使用{``}进行包裹,具有jsx语法的特点;具体参见官方demo

需要注意的是,默认的style-jsx语法是局部作用域的,不会影响嵌套组件中的元素;如果需要全局生效,可以使用global关键字;

<style jsx global>
{`
	// component style
`}
</style> 


外部CSS加载

  1. 外部CSS加载
    前面提到,next.js支持地是css-in-js语法,如果需要以模块的形式引入外部css,需要借助@zeit/next-css模块;

    首先安装模块@zeit/next-css

    npm install --save @zeit/next-css
    

    然后需要进行相应配置,在项目根目录新建next.config.js,填入以下内容:

    const withCSS = require('@zeit/next-css');
    
    module.exports = withCSS();
    

    然后新建文件public/index.css

    div {
    	color: orange;
    	border: 1px solid red;
    }
    

    最后在·index.js`中引入css

    import './public/index.css'
    

    demo具体参考codesandbox.io


  1. 按需加载antd
    为了避免使用antd时将所有的包都打包进来,影响页面性能,因此我们需要进行按需加载

    首先,安装模块babel-plugin-import;

    npm install --save babel-plugin-import
    

    在根目录新建文件.babelrc

    {
    	"presets": ["next/babel"],
    	"plugins": [
    		[
    			"import",
    			{
    				"libraryName": "antd",
    				"style": "css",
    			}
    		]
    	]
    }
    

    配置好后,重启服务运行即可;

如果在dev环境运行或最后打包时出现错误,需要单独解决;next.js提供了一个可以全局使用的文件出口,在dev环境运行时可以看到network中已经有_app.js文件,我们根据需求可以定义自己的全局属性、引入全局样式,最终都会被打包到_app.js中;

因此你阔以新建文件pages/_app.js,然后在该文件中引入antd.css,然后编写内容:

import App from 'next/app';
import 'antd/dist/antd.css';

你甚至可以在该文件中引入一些全局样式等;

项目部署

  1. 配置文件package.json
    "scripts": {
    	"start": "next start",
    	"dev": "next dev",
    	"build": "next build",
    }
    
  2. 运行以下命令进行打包
    npm run build
    
  3. 一次构建,多次运行:
    // 同时启动多个服务
    npm start -- --port=8800
    npm start -- --port=3000
    

参考文献
4. https://nextjs.org/#features
5. https://nextjs.org/learn/basics/create-dynamic-pages
6. 技术胖next.js博客
7. https://github.com/zeit/next.js/tree/canary/examples/with-ant-design

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil-

你们的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值