React新闻发布管理系统笔记

组件的样式隔离

创建组件的样式文件,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&timestamp=1689490807616&region=&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(可以把对象转换为数组)

出现打开对话框表单数据不显示的问题

可以考虑异步的方式解决

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值