- nuxtjs
- 下载项目
- 1.在集成的服务器端框架之间进行选择:
- 2. 选择您喜欢的 UI 框架:
- 3.选择您喜欢的测试框架:
- 4.选择你想要的 Nuxt 模式 (
UniversalorSPA) - axios module 以轻松地将 HTTP 请求发送到您的应用程序中。">5.添加 axios module 以轻松地将 HTTP 请求发送到您的应用程序中。
- EsLint 以在保存时代码规范和错误检查您的代码。">6.添加 EsLint 以在保存时代码规范和错误检查您的代码。
- Prettier 以在保存时格式化/美化您的代码。">7.添加 Prettier 以在保存时格式化/美化您的代码。
- pages 路由配置
- 跨域
- 附带token请求头
- vuex - store仓库
- nextjs
nuxtjs
nuxt是什么?
nuxt是一个专注于ui渲染的应用框架,你可以使用Nuxt创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码
使用nuxt你可以快速搭建一个项目,而省略了繁琐的webpack babel sass vue-loader等等配置,同时nuxt还提供了服务器端渲染功能,
所以nuxt提供的是一种快速搭建项目的能力,
我们可以从nuxt中学到什么?
1:vue项目结构
nuxt推荐的项目结构是
assets资源目录,
components组件目录,
layouts母版页, 这个之前没写过
pages页面,
plugins插件:我之前都是把vue插件直接写在main.js中
static静态文件,
Store vuex目录,
middleware中间件目录 在每一个页面加载之前调用 如果是spa模式 就是vue的beforeRouter方法
我们可以在上面的基础上扩展新的文件:
filter 数据过滤文件 提供统一的数据整理能力
2:nuxt省区了所以的配置,同时他给我们提供了一个配置文件的入口,以保证如果我们需要特定化配置,不至于没有地方进行配置
3:为支持服务器端渲染 nuxt提供了asyncData fetch nuxtServerInit功能
4:nuxt为我们提供了去扩展nuxt能力的方法 就是模块
5:nuxt提出了view层中间件的想法:中间件的思路是在每一个页面加载和渲染之前执行的方法,我们可以用来执行鉴权或其他方法
6:nuxt提出了layout的想法,我们之前都没有想过去使用模板来定制化我们的界面
所以本质上,nuxt提供一个基本的项目结构,并把服务器端渲染集成进来,同时将当前前端发展的最新技术应用于nuxt中,如预加载 页面过度效果等
下载项目
- npx create-nuxt-app 项目名
-
1.在集成的服务器端框架之间进行选择:
None (Nuxt 默认服务器)
- Express
- Koa
- Hapi
- Feathers
- Micro
- Fastify
Adonis (WIP)
2. 选择您喜欢的 UI 框架:
None (无)
- Bootstrap
- Vuetify
- Bulma
- Tailwind
- Element UI
- Ant Design Vue
- Buefy
- iView
-
3.选择您喜欢的测试框架:
None (随意添加一个)
- Jest
-
4.选择你想要的 Nuxt 模式 (
UniversalorSPA)5.添加 axios module 以轻松地将 HTTP 请求发送到您的应用程序中。
6.添加 EsLint 以在保存时代码规范和错误检查您的代码。
7.添加 Prettier 以在保存时格式化/美化您的代码。
-
pages 路由配置
根据目录结构会自动更新,
一级路由目录结构
-pages<br /> -index.vue<br /> -user.vue<br />会自动根据目录生成路由配置表
[{path: "/",component: component,name: "index",},{path: "/user",component: component,name: "user",}}]
二级路由目录结构
-index.vue<br /> -user.vue<br /> -user | 文件夹<br /> -login.vue
[{path: "/",component: component,name: "index",},{path: "/user",component: component,name: "user",children:[{path: "login",component: component,name: "user-login",}]}]
动态路由配置
-detail | 文件夹
-_id.vue[{path: "/detail/:id?",component: component,name: "detail-id",}]
跨域
在nuxt.config.js文件下配置
modules: [// https://go.nuxtjs.dev/axios"@nuxtjs/axios","@nuxtjs/proxy"],axios: {// See https://github.com/nuxt-community/axios-module#optionsproxy: true, // 表示开启代理prefix: "/api", // 表示给请求url加个前缀 /apicredentials: true // 表示跨域请求时是否需要使用凭证},proxy: {"/api": {target: "http://localhost:7001", // 目标接口域名changeOrigin: true, // 表示是否跨域pathRewrite: {"^/api": "" // 把 /api 替换成‘’}}}
在页面调用时
this.$axios.get('/list).then(res=>{//请求结果})
附带token请求头
nuxt.config.js 配置文件
plugins :[{src:'~/plugins/axios',mode:'client'}]
在plugins文件夹下面创建axios.js
export default function({$axios,redirect}){$axios.onRequest(config=>{console.log("Making request to"+config.url)//从本地获取tokenlet token = localStorage.getItem("token")//如果token不为空,将token放入请求中if(token){config.headers.authorization = token}return config})//错误处理机制$axios.onError(error => {const code = parseInt(error.response&&error.response.status)if(code===400){redirect('/400')}})}
vuex - store仓库
- 模块方式:
store目录下的每个.js文件会被转换成为状态树指定命名的子模块 (当然,index是根模块) - Classic(不建议使用):
store/index.js返回创建 Vuex.Store 实例的方法。
store
-user.js
//user.jsexport const state = () => ({count: 1});export const mutations = {add(state) {state.count = state.count + 1;}};export const actions = {xx({ commit }) {commit("add");}};
在页面中 我们通过this.$store.state.user.count 拿到仓库数据
通过 this.$store.commit(‘user/add’)调用mutations的方法
通过this.$store.dispatch(‘user/xx’)调用actions 异步方法
nextjs
https://www.nextjs.cn/docs/getting-started
基于文件路径的路由
页面
一般前端web应用都可以简化为,基于路由的页面和API两部分。Next的路由系统基于文件路径自动映射,不需要做中性化的配置。
一般都约定在根目录pages文件夹内:
./pages/index.tsx—> 首页/./pages/admin/index.tsx—>/admin./pages/admin/post.tsx—>/admin/postimport styles from './style.module.css'function About() {return <div>About</div>}export default About
默认导出一个React的组件,Next就会帮你默认生成对应路由的页面。
你不用关心head里面资源如何配置加载
可以像SPA应用一样,使用css-in-js,css module,less,sass等样式
import方式。页面间的导航
import Link from 'next/link'function Home() {return (<ul><li><Link href="/about"><a>About Us</a></Link></li></ul>)}export default Home
增加Head
import Head from 'next/head'function About() {return (<div><Head><title> Hipo Log - {props.post?.name ?? ''}</title></Head>content</div>);}export default About
Dynamic import 代码拆分
Next也支持ES2020的dynamic import()语法,可以拆分代码,或者有些第三方组件依赖浏览器API时候精致服务端渲染(
ssr: false)import dynamic from 'next/dynamic'const DynamicComponentWithCustomLoading = dynamic(() => import('../components/hello'),{loading: () => <p>...</p>,ssr: false})function Home() {return (<div><Header /><DynamicComponentWithCustomLoading /><p>HOME PAGE is here!</p></div>)}export default Home
注意:在页面代码要谨慎import代码!!
越多引入,上线访问后加载的js就越多,特别是下面钩子函数要注意,不要引入多余代码API
API类型的路由约定在
./pages/api文件夹内,next会自动映射为/api/*路径的APIimport { NextApiRequest, NextApiResponse } from 'next'export default (req: NextApiRequest, res: NextApiResponse) => {res.status(200).json({ name: 'John Doe' })}
请求方法通过req中取到。
如此你就可以很轻松的生成一个API。动态路由
正常的应用,都有动态路由,next中讨巧使用文件命名的方式来支持。
./pages/post/create.js—>/post/create./pages/post/[pid].js—>/post/1,/post/abc等,但是不会匹配/post/create./pages/post/[...slug].js—>/post/1/2,/post/a/b/c等,但是不会匹配/post/create,/post/abc
动态参数可以通过req.query对象中获取({ pid }, { slug: [ 'a', 'b' ] }),在页面中可以通过router hook获取:
import { useRouter } from 'next/router';function About() {const router = useRouter();const { bID, pID } = router.queryreturn <div>About</div>}
页面SSR 钩子以及SSG
大部分的应用内容,都不是纯静态的,我们需要数据查询才能渲染那个页面,而这些就需要同构钩子函数来满足,有了这些钩子函数,我们才可以在不同需求下作出极佳体验的web应用。
getServerSideProps(SSR)每次访问时请求数据
页面中export一个async的getServerSideProps方法,next就会在每次请求时候在服务端调用这个方法。
- 方法只会在服务端运行,每次请求都运行一边
getServerSideProps方法 - 如果页面通过浏览器端
Link组件导航而来,Next会向服务端发一个请求,然后在服务端运行getServerSideProps方法,然后返回JSON到浏览器。
getServerSideProps方法主要是升级了9.3之前的getInitialProps方法
9.3之前的getInitialProps方法有一个很大的缺陷是在浏览器中req和res对象会是undefined。也就是使用它的页面,如果是浏览器渲染你需要在组件内再显示地请求一次。开发体验不太好。 如果没有特殊问题,建议使用getServerSideProps替代getInitialProps方法。 示例:
import { GetServerSideProps, NextPage } from 'next'interface PostProps {list: Post[]}const App: NextPage<PostProps> = props => {return <div></div>}export const getServerSideProps: GetServerSideProps<PostProps> = async context => {const list = await context.req.service.post.getPost(context.params.postID)return {props: {list}}}export default App
getStaticProps和getStaticPaths(SSG)构建时请求数据
所谓的SSG也就是静态站点生成,类似像hexo或者gatsbyjs都是在build阶段将页面构建成静态的html文件,这样线上直接访问HTML文件,性能极高。
Next.js 再9.0的时候引入了自动静态优化的功能,也就是如果页面没有使用getServerSideProps和getInitialProps方法,Next在build阶段会生成html,以此来提升性能。
但是正如上文说的,一般应用页面都会需要动态的内容,因此自动静态优化局限性很大。
Next 在9.3中更近了一步,引入了getStaticProps和getStaticPaths方法来让开发者指定哪些页面可以做SSG优化。
- 使用
getStaticProps方法在build阶段返回页面所需的数据。 如果是动态路由的页面,使用
getStaticPaths方法来返回所有的路由参数,以及是否需要回落机制。export async function getStaticPaths() {// Call an external API endpoint to get postsconst res = await fetch('https://.../posts')const posts = await res.json()// Get the paths we want to pre-render based on postsconst paths = posts.map(post => ({params: { id: post.id },}))// We'll pre-render only these paths at build time.// { fallback: false } means other routes should 404.return { paths, fallback: true };}export const getStaticProps: GetStaticProps<InitProps> = async ({ params }) => {const data = await fetch(`http://.../api/p/${params.bookUUID }/${params.postUUID }`);return {props: {post: data,},};};
使用非常的简单,需要注意的是:
getStaticPaths方法返回的fallback很有用:如果fallback是false,访问该方法没有返回的路由会404- 但是如果不想活着不方便在build阶段拿到路由参数,可以设置
fallback为true,Next在访问build中没有的动态路由时候,先浏览器loading,然后服务端开始build该页面的信息,然后再返回浏览器渲染,再次访问该路由该缓存就会生效,很强大!! - 静态缓存目前没办法很灵活的更新!!,例如博客内容在build或者fallback生效之后发生更改,目前没办法很方便的替换缓存。
如何选择SSR还是SSG?
- 如果页面内容真动态(例如,来源数据库,且经常变化), 使用
getServerSideProps方法的SSR。 - 如果是静态页面或者伪动态(例如,来源数据库,但是不变化),可以酌情使用SSG。
上面就是Next.js中主要的部分了,下面是一些可能用到的自定义配置。
自定义App
用./pages/_app.tsx来自定义应用App,可以配置全局的css,或者getServerSideProps方法来给每个页面添加数据。
function MyApp({ Component, pageProps }) {return <Component {...pageProps} />}export default MyApp
自定义Document
用./pages/_document.tsx来自定义页面的Document,可以配置页面html,head属性,或者使用静态getInitialProps方法中renderPage方法来包括整个react 应用。
import Document, { Html, Head, Main, NextScript } from 'next/document'class MyDocument extends Document {static async getInitialProps(ctx) {const initialProps = await Document.getInitialProps(ctx)return { ...initialProps }}render() {return (<Html><Head /><body><Main /><NextScript /></body></Html>)}}export default MyDocument
<Html>, <Head />, <Main /> 和 `<NextScript />都是必须的。`
- 上述app和document中使用
getServerSideProps或者getInitialProps方法让整个应用都无法自动静态优化 - 上述app和document中在浏览器中不执行,包括react的hooks或者生命周期函数。
自定义构建
Next自然也可以自定义构建,根目录使用next.config.js来配置webpack,可以用来支持less编译,按需加载,path alias等。
下面是Hipo Log中的配置,支持了Antd design的按需加载。const withLess = require('@zeit/next-less');const withCss = require('@zeit/next-css');const path = require('path');const MiniCssExtractPlugin = require('mini-css-extract-plugin');const fixMiniCss = (nextConfig = {}) => {return Object.assign({}, nextConfig, {webpack(_config, options) {// force react-scripts to use newer version of `mini-css-extract-plugin` and ignore css ordering warnings// (related to issue: https://github.com/facok/create-react-app/issues/5372)let config = _config;if (typeof nextConfig.webpack === 'function') {config = nextConfig.webpack(_config, options);}let miniCssExtractOptions = {};for (let i = 0; i < config.plugins.length; i++) {const p = config.plugins[i];if (!!p.constructor &&p.constructor.name === MiniCssExtractPlugin.name) {miniCssExtractOptions = { ...p.options, ignoreOrder: true };// config.plugins[i] = new MiniCssExtractPlugin(miniCssExtractOptions);break;}}// config.plugins.push(new MiniCssExtractPlugin(miniCssExtractOptions));config.resolve.alias = {...config.resolve.alias,'@pages': path.join(__dirname, './pages'),'@components': path.join(__dirname, './components'),'@entity': path.join(__dirname, './dbEntity'),'@services': path.join(__dirname, './services'),'@lib': path.join(__dirname, './lib'),'@util': path.join(__dirname, './services/util'),'@type': path.join(__dirname, './app.type'),};return config;},});};module.exports = fixMiniCss(withLess({cssModules: true,...withCss({webpack(config, options) {const { dev, isServer } = options;if (isServer) {const antStyles = /antd\/.*?\/style\/css.*?/;const origExternals = [...config.externals];config.externals = [(context, request, callback) => {if (request.match(antStyles)) return callback();if (typeof origExternals[0] === 'function') {origExternals[0](context, request, callback);} else {callback();}},...(typeof origExternals[0] === 'function' ? [] : origExternals),];config.module.rules.unshift({test: antStyles,use: 'null-loader',});}return config;},}),}));
自定义服务
Next也支持node启动,以此来和其他框架配合实现更复杂的服务端功能,譬如Hipo Log 使用它来绑定数据库typeorm等。/ server.jsconst { createServer } = require('http')const { parse } = require('url')const next = require('next')const dev = process.env.NODE_ENV !== 'production'const app = next({ dev })const handle = app.getRequestHandler()app.prepare().then(() => {createServer((req, res) => {// Be sure to pass `true` as the second argument to `url.parse`.// This tells it to parse the query portion of the URL.const parsedUrl = parse(req.url, true)const { pathname, query } = parsedUrlif (pathname === '/a') {app.render(req, res, '/b', query)} else if (pathname === '/b') {app.render(req, res, '/a', query)} else {handle(req, res, parsedUrl)}}).listen(3000, err => {if (err) throw errconsole.log('> Ready on http://localhost:3000')})})
