从0开始手把手教你做极客园项目(一)——登录功能实现

文章目录

  • 前言
  • 一、项目准备工作
      • 1.初始化项目,安装依赖
      • 2.目录结构准备
      • 3.craco配置
  • 二、登录功能
      • 1.配置路由
      • 2.登录界面静态结构
      • 3.添加表单校验
      • 4.封装请求
      • 5.redux状态管理token
      • 6.异步获取token
      • 7.组件上触发异步
      • 8.token持久化
      • 9.axios请求头注入token
      • 10.token控制路由权限
  • 总结


前言

最近跟着黑马学习了一个项目,他采用传统的javascript的写法,当然我是理解项目的基础上用tpescript语法改写了极客园项目,采取了react+typescript+redux+antd实现的一个项目,本文接下来会详细讲解每一步,包括核心思想以及实现流程,从0开始手把手教你做极客园项目,总体篇幅较长,所以本文是先讲解了登录功能的实现源码地址极客园项目github

一、项目准备工作

1.初始化项目,安装依赖

使用react脚手架快速创建项目

npx create-react-app jikeyuan --template typescript

右击项目的文件夹,在终端中打开输入以下命令安装项目相关的依赖

1.sass:用于在React项目中使用Sass(Syntactically Awesome Stylesheets)进行样式编写。Sass是一种CSS预处理器,它扩展了CSS的功能,并提供了更多的灵活性和可重用性。
2.react-redux:React-Redux是一个用于在React应用中管理全局状态的库。它提供了一种将Redux状态管理与React组件结合使用的方式,使得在React应用中更容易管理和共享状态。
3.@craco/craco:Craco是一个用于扩展Create React App(CRA)配置的工具。它允许开发人员在不弹出CRA配置的情况下,自定义和扩展React应用的构建和开发配置。
4.@reduxjs/toolkit:Redux Toolkit是一个官方维护的Redux工具集,旨在简化和提高Redux的开发效率。它提供了一些实用的函数和工具,使得在Redux应用中编写和管理状态更加简单和直观。
5.axios:Axios是一个基于Promise的HTTP客户端,用于在浏览器和Node.js中进行HTTP请求。它提供了一种简洁而强大的方式来处理异步请求,并支持拦截器、取消请求等功能。
6.antd:Ant Design是一个流行的React UI组件库,提供了一套美观、易用且高度可定制的UI组件,可以帮助开发人员快速构建用户界面。
7.react-router-dom:React Router是一个用于在React应用中进行路由管理的库。react-router-dom是React Router的DOM绑定版本,它提供了一些用于创建和管理路由的组件和API,使得在React应用中实现路由功能更加简单和灵活。

npm install sass react-redux @craco/craco @reduxjs/toolkit axios antd react-router-dom

2.目录结构准备

apis:放置接口
assets:存放静态资源
components:通用的组件
pages:页面的组件
router:路由
store:redux状态
util:封装函数
从0开始手把手教你做极客园项目(一)——登录功能实现_第1张图片

3.craco配置

在根目录下创建一个craco.config.js文件,配置别名路径将src/替换为@/

const path = require('path');

module.exports = {
    webpack: {
        alias: {
            '@': path.resolve(__dirname, 'src')
        },
    }
};

在tsconfig.json的"compilerOptions"中添加

"baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },

在package.json文件中script中用craco替代

"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "craco eject"
  },

二、登录功能

1.配置路由

在pages里创建两个文件夹Layout和Login,每个文件夹包括一个index.tsx文件,输入代码rafce回车快速初始化组件,然后在路由文件里创建一个index.tsx文件,表示路由路径组件

import { createBrowserRouter } from "react-router-dom";
import Layout from "@/pages/Layout";
import Login from "@/pages/Login";

const router=createBrowserRouter([
    {
        path:'/',
        element:<Layout/>
    },
    {
        path:'/login',
        element:<Login/>
    }
])

export default router

在根组件index.tsx中修改代码,将路由配置到入口文件挂载

import ReactDOM from 'react-dom/client';

import { RouterProvider } from 'react-router-dom';
import router from './router';


const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <RouterProvider router={router}/>
);

2.登录界面静态结构

使用antd的 Card, Form, Input, Button组件,配合scss样式

import './index.scss'
import { Card, Form, Input, Button} from 'antd'
import logo from '@/assets/logo.png'

const Login = () => {
  return (
      <div className="login">
        <Card className="login-container">
          <img className="login-logo" src={logo} alt="" />
          {/* 登录表单 */}
          <Form>
            <Form.Item>
              <Input size="large" placeholder="请输入手机号" />
            </Form.Item>
            <Form.Item>
              <Input size="large" placeholder="请输入验证码" />
            </Form.Item>
            <Form.Item>
              <Button type="primary" htmlType="submit" size="large" block>
                登录
              </Button>
            </Form.Item>
          </Form>
        </Card>
      </div>
  )
}

export default Login

在浏览器上手动加入路径/login,静态结构就准备好了
从0开始手把手教你做极客园项目(一)——登录功能实现_第2张图片

3.添加表单校验

查阅antd的item组件的文档,在item上添加name属性和rule属性,name和后端接口数据的字段名保持相同rule则是添加校验规则,required代表这项是否需要必填,message表示输出错误的信息,这里判断输入为空和一个正则表达式来判断手机号是否符合格式

<Form.Item name="mobile"
            rules={[{ required: true, message: '手机号不能为空' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }]}
          >
            <Input size="large" placeholder="请输入手机号" />
          </Form.Item>
          <Form.Item name="code"
            rules={[{ required: true, message: '验证码不能为空' }]}>
            <Input size="large" placeholder="请输入验证码" />
          </Form.Item>

form表单上添加一个属性onfinish,表示点击登录后获取表单里的输入信息,添加onFinish函数,在控制台测试是否能够收集

<Form onFinish={onFinish}>
export interface logindata {
  moblie: string,
  code: string
}
const Login = () => {
  const onFinish=(values:logindata)=>{
   console.log(values)
  }

从0开始手把手教你做极客园项目(一)——登录功能实现_第3张图片

4.封装请求

项目中会发送很多请求,为了方便统一管理和复用,所以封装请求会简化代码量,在utils里创建request.tsx

封装包括1.根域名配置,2.超时时间,3.请求拦截器和响应拦截器

//axios封装处理
//1.根域名配置,2.超时时间,3.请求拦截器/响应拦截器
import axios from 'axios'

const request =axios.create({
   baseURL:'http://geek.itheima.net/v1_0',//根域名
   timeout:5000,//超时时间五秒
})
request.interceptors.request.use((config)=> {
    return config
  }, (error)=> {
    return Promise.reject(error)
})

// 添加响应拦截器
request.interceptors.response.use((response)=> {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response.data
  }, (error)=> {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error)
})

export{request}

在utils里添加一个中转导出index.tsx文件,所有utils里的工具函数都导入进index.tsx文件和导出

import { request } from "./request"

export {
    request,
}

5.redux状态管理token

token用于标识用的身份和权限,当用户登录后,后端会返回一个token用于前端识别用户,在store文件夹创建一个hook.tsx文件和一个index.tsx文件和一个modules文件夹,modules文件夹里创建一个user.tsx文件用于管理用户登录状态

hook.tsx
导出自定义的钩子函数useAppDispatch和useAppSelector,对useDispatch和useSelector的封装

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from '../store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

index.tsx
创建和配置Redux store,并导出RootState和AppDispatch类型

import { configureStore } from "@reduxjs/toolkit";
import userReducer from './module/user'

const store=configureStore(
    {
        reducer:{
            user:userReducer
        }
    }
)
export type RootState = ReturnType<typeof store.getState>
// 推断出类型: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export default store

user.tsx
创建和配置一个名为user的Redux模块,定义了一个同步的reducer函数setToken用于修改user模块的token状态

import { logindata } from "@/pages/Login";
import { request } from "@/utils";
import { Dispatch, createSlice } from "@reduxjs/toolkit";

const userStore = createSlice({
    name: 'user',
    initialState: {
        token: ''
    },
    reducers: {
        setToken(state, action) {
            state.token = action.payload
        }//同步修改
    }
})

const { setToken } = userStore.actions

const userReducer = userStore.reducer

export { setToken}
export default userReducer

配置好完成后,在根组件index.tsx文件中把状态添加上包裹路由出口实现状态管理

root.render(
  <Provider store={store}>
    <RouterProvider router={router}/>
  </Provider>
);

6.异步获取token

在user.tsx文件中添加一个异步函数获取token

const fetchLogin: (loginForm: logindata) => (dispatch: Dispatch) => Promise<void> = (loginForm) => {
    return async (dispatch: Dispatch) => {
        const res = await request.post('/authorizations', loginForm)
        dispatch(setToken(res.data.token))
    }
}

添加导出

export { setToken, fetchLogin }

7.组件上触发异步

在Login的index.tsx函数中使用useAppDispatch函数调用状态里的异步方法,并使用useNavigate实现登录成功跳转界面

 const navigate = useNavigate()
  const dispatch = useAppDispatch()
  const onFinish = async (values: logindata) => {
    await dispatch(fetchLogin(values))
    navigate('/')
    message.success('登录成功')
  };

可以看到在使用正确的手机号和验证码登录后,发送的请求报文包括我们表单输入的数据,响应报文中包括返回的token,并且在redux里也成功存入了token
从0开始手把手教你做极客园项目(一)——登录功能实现_第4张图片
从0开始手把手教你做极客园项目(一)——登录功能实现_第5张图片
从0开始手把手教你做极客园项目(一)——登录功能实现_第6张图片

8.token持久化

上述功能虽然能够在用户登录后根据token来确定用户身份,但是刷新界面后,会发现redux里的token变成了空串,这是因为我们刷新时,token回到了最初的状态,要想token刷新时仍然存在,使用localstorage技术在本地添加缓存可以有效实现token的持久化,在utils工具函数内封装一个token.tsx文件,用于token在localstorage的存取删方法

const TOKENKEY = 'token_key'

function setToken(token: string) {
    localStorage.setItem(TOKENKEY, token)/键、值
}

function getToken() {
    return localStorage.getItem(TOKENKEY)
}
function removeToken() {
    localStorage.removeItem(TOKENKEY)
}

export { setToken, getToken, removeToken }

同样的,在中转导出index.tsx里添加

//中转导出
import { request } from "./request";
import { getToken, removeToken, setToken } from "./token";

export {
    request,
    getToken,
    setToken,
    removeToken
}

在user.tsx里重新设置状态,当localstorage里有token时拿localstorage里的token,没有则设为空串,并且在同步存入状态里添加同步存入token到localstorage里

import { setToken as _setToken, getToken } from "@/utils";

const userStore = createSlice({
    name: 'user',
    initialState: {
        token: getToken() || ''
    },
    reducers: {
        setToken(state, action) {
            state.token = action.payload
            _setToken(action.payload)
        }
    }
})

经过测试,页面刷新后可以正常获取到token到redux的状态中
从0开始手把手教你做极客园项目(一)——登录功能实现_第7张图片
从0开始手把手教你做极客园项目(一)——登录功能实现_第8张图片

9.axios请求头注入token

在axios请求头注入token后,向后端发送数据时就会携带token用于识别当前用户,在request.tsx里的请求拦截器添加请求头携带token

request.interceptors.request.use((config)=> {
  const token=getToken()
  if(token){
    config.headers.Authorization=`Bearer ${token}`//根据后端要求拼接
  }
    return config
  }, (error)=> {
    return Promise.reject(error)
})

10.token控制路由权限

利用高阶组件HOC component判断组件是否有token,有则跳转到路由组件,没有则返回登录界面
在components里添加一个AuthRouter.tsx文件

import { getToken } from "@/utils";
import { ReactNode } from "react";
import { Navigate } from "react-router-dom";

interface AuthRouterProps{
    children: ReactNode;
};

function AuthRouter({ children }: AuthRouterProps) {
    const token = getToken()
    if (token) {
        return <>{children}</>
    } else {
        return <Navigate to={'/login'} replace />//重定向到login
    }
}

export default AuthRouter

在router里用高阶组件包裹路由组件

  {
        path:'/',
        element:<AuthRouter><Layout/></AuthRouter>
    },

当我们把localstorage里的token删除时,意味着还没有获取用户身份,此刻刷新界面时,根据重定向到登录界面
从0开始手把手教你做极客园项目(一)——登录功能实现_第9张图片

总结

登录界面包括的逻辑功能很多,包括封装函数,拦截器,redux管理状态和localstorage本地缓存存储token,以及高阶组件和重定向,几乎适用于所有登录功能的实现,后续会根据学习进度继续更新后续的极客园内容,希望大家点点赞,点点关注

你可能感兴趣的:(前端,typescript,vscode,scss,react.js,前端框架)