文章目录
- 技术栈
- 登录存信息
- 配置token
- hooks使用
- 路由配置
- 各页面技术总结
- 首页
- 发布文章
- 文章详情页
- 个人主页
- 分类页
本篇文章总结一个开发的react项目—博客系统
技术栈
React、react-redux、react-router 6,Ant Design,es6,sass,webpack
登录存信息
主要业务逻辑:redux存信息
redux如何使用,可以看这篇博客 redux使用
创建一个store文件夹
在index.js文件夹里面写总代理
import { configureStore } from "@reduxjs/toolkit"; // configureStore (): 包装 createStore 以提供简化的配置选项和良好的默认设置。它可以自动组合你的slice reducers,添加你提供的任何 Redux 中间件,默认包括 Redux-thunk,并启用 Redux DevTools 扩展。
import userReducer from "./modules/user";export default configureStore({reducer:{user:userReducer}
})
在user.js里面写用户的状态管理
首先导入createSlice,用于接受 reducer 函数的对象、片名和初始状态值,并自动生成带有相应动作创建器和动作类型的 slice reducer。
import { createSlice } from "@reduxjs/toolkit";
import { setToken as _setToken,getToken, removeToken } from "@/utils";
import { loginAPI,getProfileAPI } from "@/apis/user"; //导入需要的其他api
const userStore = createSlice({
}),在createSlice写主要逻辑
定义数据状态
initialState:{token:getToken() || '',userInfo:{}},
编写reducer逻辑
reducers:{setToken(state,action){state.token = action.payload_setToken(action.payload)//action.payload是这个action重新包装后的return返回结果,是createSlice特有的},setUserInfo(state,action){state.userInfo=action.payload},clearUserInfo(state){state.token = ''state.userInfo = {}removeToken()} }
//解构出actionCreater
const { setToken,setUserInfo,clearUserInfo } =userStore.actions
调用接口,获取数据
//异步请求const fetchLogin = (loginForm) =>{return async (dispatch)=>{const res = await loginAPI(loginForm)dispatch(setToken(res.data.token))}
}
//....其余获取个人信息的相关接口
导出reducer函数和封装的函数
//获取reducer函数
const userReducer = userStore.reducerexport {setToken,fetchLogin,fetchUserInfo,clearUserInfo}export default userReducer
完整的redux就是这些了
配置token
在专门文件里面封装token配置
//封装token方法const TOKENKEY='token_key'function setToken(token){localStorage.setItem(TOKENKEY,token)
}function getToken(){return localStorage.getItem(TOKENKEY)
}function removeToken(){localStorage.removeItem(TOKENKEY)
}export{setToken,getToken,removeToken
}
hooks使用
详细hooks可看这篇文章:react—hooks
获取列表示例,展示如何使用hooks
//获取频道列表的逻辑
import { useState,useEffect } from "react"
import { getChannelAPI }from '@/apis/article'function useChannel(){const [channelList, setChannels] = useState([])// 调用接口useEffect(() => {const getChannelList = async () => {const res = await getChannelAPI()setChannels(res.data.channels)}getChannelList()}, [])return {channelList}
}export {useChannel}
路由配置
配置路由守卫,无token信息跳转登录页
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'const AuthRoute = ({ children }) => {const isToken = getToken()if (isToken) {return <>{children}</>} else {return <Navigate to="/login" replace />}
}export default AuthRoute
基本路由配置
import AuthRoute from "@/components/AuthRoute";import { Suspense, lazy } from "react";
//<Suspense> 允许在子组件完成加载前展示后备方案。lazy 就是懒加载
import { createBrowserRouter } from "react-router-dom"; //创建路由
//createBrowserRouter底层是用到了h5的新特性history,这个方法可以实现修改地址栏地址而不会向后端发起请求,并且history这个对象本身就提供了很多控制页面跳转,前进后退等方法。而createHashRouter则是利用了锚点跳转不发起请求的特点,也就是你在网络地址后面加 上#,#后面的内容无论怎么改变都不会引起浏览器发起网络请求,然后通过监听onhashchange事件来监听这个锚点的变化,以此来匹配配置的路由。//路由懒加载
const Home = lazy(()=>import('@/pages/Home'))
const Acticles = lazy(()=>import('@/pages/Acticles'))
//....其余路由```javascript
const router= createBrowserRouter([{path:"/",element:<AuthRoute> <Layout/> </AuthRoute>,// 路由守卫嵌套children:[{path:'all',element:<Suspense fallback={'加载中'}><Home/></Suspense>,children:[{index:true, //默认加载路由element:<Suspense fallback={'加载中'}><All/></Suspense> }, ...........其余配置
在页面设置路由出口
import { Outlet } from 'react-router-dom'
<div style={{backgroundColor:'white'}}><Menumode="horizontal"selectedKeys={selectkey}onClick={onMenuClick}items={items}></Menu><Outlet></Outlet> //设置子路由出口</div>)
各页面技术总结
首页
利用useLocation进行反向高亮
//反向高亮const location = useLocation();const selectkey =location.pathname//触发个人信息的actionconst dispatch = useDispatch()useEffect(()=>{dispatch(fetchUserInfo())},[dispatch])
//获取store内的个人信息const name= useSelector(state=>state.user.userInfo.name)
选择时,高亮效果没实现,增加以下两行代码
const isHomeSelected = location.pathname.startsWith('/all');
selectedKeys={[isHomeSelected ? '/all' : selectkey]}
发布文章
回填数据
const [searchParams]= useSearchParams()const articleId =searchParams.get('id')// console.log(articleId);const [form]= Form.useForm()useEffect(()=>{//通过id获取数据async function getArticleDetail(){const res= await getArticleById(articleId)const data = res.dataform.setFieldsValue({...data,type:data.cover.type,})//回填图片列表setImageType(data.cover.type)setImageList(data.cover.images.map(url=>{return {url}}))}//只有有id才能回填if(articleId){getArticleDetail()}},[articleId,form])
文章详情页
内容是html形式,转化为文本类型,封装了一个函数
function removeHTMLTags(html) {const doc = new DOMParser().parseFromString(html, 'text/html');return doc.body.textContent || '';}
个人主页
先调用redux里面的方法 ,获取个人信息
useEffect(() => {dispatch(fetchUserInfo());}, [dispatch]);
问题:由于使用了一次useEffect,当页面再次渲染时,数据回填不上
解决方法:
进行if判断,有id机进行回填,实现异步操作
useEffect(()=>{if (data) {//进行数据回填form.setFieldsValue({ name, gender: gender === 0 ? '男' : '女', intro });//回填照片console.log(photo);let url = [{uid: '-1',name: 'image1.png',status: 'done',url:data.photo,description: '这是第一张图片'}]console.log(url);setImageUrl(url)}},[data])
问题:生日时间第一次获取到null,数据回填成默认形式,不是应有的数据
解决方法:
//在组件内设置key,当key里面值变化时,组件重新渲染
<Form.Itemlabel="生日"><DatePicker defaultValue={defaultValue} key={defaultValue} onChange={getDate} /></Form.Item>
问题:头像设置问题,在有头像时,不显示上传部分
解决方法:
//用三元表达式进行判断
<Uploadname="avatar"listType="picture-circle"className="avatar-uploader"showUploadListaction="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload"onChange={onChange}maxCount={1}fileList={imageUrl}>{count===0?<div style={{ marginTop: 8 }}><PlusOutlined /></div>:''}</Upload>
分类页
由于页面一样,数据不一样,先封装个模板,等待传来的参数
const Item = (props) => {//得到传来的listconst { parameter } = props;// console.log('/',parameter);const navigate = useNavigate().....其余代码}
问题:分类是根据标签列表实现的,所以要先进行判断,以前端页面举例
const Before=()=>{const [belist,setBelist] = useState([])//判断getList().then(res=>{let list = res.filter((item)=>{return item.channel_id===1 || item.channel_id===6 || item.channel_id===15 || item.channel_id===17 || item.channel_id===23})setBelist(list)})return (<div>//传参<Item parameter= {belist}></Item></div>)}
其他页面类似