文章目录
- 技术栈
- 登录存信息
- 配置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.reducer
export {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'}}>
<Menu
mode="horizontal"
selectedKeys={selectkey}
onClick={onMenuClick}
items={items}
></Menu>
<Outlet></Outlet> //设置子路由出口
</div>)
各页面技术总结
首页
利用useLocation进行反向高亮
//反向高亮
const location = useLocation();
const selectkey =location.pathname
//触发个人信息的action
const 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.data
form.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.Item
label="生日"
>
<DatePicker defaultValue={defaultValue} key={defaultValue} onChange={getDate} />
</Form.Item>
问题:头像设置问题,在有头像时,不显示上传部分
解决方法:
//用三元表达式进行判断
<Upload
name="avatar"
listType="picture-circle"
className="avatar-uploader"
showUploadList
action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload"
onChange={onChange}
maxCount={1}
fileList={imageUrl}
>
{count===0?<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>:''}
</Upload>
分类页
由于页面一样,数据不一样,先封装个模板,等待传来的参数
const Item = (props) => {
//得到传来的list
const { 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>
)
}
其他页面类似