npx create-next-app@latest
{
"name": "next-initial-demo",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@types/node": "20.6.0",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
"eslint": "8.49.0",
"eslint-config-next": "13.4.19",
"next": "13.4.19",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "5.2.2"
}
}
- npm i normalize.css --save
- npm i sass --save
- npm i classnames --save
import "normalize.css";
import "@/styles/globals.scss";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
// 这个四个属性是必须的
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
<meta name="description" content="react next demo" />
<link rel="icon" href="/favicon.ico" />
</Head>
<body className="hy-body">
<Main />
<NextScript />
</body>
</Html>
);
}
npm i next-redux-wrapper --save
- 可以避免在访问服务器端渲染页面时 store 的重置
- 该库可以将服务器端 redux 存的数据,同步一份到客户端上
- 该库提供了 HYDRATE 调度操作
- ➢ 当用户访问动态路由或后端渲染的页面时,会执行 Hydration 来保持两端数据状态一致
- ➢ 比如:每次当用户打开使用了 getStaticProps 或 getServerSideProps 函数生成的页面时,HYDRATE 将执行调度操作。
npm i @reduxjs/toolkit react-redux --save
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
const numberSlice = createSlice({
name: 'numberDemo',
initialState: {
counter: 100
},
reducers: {
// 默认参数就有类型提示了
increment(state, action) {
state.counter = action.payload + state.counter;
}
},
extraReducers: (builder) => {
builder.addCase(HYDRATE, (state, action: any) => {
return {
...state, // 当前模块的state
...action.payload.home // payload:rootSate
};
});
}
});
export const { increment } = numberSlice.actions;
export default numberSlice.reducer;
extraReducer:添加更多额外 reducer 处理 other action
import 'normalize.css';
import '@/styles/globals.scss';
import type { AppProps } from 'next/app';
import Layout from '@/components/C-user/layout';
import warpper from '../store';
import { Provider } from 'react-redux';
export default function App({ Component, ...rest }: AppProps) {
const { store, props } = warpper.useWrappedStore(rest);
return (
<div>
<Provider store={store}>
<Layout>
<Component {...props.pageProps} />
</Layout>
</Provider>
</div>
);
}
import { useDispatch, useSelector } from 'react-redux';
import { IAppDispatch, IAppState } from '../store';
import { increment } from '@/store/C-demo/numberDemo';
export default function Home() {
const { numberDemo } = useSelector((rootState: IAppState) => {
return {
numberDemo: rootState.numberDemo.counter
};
});
const dispatch: IAppDispatch = useDispatch();
function addNumber() {
dispatch(increment(2));
}
return (
<div style={{ minHeight: '400px', background: '#DADAE5' }}>
<button onClick={addNumber}>add2</button>
number:{numberDemo}
</div>
);
}
import { getHomeInfoData } from '@/service/user/index';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
export interface IHomeInfo {
banners?: any[];
categorys?: any[];
recommends?: any[];
digitalData?: any;
}
interface IInitialState {
requestDemoInfo: IHomeInfo;
}
const requestSlice = createSlice({
name: 'requestDemo',
initialState: {
requestDemoInfo: {}
} as IInitialState,
reducers: {
// 默认参数就有类型提示了
changeNavbarAction(state, action) {
state.requestDemoInfo = action.payload;
}
},
extraReducers: (builder) => {
builder.addCase(HYDRATE, (state, action: any) => {
return {
...state,
...action.payload.requestDemo // hydration home模块数据
};
});
}
});
// 异步的action
export const fetchHomeInfoAction = createAsyncThunk(
'fetchHomeInfoAction',
async (payload: number, { dispatch }) => {
// console.log("payload=>", payload);
const res = await getHomeInfoData();
dispatch(requestSlice.actions.changeNavbarAction(res.data));
}
);
export const { changeNavbarAction } = requestSlice.actions;
export default requestSlice.reducer;
import { useDispatch, useSelector } from "react-redux";
import { IAppDispatch, IAppState, wrapper } from "@/store/index";
import { increment } from "@/store/C-demo/numberDemo";
import { GetServerSideProps } from "next";
import { getHomeInfoData } from "@/service/user";
import { useCallback, useEffect, useState } from "react";
import { fetchHomeInfoAction } from "@/store/C-demo/requestDemo";
interface listRoot {
id: number;
productId: number;
picId: number;
backendPicId: number;
addTime: number;
position: number;
type: number;
url: string;
bannerExtJson: any;
isSetTime: number;
beginTime: number;
endTime: any;
picStr: string;
backendPicStr: string;
}
export default function Home() {
const { numberDemo } = useSelector((rootState: IAppState) => {
return {
numberDemo: rootState.numberDemo.counter,
};
});
const { requestDemo } = useSelector((rootState: IAppState) => {
return {
requestDemo: rootState.requestDemo.requestDemoInfo,
};
});
const dispatch: IAppDispatch = useDispatch();
function addNumber() {
dispatch(increment(2));
}
const [bannersList, setBannersList] = useState<listRoot[]>([]);
const getUserCb = useCallback(() => getHomeInfoData(), [numberDemo]);
useEffect(() => {
getUserCb()
.then((res) => {
console.log(res);
if (res.data.banners) setBannersList(res.data.banners);
})
.catch((err) => {
console.log(err);
});
}, [getUserCb]);
return (
<div style={{ minHeight: "400px", background: "#DADAE5" }}>
<button onClick={addNumber}>add2</button>
number:{numberDemo}
<div>
直接的List:
<ul>
{bannersList.map((item) => {
return <li key={item.id}>{item.picStr}</li>;
})}
</ul>
redux的List:
{requestDemo?.banners && (
<ul>
{requestDemo?.banners.map((item) => {
return <li key={item.id}>{item.picStr}</li>;
})}
</ul>
)}
</div>
</div>
);
}
export const getServerSideProps: GetServerSideProps =
wrapper.getServerSideProps(function (store) {
return async (context) => {
await store.dispatch(fetchHomeInfoAction(1));
return {
props: {},
};
};
});
npm install axios
npm install antd --save npm install @types/antd --save-dev
_app 全局导入: import "antd/dist/reset.css"; 页面调用: import { Button } from "antd";