image.png

01-基本结构

目标:能够利用模板代码搭建基础布局步骤

  1. 在 Login/index.js 中创建登录页面基本结构
  2. 在 Login 目录中创建 index.scss 文件,指定组件样式
  3. 将 logo.png 和 login.png 拷贝到 assets 目录中

核心代码
pages/Login/index.js 中:
import logo from ‘../../assets/logo.png’
import ‘./index.scss’

const Login = () => {
return (



登录模块 - 图2
{/ 登录表单 /}


)
}

export default Login
pages/Login/index.scss 中:
.login {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background:#f0f2f5 center/cover url(https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg);

.login-logo {
width: 200px;
height: 60px;
display: block;
margin: 0 auto 40px;
}

.login-container {
width: 320px;
height: 360px;
position: absolute;
left: 50%;
top: 42%;
transform: translate(-50%, -50%);

}
}

02-创建表单

目标:能够使用 antd 的 Form 组件创建登录表单步骤

  1. 打开 antd Form 组件文档
  2. 找到代码演示的第一个示例(基本使用),点击 < >(显示代码),并拷贝代码到组件中
  3. 分析 Form 组件基本结构
  4. 调整 Form 组件结构和样式

核心代码
pages/Login/index.js 中:
import { Form, Input, Button, Checkbox } from ‘antd’
import { UserOutlined, LockOutlined } from “@ant-design/icons”;

const Login = () => {
return (



} placeholder=”请输入手机号” />


} placeholder=”请输入验证码” />


我已阅读并同意「用户协议」和「隐私条款」





)
}

03-表单校验

目标:能够为手机号和密码添加表单校验
分析说明

  • Form 表单校验的默认触发时机是:onChange
    • 如果要修改触发时机,就必须先修改其所在 Form.Item 或 Form 的 validateTrigger 属性值
  • 如果 Form.Item 包裹的表单项的值不是 value 的话,需要通过 valuePropName 来指定其值
    • 比如,如果在 Form.Item 中使用 Checkbox,就需要将 valuePropName 设置为 checked,因为复选框操作的是选中值

步骤

  1. 为 Form 组件添加 validateTrigger 属性,指定校验触发时机的集合
  2. 为 Form.Item 组件添加 name 属性,这样表单校验才会生效
  3. 为 Form.Item 组件添加 rules 属性,用来添加表单校验

核心代码
pages/Login/index.js 中:
size=”large”
validateTrigger={[“onChange”, “onBlur”]}
autoComplete=”off”>
name=”mobile”
rules={[
{ required: true, message: “请输入手机号” },
{
pattern: /^1[3-9]\d{9}$/,
message: “手机格式不正确”,
},
]}
>
} placeholder=”请输入手机号”>

name=”code”
rules={[
{ required: true, message: “请输入验证码” },
{ len: 6, message: “验证码6个字符串” },
]}
>
} placeholder=”请输入验证码”>

name=”isAgree”
valuePropName=”checked”
>
我已阅读并同意「用户协议」和「隐私条款」





总结

  1. 表单校验时 Form.Item 可以没有 name 属性吗? 不可以
  2. 什么情况下需要添加 Form.Item 的 valuePropName 属性?
    • 简单来说,表单元素受控时,操作的不是 value 属性就需要通过该属性来指定

      04-自定义校验

      目标: 通过自定义校验完成同意复选框校验
      分析:
  • required 不支持布尔值的校验,需要通过vaildator 添加自定义校验函数
  • 函数默认两个参数 rule 和 value ,第二个就是表单元素的值
  • 自动来为value添加校验逻辑,成功调用 Promise.resolve()
    • 失败调用 Promise.reject(new Error(‘错误提示’))

核心代码:
name=”isAgree”
valuePropName=”checked”
rules={[
{
validator: (_, value) => {
if (value === true) return Promise.resolve();
else return Promise.reject(new Error(“请勾选我已阅读并同意”));
},
},
]}
>
我已阅读并同意「用户协议」和「隐私条款」

05-获取登录表单值

目标:能够拿到登录表单中的手机号码和验证码
分析说明
Form 组件提供了 onFinish 属性来进行表单提交
步骤

  1. 给 Button 添加 htmlType=”submit” 可触发 Form 组件的提交事件 onFinish
  2. 为 Form 组件添加 onFinish 属性,该事件会在点击登录按钮时触发
  3. 创建 onFinish 函数,通过函数参数 values 拿到表单值
  4. 为了方便,为 Form 组件添加 initialValues 属性,来初始化表单值

核心代码
pages/Login/index.js 中:
const onFinish = values => {
console.log(values)
}

onFinish={onFinish}
initialValues={{
mobile: ‘13911111111’,
code: ‘246810’,
isAgree: true
}}
>
// …




总结

  1. 如何点击按钮触发Form的提交事件?
  2. 如何获取到 Form 表单中的值?
  3. 通过哪个属性可以为 Form 表单初始化值?

    06-配置Redux

    目标:能够完成Redux的基础配置
    步骤

  4. 安装 redux 相关的包:yarn add redux react-redux redux-thunk redux-devtools-extension axios

  5. 在 store 目录中分别创建:actions 和 reducers 文件夹、index.js 文件
  6. 在 store/index.js 中,创建 store 并导出
  7. 创建 reducers/index.js 文件,创建 rootReducer 并导出
  8. 创建 reducers/user.js 文件,创建基础 user reducer 并导出
  9. 在 src/index.js 中为 React 组件接入 Redux

核心代码
store 目录结构:
/store
/actions
/reducers
user.js
index.js
index.js
store/index.js 中:
import { createStore, applyMiddleware } from ‘redux’
import rootReducer from ‘./reducers’

import thunk from ‘redux-thunk’
import { composeWithDevTools } from ‘redux-devtools-extension’
const middlewares = composeWithDevTools(applyMiddleware(thunk))

const store = createStore(rootReducer, middlewares)
export default store
store/reducers/index.js 中:
import { combineReducers } from ‘redux’

import user from ‘./user’

const rootReducer = combineReducers({
user
})

export default rootReducer
store/reducers/user.js 中:
// 登录功能,只需要存储 token 即可,所以,状态默认值为:’’
const initialState = {
token: ‘’
}

const user = (state = initialState, action) => {
return state
}

export default user
src/index.js 中:
import { Provider } from ‘react-redux’
import store from ‘./store’

ReactDOM.render(


,
document.querSelector(‘#root’)
)

07-Redux登录

目标:能够通过 Redux 实现登录功能
分析说明

  • 登录时,会将 token 保存到本地缓存中,为了方便后续操作,可以在 Redux 状态中也保存一份。
  • 但如果不处理,刷新页面时,Redux 中存储的 token 就没有了。
  • 所以,为了能够在刷新页面时,也能让 Redux 拿到 token 值

步骤

  1. 在 Login 组件中分发登录的异步 action
  2. 创建 actions/user.js 文件,实现登录逻辑
  3. 创建 actions/index.js 文件,统一导出 user 中的函数,简化导入
  4. 在 reducers/user.js 中,处理登录相关状态,从本地缓存初始化 Redux 的 token 状态
  5. 登录成功后,跳转到首页

核心代码
pages/Login/index.js 中:
import { message } from ‘antd’
import { useDispatch } from ‘react-redux’
import { useHistory } from ‘react-router’
import { login } from ‘@/store/actions’

const Login = () => {
const dispatch = useDispatch()
const history = useHistory()

const onFinish = async values => {
const { mobile, code } = values
try {
await dispatch(login(mobile, code))
history.replace(‘/home’)
} catch (e) {
message.error(e.response?.data?.message || ‘登录失败’)
}
}

// …


}
store/actions/user.js 中:
import axios from ‘axios’

export const login = (mobile, code) => {
return async dispatch => {
const res = await axios.post(‘http://geek.itheima.net/v1_0/authorizations‘, {
mobile,
code
})

// 注意:此处获取的是 token
const { token } = res.data.data
localStorage.setItem(‘geek-pc-token’, token)
dispatch({ type: ‘login/setToken’, payload: token })
}
}
actions/index.js 中:
// 导出 login 中的所有内容
export from ‘./user’
store/reducers/user.js 中:
const initialState = {
token: localStorage.getItem(‘geek-pc-token’) || ‘’
}
export const user = (state = initialState, action) => {
switch (action.type) {
case ‘user/setToken’:
return {
…state,
token: action.payload
}
default:
return state
}
}
*总结

  1. 使用 Redux 的套路: 组件 dispatch 异步 action -> 提供异步 action -> 完成异步操作 -> 继续 dispatch 普通 action 来发起状态更新 -> reducers 处理状态更新

    08-封装auth工具模块

    目标:能够统一处理 token 的持久化相关操作
    步骤

  2. 创建 utils/auth.js 文件

  3. 分别提供 getToken/setToken/clearToken三个工具函数并导出
  4. 创建 utils/index.js 文件,统一导出 token.js 中的所有内容,来简化工具函数的导入
  5. 将登录操作中用到 token 的地方,替换为该工具函数

核心代码
utils/auth.js 中:
const TOKEN_KEY = ‘itcast_geek_pc’

// 获取token
const getToken = () => localStorage.getItem(TOKEN_KEY)
// 存储token
const setToken = token => localStorage.setItem(TOKEN_KEY, token)
// 清除token
const clearToken = () => localStorage.removeItem(TOKEN_KEY)

export { getToken, setToken, clearToken }
uitls/index.js 中:
export * from ‘./auth’
store/actions/user.js 中:
import { setToken } from ‘@/utils’
// …
setToken(data.token)
store/reducers/user.js 中:
import { getToken } from “@/utils/auth”

const initialState = {
token: getToken()
}

09-封装http工具模块

目标:能够简化axios的使用
步骤

  1. 创建 utils/http.js 文件
  2. 创建 axios 实例,配置 baseURL,简化接口路径
  3. 在 utils/index.js 中,统一导出 http
  4. 将 actions/user.js 中的 axios,替换为 http 工具函数

核心代码
utils/http.js 中:
import axios from ‘axios’

const http = axios.create({
baseURL: ‘http://geek.itheima.net/v1_0‘,
timeout: 5000
})

http.interceptors.response.use(res => {
return res?.data?.data || res
}, e => Promise.reject(e))

export { http }
utils/index.js 中:
export * from ‘./request’
actions/user.js 中:
import { setToken, http } from ‘@/utils’

// const res = await axios.post(‘http://geek.itheima.net/v1_0/authorizations‘, {
const data = await http.post(‘/authorizations’, {
mobile,
code
})
setToken(data.token)
dispatch({ type: ‘user/setToken’, payload: data.token })

补充:可选链操作符

  • MDN 可选链操作符
  • 可选链操作符(?.):允许读取位于对象链深处的属性的值,而不必明确验证链中的每个引用是否有效

const o = {
cat: {
name: ‘tom’,
sayHi() {
console.log(‘where is jerry’)
}
}
}

// 原来:
console.log(o.dog && o.dog.name)
// 使用可选链操作符:
console.log(o.dog?.name)

// 原来:
o.dog && o.dog.sayHi()
// 使用可选链操作符:
o.dog?.sayHi()