本文主要是数据状态管理的升级以及路由升级的相关设置
升级过程中遇到的各种各样的问题记录一下
官方提供了一部分[升级指南]–如何升级到 React 18,所以仅记录一些官网没有的问题
包之间是存在依赖性的,因此并不是同时升级所有的包是最优选择,首先选定必须要升级的包,先升级之后在运行查看
关于脚手架的升级可查看react脚手架的升级,我们最好是先升级脚手架
根据查看包的更新命令发现react-router-dom也已经由5.0.1升级到6.20.1版本了
发现之前的路由的写法已经不支持了
而官网上关于路由的介绍感觉有点混乱。。。
查了很多资料,最后整理一下
发现针对路由的配置简化了很多
Root 是所有路由的外框架,老版本是在./view/root/index中初始化所有的路由,新版本的可以直接配置在路由文件src/views/page.js中了
import React from 'react';
import Root from './view/root/index';
import {Router, Switch, Route} from "react-router-dom";
import {Provider} from 'react-redux';
import {store} from './reducer/store';
import {createBrowserHistory} from "history";
const history = createBrowserHistory();
class App extends React.Component {
render(){
return (
<Provider store={store}>
<Router history={history}>
<Switch>
<Route component={Root}/>
</Switch>
</Router>
</Provider>
);
}
}
export default App;
//老版
import React from 'react';
import {Switch, Route, Redirect} from "react-router-dom";
import {routerMap} from "../../view/pages";
import RouterGuide from '../routerguide/index'
const pagesRoute = () => {
return routerMap.map((item, index) => {
if("/"==item.path){
return <Route path="/" exact key={index} render={(props) => {
return <Redirect to={{pathname: item.redirectPath, search: `${props.location.search}`}}/>
}}/>
}else{
return <RouterGuide key={index} path={item.path} component={item.component} auth={item.auth}/>
}
})
}
class Root extends React.Component {
render() {
return (
<div className="main-content">
<Switch>
{pagesRoute()}
<Redirect to="/"/>
</Switch>
</div>
)
}
}
export default Root;
import React from 'react';
import {Route} from "react-router-dom";
import {connect} from 'react-redux';
import {userMap} from '../../reducer/connect';
import {device} from 'device.js/dist/device'
class RouterGuide extends React.Component {
componentWillMount(){
let {path, auth, userId} = this.props
if (window.WEIXIN && path !== "/mobile") {
window.location.href = "/mobile";
} else if (!device.mobile && path !== "/mobile") {
window.location.href = "/mobile";
} else if (device.mobile && !window.WEIXIN && ('' === userId.userId || null === userId.userId)) {
if (auth) {
window.location.href = "/index";
}
}
}
render(){
let {path, component} = this.props
return (
)
}
}
export default connect(userMap.mapStateToProps, userMap.mapDispatchToProps)(RouterGuide);
//老版
import IndexPage from "../view/index/index";
import UserPage from "../view/user/index";
import DetailPage from "../view/detail/index";
import ResultPage from "../view/result/index";
export const routerMap=[
{
path:'/',
redirectPath:'/index',
errorPath:'/mobile',
redirect:true,
auth:false
},{
path:'/index',
component:IndexPage,
auth:false
},
{
path:'/detail',
component:DetailPage,
auth:true
},
{
path:'/user',
component:UserPage,
auth:true
},
{
path:'/result',
component:ResultPage,
auth:false
}
]
import React from "react";
import { createBrowserRouter } from "react-router-dom";
import Root from './view/root/index';
function App() {
return (
<BrowserRouter>
<Root></Root>
</BrowserRouter>
);
}
export default App;
import React,{Suspense} from "react";
import { Routes,Route, Outlet } from "react-router-dom";
import {routerMap} from "../../view/pages";
import RouterGuide from '../routerguide/index'
// React.LazyExoticComponent>
const withGuard = (item) => {
return (
<RouterGuide
element={
<Suspense>
<item.element />
</Suspense>
}
auth={item.auth}
/>
);
};
const pagesRoute = () => {
return routerMap.map((item, index) => {
return (
<Route
path={item.path}
key={index}
element={withGuard(item)}
/>
);
});
};
function Root() {
return (
<div className="main-content">
<Routes>
{pagesRoute()}
</Routes>
</div>
);
}
export default Root;
该问题是想要像5.0.1版本一样,在pagesRoute设置路由守卫的时候报错了,
的内部只能是
,哪怕是定义路由守卫都不可以
直接在element设置为函数也报错,会报错Functions are not valid as a React child
,但是调用函数,并使用Suspense
标签即可
新版的路由守卫可以直接在定义路由文件的时候直接设置,也即在方式2中通过src/views/page.js文件中设置,也可以单独设置
import React, { useEffect, Suspense } from "react";
import { Route, useNavigate, useLocation, Navigate } from "react-router-dom";
import { store } from "../../reducer/store";
// 具体实现根据项目需求来进行处理,返回是路由地址。
const onRouterBefore = (one, auth) => {
if (!device.mobile) {
return "/mobile";
} else if (auth) {
const state = store.getState();
return state.userId ? one.pathname : "/index";
} else {
return one.pathname;
}
};
function RouterGuide({ element, auth }) {
const location = useLocation();
const navigate = useNavigate();
const { pathname } = location;
useEffect(() => {
// onRouterBefore 是对路由地址进行处理的函数
const nextPath = onRouterBefore(location, auth);
if (nextPath && nextPath !== pathname) {
//路由重定向
navigate(nextPath, { replace: true });
}
}, [pathname]);
return element;
}
export default RouterGuide;
路由守卫在定义时直接设置,并且相当于在配置文件中直接设定了Root是外框架,不像老版本是在Root编写的,写法更加方便简洁
import Root from "../view/root/index"
import IndexPage from "../view/index/index";
import UserPage from "../view/user/index";
import DetailPage from "../view/detail/index";
import ResultPage from "../view/result/index";
const routerMap=[
{
path:'/',
element:(IndexPage),
errorElement: (ErrorPage),
auth:false,
},
{
path:'/index',
element:(IndexPage),
auth:false
},
{
path:'/detail',
element:(DetailPage),
auth:true
},
{
path:'/user',
element:(UserPage),
auth:true
},
{
path:'/result',
element:(ResultPage),
auth:false
}
]
export {routerMap}
//App.js 升级版
import React from "react";
import { RouterProvider,createBrowserRouter } from "react-router-dom";
import { routerMap } from "./view/pages";
const router = createBrowserRouter(routerMap);
function App() {
return <RouterProvider router={router} />;
}
export default App;
import React from "react";
import { Route, Outlet } from "react-router-dom";
function Root() {
return (
<div className="main-content">
<Outlet />
</div>
);
}
export default Root;
路由守卫在定义路由时直接设置,并且相当于在配置文件中直接设定了Root是外框架,不像老版本是手动引入Root,再在Root中引入路由拦截编写的,该方式写法更加方便简洁
import Root from "../view/root/index"
import IndexPage from "../view/index/index";
import UserPage from "../view/user/index";
import DetailPage from "../view/detail/index";
import ResultPage from "../view/result/index";
// 具体实现根据项目需求来进行处理,返回是路由地址。
const onRouterBefore = (one, auth) => {
if (!device.mobile) {
return "/mobile";
} else if (one.pathname == "/") {
return "/index";
} else if (auth) {
const state = store.getState();
return state.userId ? one.pathname : "/index";
} else {
return one.pathname;
}
};
function Guard({ element, auth }) {
const location = useLocation();
const navigate = useNavigate();
const { pathname } = location;
useEffect(() => {
// onRouterBefore 是对路由地址进行处理的函数
const nextPath = onRouterBefore(location, auth);
if (nextPath && nextPath !== pathname) {
//路由重定向
navigate(nextPath, { replace: true });
}
}, [pathname]);
return element;
}
// React.LazyExoticComponent>
const withGuard = (Comp, auth) => {
return (
<Guard
element={
<Suspense>
<Comp />
</Suspense>
}
auth={auth}
/>
);
};
const routerMap=[
{
path:'/',
element:(Root),
errorElement: (ErrorPage),
auth:false,
children:[
{
path:'/index',
element:(IndexPage),
auth:false
},
{
path:'/detail',
element:(DetailPage),
auth:true
},
{
path:'/user',
element:(UserPage),
auth:true
},
{
path:'/result',
element:(ResultPage),
auth:false
}
]
}
]
const pagesRoute = (list)=>{
list.map(item=>{
item.element=withGuard(item.element,item.auth)
if(item.children){
pagesRoute(item.children)
}
})
}
pagesRoute(routerMap)
export {routerMap}
使用老版的history路由的化话,如上面代码介绍 使用 createBrowserHistory,实现跳转的方法:
let {history} = this.props;
history.push('/detail')
新版本跳转方法:
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate("/detail")
注意,不是
usenavigation
,而是useNavigate
跳转
<Route path="/" element={<Navigate to="/index" replace />}/>
const USERID = 'userId';
const USERNAME = 'userName';
const PHONE = 'phone';
const ADDRESS = 'address';
const action_userId = {type: USERID, text: 'update the user id'}
const action_userName = {type: USERNAME, text: 'update user name'}
const action_phone = {type: PHONE, text: 'update the phone'}
const action_address = {type: ADDRESS, text: 'update address'}
export {USERID,USERNAME,PHONE,ADDRESS,action_userId,action_userName,action_phone,action_address}
import {
action_userId, action_userName,action_phone,action_address
} from './action.js'
export const userMap = {
mapStateToProps(state) {
return state;
},
mapDispatchToProps(dispatch) {
return {
getUserId: () => {
dispatch(action_userId)
},
getUserName: () => {
dispatch(action_userName)
}
}
}
};
//对使用到的状态更新
export const userInfoMap = {
mapStateToProps(state) {
return {
userId: state.userId,
phone: state.phone,
address:state.address
}
},
mapDispatchToProps(dispatch) {
return {
getUserId: () => {
dispatch(action_userId)
},
getPhone: () => {
dispatch(action_phone)
},
getAddress: () => {
dispatch(action_address)
}
}
}
};
import {combineReducers} from 'redux';
import {USERID,USERNAME,PHONE,ADDRESS} from './action.js'
/**
* 触发的处理函数
* @param state 设置一个初始值,若没有显示该初始值
* @param action
* @returns {({} & {userId: string})|{userId: string}}
*/
const getUserId = (state = {userId: ''}, action) => {
switch (action.type) {
case USERID:
return Object.assign({}, state, {
userId: state.userId
})
default:
return state
}
}
/**
* 身份证号
* @param state
* @param action
* @returns {{userName: string}|({} & {userName: string})}
*/
const getUserName = (state = {userName: ''}, action) => {
switch (action.type) {
case USERNAME:
return Object.assign({}, state, {
userName: state.userName
})
default:
return state
}
}
/**
*
* @param state
* @param action
* @returns {({} & {phone: string} & {phone: *})|{phone: string}}
*/
const getPhone = (state = {phone: ''}, action) => {
switch (action.type) {
case PHONELABEL:
return Object.assign({}, state, {
phoneLabel: state.phone
})
default:
return state
}
}
/**
*
* @param state
* @param action
* @returns {({} & {address: string} & {address: *})}
*/
const getAddress = (state = {address: ''}, action) => {
switch (action.type) {
case ADDRESS:
return Object.assign({}, state, {
address: state.address
})
default:
return state
}
}
/**
* 多个reducer方法
* @type {Reducer}
*/
export const allReducer = combineReducers({
userId: getPersonId,
userName: getUserName,
address: getAddress,
phone:getPhone,
})
import {createStore} from 'redux'
import {allReducer} from './redux'
export const store = createStore(allReducer)
import React from 'react';
import {Router, Switch, Route} from "react-router-dom";
import {Provider} from 'react-redux';
import {store} from './reducer/store';
import {createBrowserHistory} from "history";
import Template from './components/template/template';
const history = createBrowserHistory();
class App extends React.Component {
render(){
return (
<Provider store={store}>
<Router history={history}>
<Switch>
<Route component={Template}/>
</Switch>
</Router>
</Provider>
);
}
}
export default App;
import React from 'react'
import {connect} from "react-redux";
import {userInfoMap} from "../../reducer/connect";
import {setTitle} from '../../global';
import axios from 'axios';
import './index.css'
class index extends React.Component {
constructor(props){
super(props);
this.next = this.next.bind(this);
this.changeUserName = this.changeUserName.bind(this);
this.changeAddress = this.changeAddress.bind(this);
this.changePhone = this.changePhone.bind(this);
this.clearValue = this.clearValue.bind(this);
this.state = {
userId:"",
userName: '',
phone: '',
address: '',
errorMes: ''
}
}
clearValue(){
this.setState({
userName: '',
phone: '',
address: '',
errorMes: ''
})
}
changeUserName(event){
let val = event.target.value;
this.setState({
userName: val,
}, () => {
if (val.length>10) {
this.setState({
errorMes: "最大输入10个字符"
})
}
})
}
changeAddress(event){
let val = event.target.value;
this.setState({
address: val,
}, () => {
if (val.length>10) {
this.setState({
errorMes: "最大输入10个字符"
})
}
})
}
changePhone(event){
let val = event.target.value;
this.setState({
phone: val,
}, () => {
if (val.length>11) {
this.setState({
errorMes: "最大输入11个字符"
})
}
})
}
login(){
let {userId, address, phone,userName} = this.props;
axios({
method:'post',
url:"/login",
data: {
userName: this.state.userName,
address: this.state.address,
phone: this.state.phone
}
})
.then((response) => {
if (response.responseCode === "200") {
//更新当前组件,局部状态变更
this.setState({
userId: response.data.userId
}, () => {
let {history} = this.props;
history.push('/home')
})
//更新全局数据
userId.userId = response.data.userId;
address.address = response.data.address;
phone.phone = response.data.phone;
userName.userName = response.data.userName;
} else {
//局部状态变更
this.setState({
errorMes: '请求超时请稍后再试',
}, () => {
//状态变更后的回调函数
// this.changeInput();
})
}
})
}
next(){
this.login();
}
//初次挂载完成
componentDidMount(){
setTitle("客户信息");
}
//组件卸载
componentWillUnmount(){
del();
}
render(){
return (
this.changeUserName(e)} placeholder="请输入您的姓名"/>
this.clearValue()}>
this.changeAddress(e)} placeholder="请输入您的地址"/>
this.clearValue()}>
this.changePhone(e)} placeholder="请输入您的手机号"/>
this.clearValue()}>
)
}
}
export default connect(userInfoMap.mapStateToProps, userInfoMap.mapDispatchToProps)(index)
import { createSlice, configureStore } from "@reduxjs/toolkit";
const initialState = {
userId: "",
userName: "",
phone: "",
address: "",
};
const statusSlice = createSlice({
name: "stateGlobal",
initialState: initialState,
reducers: {
appReducer: (state, action) => {
return Object.assign({}, state, {
...action.payload
});
}
}
});
export const store = configureStore({
reducer: statusSlice.reducer
});
export const { appReducer } = statusSlice.actions;
首先,需要添加持久化的包
npm i redux-persist
其次,修改相关代码
import { createSlice, configureStore } from "@reduxjs/toolkit";
import { persistStore, persistReducer, PERSIST} from "redux-persist";
import storage from "redux-persist/lib/storage";
const initialState = {
userId: "",
userName: "",
phone: "",
address: "",
};
const statusSlice = createSlice({
name: "stateGlobal",
initialState: initialState,
// devTools : 开启redux-devtools,默认开启,开发环境开启,生产环境关闭
devTools: process.env.NODE_ENV === "development",
reducers: {
appReducer: (state, action) => {
return Object.assign({}, state, {
...action.payload
});
},
otherReducer:(state, action) => {
return Object.assign({}, state, {
userId:action.payload
});
},
}
});
//在localStorge中生成key为root的值
const persistConfig = {
key: "root",
version: 1,
storage,
blacklist: [] //设置某个reducer数据不持久化,
};
const reducers = persistReducer(persistConfig, statusSlice.reducer);
const store = configureStore({
reducer: reducers,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {//设置序列化检测
ignoredActions: [PERSIST],//忽略的action类型
ignoredActionPaths: [],//忽略action中的路径
ignoredPaths: []//忽略state中的路径
}
// serializableCheck: false //关闭redux序列化检测
})
});
const persistor = persistStore(store);
export const { appReducer } = statusSlice.actions;
export { store, persistor };
import React from 'react'
import { useNavigate } from "react-router-dom";
import { store, appReducer } from "../../reducer/store";
import {setTitle} from '../../global';
import axios from 'axios';
import './index.css'
export default function Index() {
const navigate = useNavigate();
//获取全局数据
const init=store.getState();
const [state,setState] = useState({
userId:"",
userName: '',
phone: '',
address: '',
errorMes: ''
})
const clearValue=()=>{
setState({
...state,
userName: '',
phone: '',
address: '',
errorMes: ''
})
}
const changeUserName=(event)=>{
let val = event.target.value;
setState({
...state,
userName: val,
errorMes: val.length>10?"最大输入10个字符":state.errorMes
})
}
const changeAddress=(event)=>{
let val = event.target.value;
setState({
...state,
address: val,
errorMes: val.length>10?"最大输入10个字符":state.errorMes
})
}
const changePhone=(event)=>{
let val = event.target.value;
setState({
...state,
phone: val,
errorMes: val.length>11?"最大输入11个字符":state.errorMes
})
}
const login=()=>{
axios({
method:'post',
url:"/login",
data: {
userName: state.userName,
address: state.address,
phone: state.phone
}
})
.then((response) => {
if (response.responseCode === "200") {
//更新当前组件,局部状态变更
setState({
...state,
userId: response.data.userId
})
navigate('/home')
//更新全局数据
store.dispatch(appReducer({
userId: response.data.userId,
address: response.data.address,
phone: response.data.phone,
userName: response.data.userName,
}))
} else {
//局部状态变更
setState({
...state,
errorMes: "请求超时请稍后再试"
})
//状态变更后的回调函数,没法设置,setState不支持回调
// changeInput();
}
})
}
const next=()=>{
login();
}
//初次挂载完成
useEffect(()=>{
setTitle("客户信息");
},[])
//组件卸载
useEffect(()=>{
return ()=>{
del();
}
})
return (
changeUserName(e)} placeholder="请输入您的姓名"/>
clearValue()}>
changeAddress(e)} placeholder="请输入您的地址"/>
clearValue()}>
changePhone(e)} placeholder="请输入您的手机号"/>
clearValue()}>
)
}
通过以上方式发现现有的状态管理比以前简单很多
register
. Value: ƒ register2(key) { _pStore.dispatch({ type: REGISTER,key});}import { persistStore, persistReducer, PERSIST} from "redux-persist";
const store = configureStore({
reducer: reducers,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
// Ignore these action types
ignoredActions: [PERSIST],
// Ignore these field paths in all actions
ignoredActionPaths: [],
// Ignore these paths in the state
ignoredPaths: []
}
// serializableCheck: false //关闭redux序列化检测
})
});
一开始 middleware
写错了位置,写在了createSlice中,后来仔细检查代码发现是位置写错了!!!!
老版本的生命周期非常清晰,新版版的因为不推荐组件式的组件,推荐的是函数式组件,然后生命周期就非常混乱
老版本比较关注:
发现 React18.2.0 采用的是函数式组件,并且状态更新的回调已经没有了,所以不能行云流水的在设置卸载前的功能了,挂载后需要执行的操作,并且因为每次状态更新都要重新渲染 DOM ,因此如果在之后的回调中需要设置操作,但是没有状态后的回调了,导致整体逻辑去设置的时候非常混乱,会存在很多坑!!!并且存在什么渲染两次!!!!!!!这些都需要自己考虑,感觉是对用户的维护成本很高
新版本的虽说是可以使用useEffect
,但是逻辑非常不清晰,并且官网的手册也根本没有对这些变更给与非常明确的解决方案,感觉?#$%^&*,我们需要花大量的时间去考虑各个变量之间的关系,可以不可以在某个位置更新,反正目前为止在升级的过程中的维护感觉很痛苦,我在考略要不要放弃这个框架了。。。。。。
这里是升级过程中遇到的问题汇总
如果启动后发现以上报错信息,说明之前米有初始化该工具包,初始化一下即可
npm i [email protected] -D
es6模块循环依赖问题导致,查找代码把循环依赖注释掉即可
在更新前,使用this.setState()
修改状态,并且this.setState()
是异步函数,有回调函数的;
包升级后,使用useState()
修改状态,并且useState()
是异步函数,但是没有回调函数
这是本人在开发工程中,从网上查找相关资料,说是组件名称没有大写开头,可是本人组件名称是大写!!!!但是提示next不是React组件方法名 ,感觉有点莫名其妙,想到next确实小写,本人将 useEffect 方式写在了函数"next"中,移到组件根块下报错消失!!!!所以useEffect是必须直接使用在组件结构下