- Token 存储
- 数据持久化
- 状态管理
- 集成状态管理器 Redux 及共享 Token 信息
- 注意
- 总结
剩下代码
pages/login.js
import React, {useState, useEffect} from "react"; import {Form, Input, Button, Checkbox, message, Alert, Typography} from "antd"; import Record from "../../components/layout/record"; import styles from "./index.module.scss"; import {useRouter} from "next/router"; import {useSelector, useDispatch} from 'react-redux' import {login} from '@/store/slices/auth'; import {wrapper} from '@/store' const {Text, Link} = Typography; const layout = { labelCol: {span: 24}, wrapperCol: {span: 24} }; const Login = props => { const dispatch = useDispatch(); const router = useRouter(); const [isLoding, setIsLoading] = useState(false); const [error, setError] = useState({ show: false, content: "" }); function closeError() { setError({ show: false, content: "" }); } const onFinish = async ({username, password}) => { if (!username) { setError({ show: true, content: "请输入用户名" }); return; } if (!password) { setError({ show: true, content: "请输入密码" }); return; } setIsLoading(true); let res = await dispatch(login({ grant_type: "password", username, password })); if (res.payload.errorMsg) { message.warning(res.payload.errorMsg); } else { router.push("/"); } setIsLoading(false); }; function render() { return props.isLogin ? ( <></> ) : ( <div className={styles.container}> <div className={styles.content}> <div className={styles.card}> <div className={styles.cardBody}> <div className={styles.error}>{error.show ? <Alert message={error.content} type="error" closable afterClose={closeError}/> : null}</div> <div className={styles.cardContent}> <Form {...layout} name="basic" initialValues={{remember: true}} layout="vertical" onFinish={onFinish} // onFinishFailed={onFinishFailed} > <div className={styles.formlabel}> <b>用户名或邮箱</b> </div> <Form.Item name="username"> <Input size="large"/> </Form.Item> <div className={styles.formlabel}> <b>密码</b> <Link href="/account/password_reset" target="_blank"> 忘记密码 </Link> </div> <Form.Item name="password"> <Input.Password size="large"/> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" block size="large" className="submit" loading={isLoding}> {isLoding ? "正在登录..." : "登录"} </Button> </Form.Item> </Form> <div className={styles.newaccount}> 首次使用 Seaurl?{" "} <Link href="/join?ref=register" target="_blank"> 创建一个账号 </Link> {/* <a className="login-form-forgot" href="" > 创建一个账号</a> */} </div> </div> </div> <div className={styles.recordWrapper}> <Record/> </div> </div> </div> </div> ); } return render(); }; export const getServerSideProps = wrapper.getServerSideProps(store => ({ctx}) => { const {isLogin, me} = store.getState().auth; if(isLogin){ return { redirect: { destination: '/', permanent: false, }, } } return { props: {} }; }); export default Login;
注意
1、使用了next-redux-wrapper
一定要加 HYDRATE,目的是同步服务端和客户端 reducer 数据,否则两个端数据不一致造成冲突。
[HYDRATE]: (state, action) => { console.log('HYDRATE', state, action.payload); return Object.assign({}, state, {...action.payload.auth}); },
2、注意next-redux-wrapper
和next-redux-cookie-wrapper
版本。
"next-redux-cookie-wrapper": "^2.0.1", "next-redux-wrapper": "^7.0.2",
总结
- ssr 项目不要用持久化,而是直接从 server 端请求接口拿数据直接渲染,否则失去使用 SSR 的意义了;
- Next.js 分为静态渲染和服务端渲染,其实 SSR 项目如果你的项目很小,或者都是静态数据可以考虑直接使用客户端静态方法
getStaticProps
来渲染。