组件的样式隔离
创建组件的样式文件,Child.module.css,其中module必须加,然后引入样式,通过style的方式获取class类或id标签
import React from 'react'
import style from './Child.module.scss'
export default function Child() {
return (
<div className={style.box}>Child</div>
)
}
反向代理
在src目录下创建setupProxy.js文件
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8001',
changeOrigin: true,
})
);
};
在通过useEffect钩子进行ajax请求,后面的[ ]空数组代表只进行一次,此时可以请求数据成功
useEffect(() => {
axios.get("/api/pv?v=1&sdk=1.9.3&project=com.sankuai.myfe.hlb&pageurl=https%3A%2F%2Fi.maoyan.com%2F%23movie%2F.f-hot&pageId=owl-c182de61-0076-d42d-2fe8-f5d7-1689490807611×tamp=1689490807616®ion=&operator=&network=&container=&os=&unionid=1895d7a5c0cc8-03001564e70ae7-337234f-144000-1895d7a5c0cc8").then(res => {
console.log(res)
})
}, [])
路由架构
我使用的是react-router-dom5的版本,创建router文件夹
判断本地是否有token,无就返回到登录页面
import React from 'react'
import { HashRouter, Redirect, Route, Switch } from 'react-router-dom/cjs/react-router-dom.min'
import Login from '../views/login/Login'
import NewsSendBox from '../views/sendbox/NewsSendBox'
export default function IndexRouter() {
return (
<HashRouter>
<Switch>
<Route path='/login' component={Login}></Route>
<Route path='/' render={() => localStorage.getItem('token') ? <NewsSendBox></NewsSendBox> : <Redirect to='/login'></Redirect>}></Route>
</Switch>
</HashRouter>
)
}
最后在App.js中引入路由组件
import IndexRouter from './router/IndexRouter'
export default function App() {
return (
<div>
<IndexRouter></IndexRouter>
</div>
)
}
搭建路由
在components文件夹下创建侧边栏和头部栏组件
在views文件夹下创建相应的路由组件
在NewsSendBox(首页)组件中创建对应的路由文件,其中components文件夹下是每个路由都需要的组件,为公共组件,若没有相应路由则应该跳转403页面(我不小心把Route的path写成to,太菜了啊)
import React from 'react'
import { Redirect, Route, Switch } from 'react-router-dom/cjs/react-router-dom.min'
import SideMenu from '../../components/sandbox/SideMenu'
import TopHeader from '../../components/sandbox/TopHeader'
import Home from './home/Home'
import NoPermission from './nopermission/NoPermission'
import RightList from './right-manage/RightList'
import RoleList from './right-manage/RoleList'
import UserList from './user-manage/UserList'
export default function NewsSendBox() {
return (
<div>
<TopHeader></TopHeader>
<SideMenu></SideMenu>
<Switch>
<Route path='/home' component={Home}></Route>
<Route path='/user-manage/list' component={UserList}></Route>
<Route path='/right-manage/role-list' component={RoleList}></Route>
<Route path='/right-manage/right-list' component={RightList}></Route>
<Redirect from='/' to='/home' exact></Redirect>
<Route path='*' component={NoPermission}></Route>
</Switch>
</div>
)
}
json-server
全局安装json-server,通过终端输入json-server --watch ./src/json/db.json --port 8001即可形成虚拟后端,无跨域限制,id自增长,put是全部替换式的更新,patch是补丁式的部分更新,_embed(嵌套)可以实现类似两个表连接的功能,_expand可以实现例如一个评论和新闻,评论只有postid不知道由哪个新闻发的,就可以通过此功能实现。
item.children?.length
如果item.children为假,则不会执行后面语句,为真才会求length长度
后台数据处理
const [regionList, setregionList] = useState([])
useEffect(() => {
axios.get('http://localhost:8001/regions').then(res => {
// console.log(res)
setregionList(res.data)
})
获得后台数据,而antd中Select展示属性为
而后端返回的数据为
此时需要对数据进行处理,options={regionNewList()}
const regionNewList = () => {
const arr = regionList.map(item => {
return {
value: item.value,
label: item.title
}
})
return arr
}
此时就能获得Select中对应的属性值
forwardRef
forwardRef实际上就是当父组件需要得到子组件元素时,可以利用forwardRef来实现。
该方法接受一个有额外ref参数的react组件函数,不调用该方法,普通的组件函数是不会获得该参数的。
子组件
import React, { forwardRef } from 'react'
const UserModalList = forwardRef((props,ref) => {
...
<Form ref={ref}>
...
</Form>
}
)
export default UserModalList
父组件
import React, { useRef} from 'react'
export default function UserList() {
const addForm = useRef(null)
<UserModalList ref={addForm}></UserModalList>
}
此时父组件就可以获取子组件中ref的内容
Promise.all
获取两个接口数据并把他们合并
useEffect(() => {
Promise.all([axios.get('http://localhost:8001/rights'),
axios.get('http://localhost:8001/children')
]).then(res => {
setRouteList([...res[0].data, ...res[1].data])
})
}, [])
nprogress
npm i nprogress下载进度条的包,因为切换路由都会重新渲染该页面,所以选择把进度条放在这里
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
export default function NewsSendBox() {
NProgress.start()
useEffect(() => {
NProgress.done()
})
...
}
react-draft-wysiwyg富文本编辑器
cnpm i --save react-draft-wysiwyg draft-js
cnpm i draftjs-to-html
使用如下
import React, { useState } from 'react'
import { Editor } from "react-draft-wysiwyg";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import { convertToRaw } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
export default function NewEditor() {
const [editorState, setEditorState] = useState('')
return (
<div>
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(editorState) => setEditorState(editorState)}
onBlur={() => {
console.log(draftToHtml(convertToRaw(editorState.getCurrentContent())))
}}
/>;
</div>
)
}
这个是加了解析html的
import React, { useEffect, useState } from 'react'
import { Editor } from "react-draft-wysiwyg";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import { EditorState, convertToRaw, ContentState } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs'
export default function NewEditor(props) {
const [editorState, setEditorState] = useState('')
// 解析html
useEffect(() => {
// console.log(props.content)
const html = props.content
if (html === undefined) return
const contentBlock = htmlToDraft(html);
if (contentBlock) {
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
setEditorState(editorState)
}
}, [props.content])
return (
<div>
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(editorState) => setEditorState(editorState)}
onBlur={() => {
// console.log(draftToHtml(convertToRaw(editorState.getCurrentContent())))
props.getContent(draftToHtml(convertToRaw(editorState.getCurrentContent())))
}}
/>;
</div>
)
}
具体使用请去github搜索 react-draft-wysiwyg
moment包
cnpm i moment
是一个对时间格式化的包
dangerouslySetInnerHTML
遇到后端返回的html标签请用这个属性
<div dangerouslySetInnerHTML={{
__html: newData.content
}}></div>
自定义hooks
当有重复代码时可以用use开头创建js文件,里面放重复的代码,将要返回的值return出去,再将函数暴露出去
import { message } from 'antd'
import axios from 'axios'
import { useEffect, useState } from 'react'
function usePublish(type) {
const [dataSource, setDataSource] = useState([])
const { username } = JSON.parse(localStorage.getItem('token'))
// 下线
const downNews = (item) => {
// console.log(item)
setDataSource(dataSource.filter(i => i.id !== item.id))
axios.patch(`/news/${item.id}`, {
publishState: 3
}).then(res => {
message.success('下线成功')
})
}
// 删除
const deleteNews = (item) => {
// console.log(item)
setDataSource(dataSource.filter(i => i.id !== item.id))
axios.delete(`/news/${item.id}`).then(res => {
message.success('删除成功')
})
}
// 发布
const publishNews = (item) => {
// console.log(item)
setDataSource(dataSource.filter(i => i.id !== item.id))
axios.patch(`/news/${item.id}`, {
publishState: 2,
publishTime: Date.now()
}).then(res => {
message.success('发布成功')
})
}
useEffect(() => {
axios.get(`/news?author=${username}&publishState=${type}&_expand=category`).then(res => {
console.log(res)
setDataSource(res.data)
})
}, [username, type])
return {
dataSource,
downNews,
deleteNews,
publishNews
}
}
export default usePublish
使用usePublish
import React from 'react'
import usePublish from '../../../components/publish-manage/usePublish'
import PublishTable from '../../../components/publish-manage/PublishTable'
import { Button } from 'antd'
export default function Published() {
const { dataSource, downNews } = usePublish(2)
return (
<div>
<PublishTable dataSource={dataSource} button={(item) => <Button danger onClick={() => downNews(item)}>下线</Button>}></PublishTable>
</div>
)
}
react-redux
cnpm i redux react-redux
创建以下目录
store中的内容
import { legacy_createStore as createStore, combineReducers } from 'redux'
import { CollapsedReducer } from './reducer/CollapsedReducer'
import { isLoadingReceduer } from './reducer/IsLoading'
const reducer = combineReducers({
CollapsedReducer,
isLoadingReceduer
})
const store = createStore(reducer)
export default store
CollapsedReducer中的内容
export const CollapsedReducer = (prevState = {
isCollapsed: false
}, action) => {
// console.log(prevState)
let { type } = action
switch (type) {
case 'change-collapsed':
const newState = { ...prevState }
newState.isCollapsed = !newState.isCollapsed
return newState
default:
return prevState
}
}
IsLoading中 的内容
export const isLoadingReceduer = (prevState = {
loading: true
}, action) => {
const { type, payload } = action
switch (type) {
case 'change-loading':
const newState = { ...prevState }
newState.loading = payload
return newState
default:
return prevState
}
}
在APP.js中
import IndexRouter from './router/IndexRouter'
import './App.css'
import { Provider } from 'react-redux'
import store from './redux/store'
export default function App() {
return (
<Provider store={store}>
<IndexRouter></IndexRouter>
</Provider>
)
}
CollapsedReducer在一个组件中的用法
onst mapStateToProps = ({ CollapsedReducer: { isCollapsed } }) => {
// console.log(state)
return {
isCollapsed
}
}
const mapDispatchToProps = {
changeCollapsed() {
return {
type: 'change-collapsed'
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(TopHeader))
在axios拦截器的用法
import axios from "axios";
import store from '../redux/store'
axios.defaults.baseURL = 'http://localhost:8001'
// 请求拦截器
axios.interceptors.request.use(function (config) {
store.dispatch({
type: 'change-loading',
payload: true
})
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 相应拦截器
axios.interceptors.response.use(function (response) {
store.dispatch({
type: 'change-loading',
payload: false
})
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
状态管理持久化
cnpm i redux-persist
store中的代码改为如下
import { legacy_createStore as createStore, combineReducers } from 'redux'
import { CollapsedReducer } from './reducer/CollapsedReducer'
import { isLoadingReceduer } from './reducer/IsLoading'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const reducer = combineReducers({
CollapsedReducer,
isLoadingReceduer
})
const persistConfig = {
key: 'collapsed',
storage,
blacklist: ['isLoadingReceduer']
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer)
const persistor = persistStore(store)
export { store, persistor }
APP.js中的代码改为如下
import IndexRouter from './router/IndexRouter'
import './App.css'
import { Provider } from 'react-redux'
import { store, persistor } from './redux/store'
import { PersistGate } from 'redux-persist/integration/react'
export default function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<IndexRouter></IndexRouter>
</PersistGate>
</Provider>
)
}
lodash
想对后端返回的数据进行分类,可以使用_.groupBy方法
import _ from 'lodash'
...
// 柱状图数据
useEffect(() => {
axios.get('/news?publishState=2&_expand=category').then(res => {
console.log(_.groupBy(res.data, item => item.category.title))
})
...
分类前
分类后
Object方法
Object.keys,Object.values,Object.entries(可以把对象转换为数组)
出现打开对话框表单数据不显示的问题
可以考虑异步的方式解决