冷冷的鹰眼 發表於 2020-7-4 20:02:00

next.js的框架

<h2 id="2-nextjs服务器端渲染">2. Next.js服务器端渲染</h2>
<p><strong>学习目标</strong></p>
<ul>
<li><strong>了解Next.js的作用</strong></li>
<li><strong>掌握Next.js中的路由</strong></li>
<li><strong>掌握Next.js中布局组件的创建</strong></li>
<li><strong>掌握Next.js中的静态文件服务</strong></li>
<li><strong>掌握Next.js中获取页面数据的方法</strong></li>
<li><strong>掌握Next.js中组件样式的书写</strong></li>
<li><strong>使用Next.js完成豆瓣电影案例</strong></li>
<li><strong>能够自定义头部元素head</strong></li>
</ul>
<h3 id="21-什么是nextjs">2.1 什么是Next.js?</h3>
<p>Next.js官网</p>
<p>Next.js是一个基于React的一个服务端渲染简约框架。它使用React语法,可以很好的实现代码的模块化,有利于代码的开发和维护。</p>
<p>Next.js带来了很多好的特性:</p>
<ul>
<li>默认服务端渲染模式,以文件系统为基础的客户端路由</li>
<li>代码自动分割使页面加载更快</li>
<li>以webpack的热替换(HMR)为基础的开发环境</li>
<li>使用React的JSX和ES6的module,模块化和维护更方便</li>
<li>可以运行在Express和其他Node.js的HTTP 服务器上</li>
<li>可以定制化专属的babel和webpack配置</li>
</ul>
<p>使用服务器端渲染好处:</p>
<ul>
<li>对SEO友好</li>
<li>提升在手机及低功耗设备上的性能</li>
<li>快速显示首页</li>
</ul>
<h3 id="22-nextjs初体验">2.2 Next.js初体验</h3>
<pre><code class="language-bash">mkdir hello-next
cd hello-next
npm init -y
npm install --save react react-dom next
mkdir pages
</code></pre>
<p>配置package.json中的scripts属性</p>
<pre><code class="language-js">{
"scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
}
}
</code></pre>
<p>此时<code>npm run dev</code> 会得到一个404页面</p>
<p>创建一个pages/index.js</p>
<pre><code class="language-js">const Index = () =&gt; (
&lt;div&gt;
    &lt;p&gt;Hello Next.js&lt;/p&gt;
&lt;/div&gt;
)

export default Index
</code></pre>
<p>创建一个pages/next-route/teacher.js页面</p>
<pre><code class="language-js">const Teacher = () =&gt; (
&lt;div&gt;
    &lt;p&gt;教师页面&lt;/p&gt;
&lt;/div&gt;
)

export default Teacher
</code></pre>
<h3 id="23-页面导航">2.3 页面导航</h3>
<h4 id="231-路由跳转">2.3.1 路由跳转</h4>
<ol>
<li>Link组件</li>
</ol>
<ul>
<li>Link组件内不能直接写文字,必须使用标签包裹,标签可以是任何标签,但是必须只能保证Link组件内只有一个子元素;</li>
<li>给Link组件设置样式不会生效,因为Link组件是一个HOC(高阶组件),但是可以给它里面的子元素设置样式;</li>
</ul>
<pre><code class="language-js">import Link from 'next/link'
&lt;Link href="/teachers"&gt;
&lt;a&gt;教师页面&lt;/a&gt;
&lt;/Link&gt;
</code></pre>
<p>组件<code>&lt;Link&gt;</code>可接收 URL 对象,而且它会自动格式化生成 URL 字符串。例如:</p>
<pre><code class="language-js">&lt;Link href={{pathname: '/teachers', query: {id: 1}}}&gt;
&lt;a&gt;教师页面&lt;/a&gt;
&lt;/Link&gt;
</code></pre>
<ol start="2">
<li>命名式路由</li>
</ol>
<pre><code class="language-javascript">import Router from 'next/router'

export default () =&gt; (&lt;div&gt;&lt;span onClick={() =&gt; Router.push('/teacher')}&gt;教师&lt;/span&gt;&lt;/div&gt;)
</code></pre>
<p>URL对象语法:</p>
<pre><code class="language-js">Router.push({pathname: '/teacher', query: {id: 1}})
</code></pre>
<p>注意:如果没有匹配到的话,默认会去找<code>_error.js</code>中定义的组件; 路由跳转不会向服务器发送请求页面的请求。</p>
<h4 id="232-创建组件">2.3.2 创建组件</h4>
<ol>
<li>
<p>普通组件</p>
<p>组件的创建可以在任何的文件夹下面,但是不要放在pages下面,因为组件并不需要url</p>
</li>
<li>
<p>布局组件</p>
<p>利用this.props.children</p>
</li>
<li>
<p>全局布局组件, 创建_app.js,模板入下:</p>
<pre><code class="language-js">import React from 'react'
import App, {Container} from 'next/app'

class Layout extends React.Component {
render () {
    const {children} = this.props
    return &lt;div className='layout'&gt;
      {children}
    &lt;/div&gt;
}
}

export default class MyApp extends App {
render () {
    const {Component, pageProps} = this.props
    return &lt;Container&gt;
      &lt;Layout&gt;
      &lt;Component {...pageProps} /&gt;
      &lt;/Layout&gt;
    &lt;/Container&gt;
}
}
</code></pre>
</li>
</ol>
<h4 id="233-query-strings">2.3.3 query strings</h4>
<ol>
<li>创建一个带query的链接</li>
<li>如果你想应用里每个组件都处理路由对象,你可以使用<code>withRouter</code>高阶组件。从next/router中引入withRouter,注入路由对象到Next.js中的组件作为组件属性,从而获取query对象</li>
<li>组件使用props.router.query.xxx获取query strings</li>
</ol>
<h4 id="234-clean-urls-with-route-masking">2.3.4 Clean URLs with Route Masking</h4>
<p>通过as属性,给browser history来个路由掩饰,但是按刷新按钮路由就找不到了,因为服务器回去重新找/p/xxxx页面,但是实际上此时并不存在xxxx页面</p>
<pre><code class="language-html">    // /post?title=xxxx 会变成 /p/xxxx
        &lt;Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}&gt;
      &lt;a&gt;{props.title}&lt;/a&gt;
    &lt;/Link&gt;
</code></pre>
<h4 id="235-服务器端支持clean-urls">2.3.5 服务器端支持Clean URLs</h4>
<p>创建自定义服务</p>
<ol>
<li>安装express <code>npm install --save express</code></li>
<li>创建server.js,添加如下内容</li>
</ol>
<pre><code class="language-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(() =&gt; {
      const server = express()

      server.get('*', (req, res) =&gt; {
      return handle(req, res)
      })

      server.listen(3000, (err) =&gt; {
      if (err) throw err
      console.log('&gt; Ready on http://localhost:3000')
      })
    })
    .catch((ex) =&gt; {
      console.error(ex.stack)
      process.exit(1)
    })
</code></pre>
<ol start="3">
<li>修改package.json文件中scripts字段</li>
</ol>
<pre><code class="language-js"> "scripts": {
   "dev": "node server.js",
   "build": "next build",
   "start": "NODE_ENV=production node server.js"
}
</code></pre>
<ol start="4">
<li>创建自定义路由</li>
</ol>
<pre><code class="language-js">server.get('/teacher/:id', (req, res) =&gt; {
    const actualPage = '/teacher/detail'
    const queryParams = { id: req.params.id }
    app.render(req, res, actualPage, queryParams)
})
</code></pre>
<h3 id="24-静态文件服务">2.4 静态文件服务</h3>
<p>项目的根目录新建 <code>static</code> 文件夹,代码通过 <code>/static/</code> 开头的路径来引用此文件夹下的文件,例如:</p>
<p><code>export default () =&gt; &lt;img src="/static/logo.png" /&gt;</code></p>
<h3 id="25-获取页面数据">2.5 获取页面数据</h3>
<ol>
<li>下载isomorphic-unfetch : <code>npm install --save isomorphic-unfetch</code></li>
<li>引入 <code>import fetch from 'isomorphic-unfetch';</code></li>
</ol>
<p>使用异步静态方法<code>getInitialProps</code>获取数据,此静态方法能够获取所有的数据,并将其解析成一个 <code>JavaScript</code>对象,然后将其作为属性附加到 <code>props</code>对象上</p>
<p>当页面初次加载时,<code>getInitialProps</code>只会在服务端执行一次。<code>getInitialProps</code>只有在路由切换的时候(如<code>Link</code>组件跳转或命名式路由跳转)时,客户端的才会被执行。</p>
<p>注意:getInitialProps <strong>不能</strong> 在子组件上使用,只能使用在<code>pages</code>页面中。</p>
<pre><code class="language-js">// 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
}
}
</code></pre>
<p>如果你的组件是一个类组件,你需要这样写:</p>
<pre><code class="language-javascript">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 (
      &lt;div&gt;
      {this.props.movieList.map(item =&gt; (
          &lt;p key={item.id}&gt;{item.title}&lt;/p&gt;
      ))}
      &lt;/div&gt;
    )
}
}
</code></pre>
<p><code>getInitialProps</code>: 接收的上下文对象包含以下属性:</p>
<ul>
<li>
<p><code>pathname</code>: <code>URL</code>的 <code>path</code>部分</p>
</li>
<li>
<p><code>query</code>: <code>URL</code>的 <code>query string</code>部分,并且其已经被解析成了一个对象</p>
</li>
<li>
<p><code>asPath</code>: 在浏览器上展示的实际路径(包括 <code>query</code>字符串)</p>
</li>
<li>
<p><code>req</code>: <code>HTTP request</code> 对象 (只存在于服务器端)</p>
</li>
<li>
<p><code>res</code>: <code>HTTP response</code> 对象 (只存在于服务器端)</p>
</li>
<li>
<p><code>jsonPageRes</code>: 获取的响应数据对象 Fetch Response (只存在于客户端)</p>
</li>
<li>
<p><code>err</code>: 渲染时发生错误抛出的错误对象</p>
</li>
</ul>
<pre><code class="language-js">// 在另外一个组件中,可以使用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}
}
</code></pre>
<h3 id="26-组件样式">2.6 组件样式</h3>
<ol>
<li>
<p>css样式文件</p>
</li>
<li>
<p>css in js</p>
</li>
<li>
<p>styled-jsx</p>
</li>
</ol>
<ul>
<li>
<p><strong>scoped</strong></p>
<p>如果添加了 <code>jsx</code>属性,只作用于当前组件,不包括子组件</p>
</li>
</ul>
<pre><code class="language-js">   &lt;style jsx&gt;{`
   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;
   }
   `}&lt;/style&gt;

</code></pre>
<ul>
<li>
<p><strong>global</strong></p>
<p>作用于当前组件,包括子组件</p>
</li>
</ul>
<pre><code class="language-css">&lt;style jsx global&gt;{``}&lt;/style&gt;
</code></pre>
<h3 id="27-豆瓣电影案例">2.7 豆瓣电影案例</h3>
<p><strong>接口</strong></p>
<p>获取电影列表:<code>http://localhost:3301/in_theaters</code>    (in_theaters可以替换为coming_soon及top250)</p>
<p>获取电影详情:<code>http://localhost:3301/in_theaters/1?_embed=details</code></p>
<h4 id="271-豆瓣电影首页">2.7.1 豆瓣电影首页</h4>
<p><code>MovieHeader</code>组件样式</p>
<pre><code class="language-scss">    .movie-header {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
    }
    ul {
      display: flex;
      justify-content: space-around;
      align-items: center;
      padding: 15px 0;
      background-color: #1e2736;
      margin: 0;
    }
    li {
      list-style: none;
      line-height: 30px;
      height: 30px;
    }
    li a {
      color: white;
    }
    li a:hover {
      color: red;
    }
</code></pre>
<h4 id="272-豆瓣电影列表页">2.7.2 豆瓣电影列表页</h4>
<pre><code class="language-scss">.movie-type {
display: flex;
flex-direction: column;
align-items: center;
}
.movie-box {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0;
padding: 10px 0;
width: 40%;
box-shadow: 0 0 10px #bbb;

}
.movie-box:hover {
box-shadow: rgba(0,0,0,0.3) 0px 19px 60px;
}
</code></pre>
<h4 id="273-豆瓣电影详情页">2.7.3 豆瓣电影详情页</h4>
<pre><code class="language-scss">.detail {
width: 40%;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
box-shadow: 0 0 10px #bbb;
}
.detail-box {
text-align: center;
}
</code></pre>
<h3 id="28-自定义头部元素head">2.8 自定义头部元素head</h3>
<p>引入next/head</p>
<pre><code class="language-js">export default () =&gt; {
    &lt;div&gt;
            &lt;Head&gt;
                      &lt;meta name="keywords" content="" key="viewport" /&gt;
            &lt;/Head&gt;
    &lt;/div&gt;
}
</code></pre>
<p>注意:在卸载组件时,<code>&lt;head&gt;</code>的内容将被清除。请确保每个页面都在其<code>&lt;head&gt;</code>定义了所需要的内容,而不是假设其他页面已经加过了</p><br><br>
来源:https://www.cnblogs.com/wangyuwei5955616/p/13236348.html
頁: [1]
查看完整版本: next.js的框架