Next.js ставит пит-рекорд

JavaScript React.js

Я не писал статей почти целый год, в основном потому, что я ленивый, и я очень занят работой. Но я хочу воспользоваться концом года, чтобы опубликовать статью, надеясь быть более усердным в следующем году. На самом деле не то что бы нечего писать, но вникнуть в дело все равно очень сложно, если приходится проверять всякую информацию, то лень в итоге будет.

next.js — это изоморфная библиотека React, во многих статьях она рассматривается как скаффолдинг, что не является невозможным, но лично я считаю, что next.js может больше, чем обычный скаффолдинг, но и у него есть ограничения. За последние два дня я вернулся с работы, чтобы попрактиковаться в разработке nextjs. Наткнулся на несколько ям, а также зафиксировал здесь некоторые успехи.

запросить данные

nextjs не имеет жизненного цикла на стороне клиента, только один статический методgetInitialProps, поэтому данные интерфейса можно получить только этим методом.getInitialPropsВозвращенные данные используются в качестве реквизита компонента.getInitialPropsЕсть два параметра: req и res, которые являются параметрами http, с которыми мы хорошо знакомы. Кроме того, существующие веб-фреймворки Node используют req в качестве входных данных, res в качестве выходных данных и добавляют различные промежуточные программы.

Поэтому я лично считаю, что компонентная форма nextjs слишком подходит для компонентов без состояния. Вот простой пример кода:

// 获取电影列表并渲染
class MovieList extends Component {
	static async getInitialProps(){
  	const data = await getMovieList()
    return {
    	list: data
    }
  }
  
  render () {
  	return (
    	<div>
        {this.props.list.map(movie => {
        	<MovieCard key={movie.id} movie={movie}>
        })}
      </div>
    )
  }
}

Конечно, это только список, выдаваемый сервером в конце, у нас могут быть и другие операции, такие как удаление и загрузка следующей страницы, но эти операции не являются обязательными для работы на сервере. Добавьте несколько соответствующих методов.

управление маршрутом

Маршрутизация nextjs основана на файловой системе, которая достаточно понятна и проста, например, вpagesДобавьте компонент фильма в папку и напишите соответствующий код, мы можем получить доступ/movie-detailэтот маршрут. Сначала я думал, что эта форма маршрутизации слишком элегантна, но спустя долгое время обнаружится много проблем.

вложение маршрута

Первый — это вложенные маршруты, такие как я хочу построить/user/profileЭтот маршрут на самом деле очень легко решить, он находится вpagesСледующие папки вложены последовательно:

image.png

именованный маршрут

Во-вторых, официальной реализации именованных путей нет.Что такое именованный путь? то есть/movie/:idВ этой форме я лично считаю, что nextjs в этом отношении следует за react-router4. Изоморфный фреймворк nuxtjs из vuejs не имеет этой проблемы, потому что сам vue-router также управляет маршрутизацией унифицированным способом. Не будем говорить, хорошо это или плохо, давайте найдем выход.

Основываясь на примерах и документации, которые я нашел, в настоящее время есть два решения:

Используйте запрос вместо именованных путей

Как видно из рисунка ниже, запрос действительно существует в следующем js-маршрутизаторе.

image.png

Затем мы можем написать это, когда нам нужно получить доступ к именованной странице маршрутизации и передать идентификатор с запросом/movie-detail?id=xxx:

// 电影详情页面

class MovieDetail extends Component {
	static async getInitialProps({ req }) {
    const { id } = req.query
    const detail = await getDetail(id) 
  	return {
    	detail
    }
  }
  
  render () {
  	return (
    	// do anything you want
    )
  }
}

индивидуальное серверное решение

Использование запроса для передачи параметров действительно может решить проблему в прошлом, но это слишком неэлегантно и не соответствует идее отдыха. Поэтому следующее сообщество нашло другое решение, используя собственный сервер.

Прежде чем мы поговорим о конкретном решении, мы можем понять, что nextjs не является каркасом для генерации статических ресурсов, и next в конечном итоге должен развертывать службы узлов отдельно. То есть у nextjs на самом деле есть встроенная служба http.Если мы не используем собственный сервер, встроенная служба все равно может помочь нам выполнить задачу рендеринга страниц.

Но если наша нода не только отрисовывает страницы, но и должна писать интерфейсы. Тогда ситуация в это время очень похожа на традиционный режим внутренней разработки: нужно не только написать интерфейс, но и отрисовать страницу.

Очевидно, что встроенный http-сервис nextjs не может справиться с этой задачей, и нам нужен более полный веб-фреймворк. Ведь профессиональные вещи остаются профессионалам. Это когда пользовательский сервер вступает в свои права. В nextjs также есть ряд примеров:

image.png

Так как же пользовательский сервер решает проблему именованных путей? Мы заимствуем возможности рендеринга nextjs. Вот пример экспресса, конкретный код выглядит следующим образом:

// server.js
const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev, quiet: false })
const handle = app.getRequestHandler()
const SERVE_PORT = process.env.SERVE_PORT || 8001

app.prepare().then(() => {
  const server = express()

  server.get('/movie-detail/:id', async (req, res) => {
    // 渲染movie-detail这个组件
    const html = await app.renderToHTML(req, res, '/movie-detail', req.query)
    res.send(html)
  })

  server.get('*', (req, res) => handle(req, res))

  server.listen(SERVE_PORT, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${SERVE_PORT}`)
  })
})

Выше приведен сокращенный код server.js.Конечно, с ним тоже нужно разобраться в компоненте.Код выглядит следующим образом

// /pages/movie-detail.jsx
// 电影详情页面

class MovieDetail extends Component {
	static async getInitialProps({ req }) {
    const { id } = req.params
    const detail = await getDetail(id) 
  	return {
    	detail,
      id
    }
  }
  
  render () {
  	return (
    	// do anything you want
    )
  }
}

кеш страницы

Для приложения csr response не является большой проблемой то, что рендеринг занимает 100 мс, но когда дело доходит до сервера, 100 мс явно невыносимы. Во-первых, рендеринг на стороне клиента не приведет к пустой трате ресурсов сервера и, по сути, не вызовет слишком большой нагрузки на сервер. Но сервер другой. Как только количество пользователей будет большим, это неизбежно вызовет различные проблемы, поэтому кэширование страниц все же необходимо.

Кэширование конкретной страницы не является предметом нашего рассмотрения.Точно так же кеш страницы также должен использовать пользовательский сервер, и конкретная серверная структура должна быть настроена. Здесь lru-cache используется в качестве примера для создания простого кеша страниц, на самом деле нет проблем с заменой его другим, например, Redis.

const dev = process.env.NODE_ENV !== 'production'

const next = require('next')
const express = require('express')
const LRUCache = require('lru-cache')

const ssrCache = new LRUCache({
  max: 1000, // cache item count
  maxAge: 1000 * 60 * 60, // 1 hour
})

const app = next({ dev, quiet: false })

const handle = app.getRequestHandler()

const SERVE_PORT = process.env.SERVE_PORT || 8001

app.prepare().then(() => {
  const server = express()

  server.get('/', async (req, res) => {
    renderAndCache(req, res, '/', { ...req.query })    
  })

  server.get('/movie-detail/:id', async (req, res) => {
    renderAndCache(req, res, '/movie-detail', { ...req.query })
  })

  server.get('*', (req, res) => handle(req, res))

  server.listen(SERVE_PORT, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${SERVE_PORT}`)
  })
})

const getCacheKey = req => `${req.url}`

// 缓存并渲染页面,具体是重新渲染还是使用缓存
async function renderAndCache(req, res, pagePath, queryParams) {
  const key = getCacheKey(req)
  if (ssrCache.has(key)) {
    res.setHeader('x-cache', 'HIT')
    res.send(ssrCache.get(key))
    return
  }

  try {
    const html = await app.renderToHTML(req, res, pagePath, queryParams)

    // Something is wrong with the request, let's skip the cache
    if (res.statusCode !== 200) {
      res.send(html)
      return
    }

    // Let's cache this page
    ssrCache.set(key, html)

    res.setHeader('x-cache', 'MISS')
    res.send(html)
  } catch (err) {
    app.renderError(err, req, res, pagePath, queryParams)
  }
}

вrenderAndCacheэто ключ. Здесь оценивается, кэширована ли страница, и если да, то кэшированный контент выводится напрямую. В противном случае повторите рендеринг. Что касается времени кеша и размера кеша, то это зависит от личных настроек, поэтому здесь я не буду вдаваться в подробности.

Развертывание запущено

Про развёртывание и запуск особо и говорить нечего, если говорить простым языком, то можно напрямую запустить нод-сервис, если посложнее, то будет включать аварийные перезагрузки и т.д., всё зависит от индивидуальных обстоятельств.

Лично я использую супервизор для запуска службы узла.

Суммировать

Сказав так много выше, на самом деле в официальных документах есть соответствующие примеры, так что давайте считать это моим личным рекордом по наступлению на яму.

Что касается nextjs, я думаю, что если это приложение для отображения, его следует использовать с уверенностью. Мало того, что разработка быстрая и крутая, так еще и конфигурация вебпака заблокирована, есть ли причины его не использовать?

Если он функциональный, например ряд компонентов для рисования, в нем нет необходимости. Для холста и т. п. он должен отображаться на стороне клиента. Однако у nextjs нет жизненного цикла, поэтому использование nextjs может быть довольно яма.

Очень рекомендую для личного развития. Зачем тратить свою жизнь на настройку веб-пакета?

Для полностью статических приложений я рекомендую gatsbyjs. Как им пользоваться - это отдельная тема.

Если есть ошибка, коснитесь спрея. над