[react]react-router-dom 与 redux 版本升级

[react]react-router-dom 与 redux 版本升级

  • 环境
  • 脚手架的升级
  • react-router-dom 升级
    • 关于路由相关文件的写法--react-router-dom 5.0.1
      • 入口渲染文件App.js
      • 路由框架src/views/root/index.js
      • 路由守卫 src/views/routerguide/index.jsx
      • 路由文件src/views/page.js
    • 关于路由相关文件的写法--react-router-dom 6.20.1方式1
      • 入口渲染文件App.js
      • 路由框架src/views/root/index.js
      • 报错信息
        • [RouterGuide] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
      • 路由守卫 src/views/routerguide/index.jsx
      • 路由文件src/views/page.js
    • 关于路由相关文件的写法--react-router-dom 6.20.1方式2
      • 入口渲染文件App.js
      • 路由框架src/views/root/index.js
      • 路由文件 src/views/page.js
    • 路由的跳转
      • 代码跳转
      • 标签跳转
  • 状态管理
    • React 状态管理--Redux4.0.4版本
      • action.js定义改变状态类型
      • connect.js定义组件需要修改的全局变量
      • redux.js 定义改变状态类型
      • store.js 定义所有的全局变量
      • App.js 全局变量的注册:
      • 组件使用全局变量
    • React 状态管理--Redux5.0.0版本
      • store.js 定义所有全局状态
      • store.js 定义所有全局状态--持久化版本
      • 组件使用全局变量:
      • 报错信息
        • store.js:60 A non-serializable value was detected in an action, in the path: `register`. Value: ƒ register2(key) { _pStore.dispatch({ type: REGISTER,key});}
  • 生命周期
  • 问题汇总
    • Module not found: Can't resolve 'web-vitals'
    • Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
    • State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect()
    • Line 296:7: React Hook "useEffect" is called in function "next" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

本文主要是数据状态管理的升级以及路由升级的相关设置

环境

  • node -v.18.15.0
  • react - 16.9.0

脚手架的升级

升级过程中遇到的各种各样的问题记录一下

官方提供了一部分[升级指南]–如何升级到 React 18,所以仅记录一些官网没有的问题

包之间是存在依赖性的,因此并不是同时升级所有的包是最优选择,首先选定必须要升级的包,先升级之后在运行查看

关于脚手架的升级可查看react脚手架的升级,我们最好是先升级脚手架

react-router-dom 升级

根据查看包的更新命令发现react-router-dom也已经由5.0.1升级到6.20.1版本了

发现之前的路由的写法已经不支持了

而官网上关于路由的介绍感觉有点混乱。。。

查了很多资料,最后整理一下

发现针对路由的配置简化了很多

关于路由相关文件的写法–react-router-dom 5.0.1

入口渲染文件App.js

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;

路由框架src/views/root/index.js

//老版
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;

路由守卫 src/views/routerguide/index.jsx

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 (
            <Route path={path} component={component}/>
        )
    }
}

export default connect(userMap.mapStateToProps, userMap.mapDispatchToProps)(RouterGuide);

路由文件src/views/page.js

//老版
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
    }
]

关于路由相关文件的写法–react-router-dom 6.20.1方式1

入口渲染文件App.js

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;

路由框架src/views/root/index.js

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<ComponentType<any>>
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;

报错信息

[RouterGuide] is not a component. All component children of must be a or <React.Fragment>

该问题是想要像5.0.1版本一样,在pagesRoute设置路由守卫的时候报错了,<Routes></Routes>的内部只能是<Route/>,哪怕是定义路由守卫都不可以

直接在element设置为函数也报错,会报错Functions are not valid as a React child,但是调用函数,并使用Suspense标签即可

路由守卫 src/views/routerguide/index.jsx

新版的路由守卫可以直接在定义路由文件的时候直接设置,也即在方式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;

路由文件src/views/page.js

路由守卫在定义时直接设置,并且相当于在配置文件中直接设定了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}

关于路由相关文件的写法–react-router-dom 6.20.1方式2

入口渲染文件App.js

//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;

路由框架src/views/root/index.js

import React from "react";
import { Route,  Outlet } from "react-router-dom"; 
function Root() {
  return (
      <div className="main-content">
        <Outlet />
      </div>
  );
}
export default Root;

路由文件 src/views/page.js

路由守卫在定义路由时直接设置,并且相当于在配置文件中直接设定了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<ComponentType<any>>
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 />}/>

状态管理

React 状态管理–Redux4.0.4版本

  • “redux”: “^4.0.4”
  • “react-redux”: “^7.1.0”

action.js定义改变状态类型

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}

connect.js定义组件需要修改的全局变量

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)
            }
        }
    }
};

redux.js 定义改变状态类型

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<any>}
 */
export const allReducer = combineReducers({
    userId: getPersonId,
    userName: getUserName,
    address: getAddress,
    phone:getPhone,
})

store.js 定义所有的全局变量

import {createStore} from 'redux'
import {allReducer} from './redux'
export const store = createStore(allReducer)

App.js 全局变量的注册:

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 (
            <div className="page">
              <div className="margin">
                  <div className="form-group top10">
                    <input type="text" value={this.state.userName}
                     onChange={(e) => this.changeUserName(e)} placeholder="请输入您的姓名"/>
                    <i onClick={() => this.clearValue()}></i>
                  </div>
                  <div className="form-group top10">
                    <input type="text" value={this.state.address}
                     onChange={(e) => this.changeAddress(e)} placeholder="请输入您的地址"/>
                    <i onClick={() => this.clearValue()}></i>
                  </div>
                  <div className="form-group top10">
                    <input type="text" value={this.state.phone}
                     onChange={(e) => this.changePhone(e)} placeholder="请输入您的手机号"/>
                    <i onClick={() => this.clearValue()}></i>
                  </div>

                  <div className="form-group">
                    <label onClick={() => this.next()}>下一步</label>
                  </div>
              </div>
            </div>
        )
    }
}

export default connect(userInfoMap.mapStateToProps, userInfoMap.mapDispatchToProps)(index)

React 状态管理–Redux5.0.0版本

  • “redux”: “^5.0.0”
  • “@reduxjs/toolkit”: “^2.0.1”
  • “react”: “^18.2.0”

store.js 定义所有全局状态

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;

store.js 定义所有全局状态–持久化版本

首先,需要添加持久化的包

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 (
      <div className="page">
        <div className="margin">
            <div className="form-group top10">
              <input type="text" value={state.userName}
                onChange={(e) => changeUserName(e)} placeholder="请输入您的姓名"/>
              <i onClick={() => clearValue()}></i>
            </div>
            <div className="form-group top10">
              <input type="text" value={state.address}
                onChange={(e) => changeAddress(e)} placeholder="请输入您的地址"/>
              <i onClick={() => clearValue()}></i>
            </div>
            <div className="form-group top10">
              <input type="text" value={state.phone}
                onChange={(e) => changePhone(e)} placeholder="请输入您的手机号"/>
              <i onClick={() => clearValue()}></i>
            </div>

            <div className="form-group">
              <label onClick={() =>next()}>下一步</label>
            </div>
        </div>
      </div>
  )
}

通过以上方式发现现有的状态管理比以前简单很多

报错信息

store.js:60 A non-serializable value was detected in an action, in the path: 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,但是逻辑非常不清晰,并且官网的手册也根本没有对这些变更给与非常明确的解决方案,感觉?#$%^&*,我们需要花大量的时间去考虑各个变量之间的关系,可以不可以在某个位置更新,反正目前为止在升级的过程中的维护感觉很痛苦,我在考略要不要放弃这个框架了。。。。。。

问题汇总

这里是升级过程中遇到的问题汇总

Module not found: Can’t resolve ‘web-vitals’

如果启动后发现以上报错信息,说明之前米有初始化该工具包,初始化一下即可

npm i web-vitals@2.1.4 -D

Cannot access ‘WEBPACK_DEFAULT_EXPORT’ before initialization

es6模块循环依赖问题导致,查找代码把循环依赖注释掉即可

State updates from the useState() and useReducer() Hooks don’t support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect()

在更新前,使用this.setState()修改状态,并且this.setState()是异步函数,有回调函数的;
包升级后,使用useState()修改状态,并且useState()是异步函数,但是没有回调函数

Line 296:7: React Hook “useEffect” is called in function “next” that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word “use”

这是本人在开发工程中,从网上查找相关资料,说是组件名称没有大写开头,可是本人组件名称是大写!!!!但是提示next不是React组件方法名 ,感觉有点莫名其妙,想到next确实小写,本人将 useEffect 方式写在了函数"next"中,移到组件根块下报错消失!!!!所以useEffect是必须直接使用在组件结构下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/276733.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

白话机器学习的数学-1-回归

1、设置问题 投入的广告费越多&#xff0c;广告的点击量就越高&#xff0c;进而带来访问数的增加。 2、定义模型 定义一个函数&#xff1a;一次函数 y ax b &#xff08;a 是斜率、b 是截距&#xff09; 定义函数&#xff1a; 3、最小二乘法 例子&#xff1a; 用随便确定的参…

BDTC2023:CloudberryDB开源创新与实践

中国大数据技术大会&#xff08;BDTC&#xff09;由中国计算机学会&#xff08;CCF&#xff09;创立于2008年&#xff0c;已经成为国内外极具行业实践的专业大数据交流平台。12月22日-24日&#xff0c;第十七届中国大数据技术大会&#xff08;BDTC 2023&#xff09;在广州举行。…

文字识别技术在未来会有怎样的发展?

随着科技的不断发展&#xff0c;文字识别技术也在不断地改进和完善。未来&#xff0c;文字识别技术将会在更多的领域得到应用&#xff0c;并且将会更加智能化、高效化和个性化。 首先&#xff0c;随着深度学习技术的不断发展&#xff0c;文字识别技术将会更加智能化。目前&…

ubuntu22下安装minconda

bing 搜索 canda install 找到官方网站 https://docs.conda.io/projects/miniconda/en/latest/ 这里我们安装minconda。 官网有安装方法。 mkdir -p ~/miniconda3 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh…

docker的基本管理和相关概念

docker的基本管理和概念 docker&#xff1a;开源的应用容器引擎。基于go语言开发的。运行在linux系统当中的开源的&#xff0c;轻量级的“虚拟机” docker的容器技术可以在一台主机上轻松的为任何应用创建一个轻量级的&#xff0c;可移植的&#xff0c;自给自足的容器 docke…

Java 读取超大excel文件

注意&#xff1a;此参考解决方案只是针对xlsx格式的excel文件&#xff01; Maven <dependency><groupId>com.monitorjbl</groupId><artifactId>xlsx-streamer</artifactId><version>2.2.0</version> </dependency>读取方式1…

Android画布Canvas矩阵Matrix放大裁剪Rect区域的Bitmap,Kotlin

Android画布Canvas矩阵Matrix放大裁剪Rect区域的Bitmap&#xff0c;Kotlin private fun mydraw() {val originBmp BitmapFactory.decodeResource(resources, R.mipmap.pic).copy(Bitmap.Config.ARGB_8888, true)val newBmp Bitmap.createBitmap(originBmp.width, originBmp.h…

爬虫基础一(持续更新)

爬虫概念&#xff1a; 通过编写程序&#xff0c;模拟浏览器上网&#xff0c;然后让其去互联网上抓取数据的过程 分类&#xff1a; 1&#xff0c;通用爬虫&#xff1a;抓取一整张页面数据 2&#xff0c;聚焦爬虫&#xff1a;抓取页面中的局部内容 3&#xff0c;增量式爬虫&…

CUMT--Java--线程

目录 一、线程 1、概述 2、Java线程模型 3、主线程 二、创建线程 1、继承Thread类 2、实现Runnable接口 3、使用Callable和Future接口 三、线程生命周期 1、新建和就绪状态 2、运行和阻塞状态 3、死亡状态 四、线程优先级 五、线程同步 1、非同步情况 2、同步…

浅谈WPF之控件模板Control Template和数据模板Data Template

WPF不仅支持传统的Windows Forms编程的用户界面和用户体验设计&#xff0c;同时还推出了以模板为核心的新一代设计理念。在WPF中&#xff0c;通过引入模板&#xff0c;将数据和算法的“内容”和“形式”进行解耦。模板主要分为两大类&#xff1a;数据模板【Data Template】和控…

创建加密分区或者文件

文章目录 [GParted 中已清除的分区与未格式化的分区](https://superuser.com/questions/706624/cleared-vs-unformatted-partition-in-gparted)创建加密分区解密创建的加密分区以便挂载格式化设备未具体的格式&#xff08;这里为ext4格式&#xff09;创建挂载点目录挂载加密的文…

java在线票务系统(选座)Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java servlet 在线票务系统&#xff08;选座&#xff09;管理系统是一套完善的java web信息管理系统 系统采用serlvetdaobean&#xff08;mvc模式)&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要…

深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能

深入浅出图解C#堆与栈 C# Heaping VS Stacking 第六节 理解垃圾回收GC&#xff0c;提搞程序性能 [深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈](https://mp.csdn.net/mdeditor/101021023)[深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基…

泛目录是干什么用的蚂蚁seo泛程序

泛目录是干什么用的蚂蚁seo泛程序目录 泛目录是一种常见的网站优化方法&#xff0c;属于黑帽技术的一种。它的核心原理是利用高权重的网站继承目录&#xff0c;然后快速获得收录与排名。这种方法可以帮助网站在搜索引擎中获得更好的排名&#xff0c;从而吸引更多的流量。 泛目…

右键菜单“以notepad++打开”,在windows文件管理器中

notepad 添加到文件管理器的右键菜单中 找到安装包&#xff0c;重新安装一般即可。 这里有最新版&#xff1a;地址 密码:f0f1 方法 在安装的时候勾选 “Context Menu Entry” 即可 Notepad的右击打开文件功能 默认已勾选 其作用是添加右键快捷键。即&#xff0c;对于任何…

云计算:OpenStack 配置云主机实例的资源实现内网互通

目录 一、实验 1. 环境 2.配置项目及用户 3.配置规格实例与镜像 4.配置VPC 5. 配置安全组 6. 创建云主机 cs_01 &#xff08;cirros系统&#xff09; 7.创建云主机 cs_02 &#xff08;cirros系统&#xff09; 8.创建云主机 cs_03 &#xff08;cirros系统&#xff09; …

小米路由器2(R2D) 安装 MIXBOX

1. 先刷开发版 ROM http://www1.miwifi.com/miwifi_download.html 进入上述网页&#xff0c;找到 R2D 点击下载 开发版 ROM 教程 看 下载按钮上边的 “刷机教程” 刷机教程 2. 开启SSH工具 登录自己的小米账号后&#xff0c;里面会显示出 自己的 root密码&#xff1b; 默认…

uniapp中的uview组件库丰富的Keyboard 键盘 用法

目录 基本使用 #隐藏键盘"."符号 #是否打乱按键的顺序 #如何控制键盘的打开和关闭&#xff1f; #如何监听键盘按键被点击&#xff1f; API #Props #Events #Slot 基本使用 通过mode参数定义键盘的类型&#xff0c;show绑定一个值为布尔值的变量控制键盘的弹…

每日一题:LeetCode-LCR 179. 查找总价格为目标值的两个商品

每日一题系列&#xff08;day 16&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

JVM 常用知识和面试题

1. 什么是JVM内存结构&#xff1f; jvm将虚拟机分为5大区域&#xff0c;程序计数器、虚拟机栈、本地方法栈、java堆、方法区&#xff1b; 程序计数器&#xff1a;线程私有的&#xff0c;是一块很小的内存空间&#xff0c;作为当前线程的行号指示器&#xff0c;用于记录当前虚拟…