[前端]Next.js

手写Next.js

 

next基础

简介和目录结构

 

 

app.jsx

 配置

.env

next.config.js /mjs

样式的编写和静态资源导入

 

 

 

路面路径映射

编程导航

next支持编程导航,但是不利于SEO

一般在App内全局监听
import { useEffect } from "react";
 const router = useRouter();
  useEffect(() => {
    const handler = (url:any) => {
      //url 当前访问的路径
      console.log(url);
      
    };
    router.events.on("routeChangeStart",handler);
    return () => {
      router.events.off("routeChangeStart",handler);
    };
  }, [router]);

动态路由

 获取值

  const router=useRouter()
    //这种情况最好像nuxt那样路由拦截一下
    //都使用query,如果重名,动态路由优先,查询字符串对应的字段会丢失
  const {id} =  router.query

404 Page

跟nuxt的错误页面相同,文件名为[...slug].tsx,这里的slug可以是任意的名称

const Error : FC<IProps>=function(props){
    const router=useRouter()
    const { slug} = router.query
      return (<div>
         {JSON.stringify(slug)}
      </div>)         
}

 next核心

中间件

根目录创建middleware.ts

import { NextResponse, type NextRequest } from "next/server";
//这个中间件只在服务端运行
export function middleware(req:NextRequest){
   console.log(req.url);      //路径 
   console.log(req.nextUrl); //源,协议,路径
//    return NextResponse.next() //跟不反悔效果一样
    // const cookie=req.cookies.get("token")
    // if(!cookie && req.nextUrl.pathname!="login"){
    //     return NextResponse.redirect(new URL("/login",req.nextUrl.origin))
    // }
    if(req.nextUrl.pathname.startsWith("/bane/ds")){
        
    }
   
} 
//过滤想要得到的请求,支持正则表达式
export const config={
    matcher:["/(?!_next/static|api|favicon.ico.*)"]
}

layout

 简单实现layout组件

import { memo,ReactElement} from "react"
import type {FC} from "react"
import MianHeader from "../mian-header"
import MainFooter from "../main-footer"
export interface IProps{
    children?:ReactElement
}
const Layout : FC<IProps>=function(props){
      const { children  } = props
      return (<div>
        <MianHeader></MianHeader>
         {children} 这个是页面内容
        <MainFooter></MainFooter>
      </div>)         
}
export default memo(Layout)
Layout.displayName="Layout"

 在App内包裹呈现的组件即可

export default function App({ Component, pageProps }: layoutAppProps) {
    return (
      <Layout>
        <Component {...pageProps} />
      </Layout>
    )
}
  
 

某些页面没layout

我们我可以通过Component的displayName,每个组件都可以挂一个这个属性,我们根据名字来判断展示效果

export default function App({ Component, pageProps }: layoutAppProps) {
  if(Component.displayName == "User"){
    return    <Component {...pageProps} />
  } else{
    return (
      <Layout>
        <Component {...pageProps} />
      </Layout>
    );
  }
}

抽取

但是如果我们定制度较高,主页面就太多的判断的

这里我们扩展了组件类型,增加一个调用前面,这个方法返回一个被layout包裹的组件

import Layout from "@/components/layout"
import { memo,ReactElement} from "react"
import type {FC} from "react"
export interface IProps{
    children?:ReactElement
}
export interface ComLaout{
    getlayout: (el:ReactElement)=>ReactElement
}
const User : FC<IProps> & ComLaout=function(props){
     return (<> 
        this is a user
     </> )   
}
User.getlayout=(page:ReactElement)=>{
  return (<Layout>{page}</Layout>)
}
export default User
User.displayName="User"

App.txs, 这里同样扩展了类型

import Layout from "@/components/layout";
import "@/styles/globals.css";
import { NextPage } from "next";
import type { AppProps } from "next/app";
import Link from "next/link";
import { ReactElement } from "react";
type layoutcomponent= NextPage & {
  getlayout?:(e:ReactElement) =>ReactElement
}
type layoutAppProps = AppProps & {
  Component:layoutcomponent
}
export default function App({ Component, pageProps }: layoutAppProps) {
   let commpone = Component.getlayout ? Component.getlayout : ( e:ReactElement)=>({e})
   return commpone(<Component {...pageProps} />)
  
}

嵌套路由

伪造嵌套路由

在page中实现嵌套路由,只能找一个公共的layout,在layout内实现跳转的逻辑

嵌套路由

在新版中其实也是通过同一个layout来实现二级路由,每一个路径都可以有一个layout组件,子包下会复用父级的layout从而达成嵌套路由,但是现在还有很多问题

生命周期

Class Components,函数式组件只会调用方法本身

 axios

图和代码有一点不同,一个是返回结果直接拿data,一个是拿到全部结果

 

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse }  from "axios";
const  BASE_URL=""
class niurequest{
    instance: AxiosInstance
    constructor(config : AxiosRequestConfig){
        this.instance=axios.create(config)
    }
    request<T=any>(config:AxiosRequestConfig):Promise<AxiosResponse<T>>{
        return new Promise((resolve,reject)=>{
            this.instance.request<T>(config).then(res=>resolve(res)).catch(err=>reject(err))
        })
    }
    get<T>(config:AxiosRequestConfig):Promise<T>{
        return this.request<T>({...config,method:"GET"})
    }
    post<T>(config:AxiosRequestConfig):Promise<T>{
        return this.request<T>({...config,method:"POST"})
    }
}
export default new niurequest({
    baseURL:BASE_URL,
    timeout:3000
})

 后端接口

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
type Data = {
  name: string;
};
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>,
) {
  console.log(req.body);
  console.log(req.query);
  console.log(req.method)
  res.status(200).send({ name: "John Doe" });
  // res.status(405).end()
}

预渲染

 不依赖外部文件的页面, 正常打包就是SSG,那如果我们的页面生成依赖数据,甚至依赖动态路由的数据


import { memo,ReactElement} from "react"
import type {FC} from "react"
export interface IProps{
    children?:ReactElement,
    books?:string[]
}
const PageSsg : FC<IProps>=function(props){
      const {books}= props
      return (<div>
         { books?.map(item=>{
            return <div key={item}>{item}</div>
         }) }
      </div>)         
}

export default memo(PageSsg)
PageSsg.displayName="PageSsg"
export  const getStaticProps=(context:any)=>{
    //发送网络请求拿到数据
    const data=["da","bb","vv","ee"]
    return{
        props:{
           books:data
        }
    }

}

 有时候我们需要根据动态路由的参数再去请求对应的页面数据,我们需要枚举生成有可能访问的对应页面

 SSR

每次请求都会重新请求

增量静态再生(ISR)

客户端渲染

也就是在 useEffect中获取数据渲染页面

集成Redux

 项目集成

公共样式导入

_app.tsx

import Layout from "@/components/layout";
import "@/styles/globals.scss";
import type { AppProps } from "next/app";
import warpper from "@/store/index"
import { Provider } from "react-redux"
import "normalize.css"
export default function App({ Component, ...rest }: AppProps) {
  //redux接入App
  const {store,props} =warpper.useWrappedStore(rest)
  return <Provider store={store}>
     <Layout><Component {...props.pageProps} /></Layout>
  </Provider> ;
}

axios

可能会出现域名无法访问,根据控制台提示来配置即可

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse }  from "axios";
const  BASE_URL="http://codercba.com:9060/music-next/api"
export interface resultType<T>{
    code:number,
    data:T
}
class niurequest{
    instance: AxiosInstance
    constructor(config : AxiosRequestConfig){
        this.instance=axios.create(config)
    }
    request<T=any>(config:AxiosRequestConfig):Promise<T>{
        return new Promise((resolve,reject)=>{
            this.instance.request<T>(config).then(res=>resolve(res.data)).catch(err=>reject(err))
        })
    }
    get<T>(config:AxiosRequestConfig){
        return this.request<T>({...config,method:"GET"})
    }
    post<T>(config:AxiosRequestConfig){
        return this.request<T>({...config,method:"POST"})
    }
}
export default new niurequest({
    baseURL:BASE_URL,
    timeout:3000
})

请求示例

export const getsearch=()=>{
   return niurequest.get<resultType<searchSuggestResult>>({url:"/searchsuggest/get"})
}
export const  gethomeinfo=()=>{
    return niurequest.get<resultType<homeinfo>>({url:"/home/info"})
}

redux

需要安装next-redux-wrapper还有@redux/toolkit react-redux

改造store

import {configureStore} from "@reduxjs/toolkit"
import {createWrapper} from "next-redux-wrapper"
import home from "./moudule/home"
const store= configureStore({
    reducer:{
        home
    }
})
const markstore=()=>store

export default createWrapper(markstore)
export type AppDispatch =typeof store.dispatch
export type rootState =ReturnType<typeof store.getState>

改造Slice

import { gethomeinfo, getsearch, homeinfo, searchSuggestResult } from "@/service/module/home"
import { createAsyncThunk, createSlice} from "@reduxjs/toolkit"
import { HYDRATE } from "next-redux-wrapper"
export const getSearchSugget=createAsyncThunk("getSearchSugget",async()=>{
        const res=await getsearch()
        
        return res.data
})
export const getHomeinfo=createAsyncThunk("gethomeinfo",async()=>{
    const res=await gethomeinfo() 
    console.log(res.data);
    
    return res.data
})
interface homestate{
    name:string,
    navbar: searchSuggestResult,
    homeinfo:homeinfo
}
const homeSlice=createSlice({
    name:"home",
    initialState:{
        name:"niuniu",
        navbar:{},
        homeinfo:{}
    } as homestate ,
    reducers:{
       unpdatename(store,{payload}){
          store.name=payload
       }
    },
    extraReducers:(builder)=>{
        builder.addCase(HYDRATE,(state,action:any)=>{
            //state->initialState
            //payload.home=>拿到根的一个模块,也就是当前模块
            //目的是保证客户端和服务器端数据的一致性
             return {
                ...state,
                ...action.payload.home
             }
        }).addCase(getSearchSugget.fulfilled,(state,{payload})=>{
            state.navbar=payload
        }).addCase(getHomeinfo.fulfilled,(state,{payload})=>{
            state.homeinfo=payload
        })
    }
})
export default homeSlice.reducer
export const { unpdatename } = homeSlice.actions

改造_app.tsx

import Layout from "@/components/layout";
import "@/styles/globals.scss";
import type { AppProps } from "next/app";
import warpper from "@/store/index"
import { Provider } from "react-redux"
import "normalize.css"
export default function App({ Component, ...rest }: AppProps) {
  //redux接入App
  const {store,props} =warpper.useWrappedStore(rest)
  return <Provider store={store}>
     <Layout><Component {...props.pageProps} /></Layout>
  </Provider> ;
}

我们采用SSR的渲染方式,页面的获取数据也要更改



import type { AppDispatch, rootState } from "@/store";
import {  getSearchSugget, unpdatename } from "@/store/moudule/home";
import { GetServerSideProps } from "next";
import { Inter } from "next/font/google";
import { useSelector,useDispatch} from "react-redux"
import wapper from "@/store/index"
import { Button } from 'antd';
import { gethomeinfo, IBanner, ICategory } from "@/service/module/home";
import type { FC } from "react";
import Banners from "@/components/banners";
const inter = Inter({ subsets: ["latin"] });
interface IProps{
  banners: IBanner[],
  categorys: ICategory,
  recommends: any,
  disitalData:any
}
const Home :FC<IProps>=(props)=> {
  const {banners} =props
   const dispatch=useDispatch()
   const {name,navbar} = useSelector((store:rootState)=>{
     return{ name:store.home.name,navbar:store.home.navbar}
   })
  
  return (
    <>
      <div>
        <Banners banners={banners}/>
         
      </div>
    </>
  );
}
export default Home
export const getServerSideProps:GetServerSideProps=wapper.getServerSideProps((store)=>{
  return  async()=>{
   await  store.dispatch( getSearchSugget())
    const res =await gethomeinfo()
    console.log(res,"ddd");
    
    return {
      props:{
          banners:res.data.banners || [],
          categorys:res.data.categorys || [],
          recommends:res.data.recommends ||[],
          disitalData:res.data.digitalData || ""
      }
    }
  }
})

sass

npm i sass

ant-design

npm i antd --save 
npm @types/antd --save-dev
随后按照官网覆盖样式

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值