老项目升级:React16升级至React17

3,108 阅读6分钟

最近接了个任务,老项目升16升级至18,项目组经过多方评估,还是决定先升级至17会顺滑一点。 现将升级步骤和情况记录一下。

升级步骤

原packgae.json文件配置如下:

{  
  "name": "web",  
  "version": "0.1.0",  
  "private": true,  
  "dependencies": {  
    "@ant-design/icons": "^4.5.0",  
    "@testing-library/jest-dom": "^5.11.9",  
    "@testing-library/react": "^11.2.5",  
    "@testing-library/user-event": "^12.8.3",  
    "@types/jest": "^26.0.20",  
    "@types/react": "^17.0.6",  
    "@types/react-dom": "^17.0.2",  
    "antd": "^4.2.4",  
    "axios": "^0.19.2",  
    "formik": "^2.2.6",  
    "history": "^4.10.1",  
    "moment": "^2.29.1",  
    "query-string": "^6.13.8",  
    "react": "^16.14.0",  
    "react-beautiful-dnd": "^13.1.0",  
    "react-diff-viewer": "^3.1.1",  
    "react-dom": "^16.14.0",  
    "react-highlight-words": "^0.17.0",  
    "react-query": "^3.34.0",  
    "react-quill": "^1.3.5",  
    "react-router-dom": "^5.2.0",  
    "react-scripts": "4.0.3",  
    "shortid": "^2.2.16",  
    "typescript": "^4.2.3",  
    "virtualizedtableforantd4": "^1.1.2",  
    "web-vitals": "^1.1.1"  
  },  
  "scripts": {  
    "start": "react-scripts start",  
    "build": "react-scripts build",  
    "test": "react-scripts test",  
    "eject": "react-scripts eject"  
  },  
  "eslintConfig": {  
    "extends": [  
      "react-app",  
      "react-app/jest"  
    ]  
  },  
  "browserslist": {  
    "production": [  
      ">0.2%",  
      "not dead",  
      "not op_mini all"  
    ],  
    "development": [  
      "last 1 chrome version",  
      "last 1 firefox version",  
      "last 1 safari version"  
    ]  
  },  
  "devDependencies": {  
    "@types/lodash": "^4.14.170",  
    "@types/node": "^14.14.35",  
    "@types/react-beautiful-dnd": "^13.1.1",  
    "@types/react-router-dom": "^5.1.7",  
    "@types/shortid": "^0.0.29"  
  }  
}

我们可以分析一下这个package.json文件,在此次升级中,主要升的是react,react-dom及对应的校验,先保证项目主体能正常运行,再升级其它的依赖。

经分析得出了如下升级步骤:

  1. 安装原项目环境,保证原项目能顺利使用;

  2. 注释所有的的业务代码及插件相关的代码,保证项目正常运行;

  3. 在2的基础上升级react16.14.0->react17.0.0,react-dom@16.14.0->react-dom@17.0.0,测试在去除所有业务代码的情況下,现有环境是否支持正常使用react17;

  4. 检查@testing-library/jest-dom,@testing-library/react,@testing-library/user-event,@types/jest,@types/react,@types/react-dom等与业务无关的插件是否匹配react17;

  5. 逐步放开业务代码,找出报错位置及原因,保证能正常調用;

    1. 测试antd组件能否正常使用

    2. 测试只调用router的情況,保证router5.2能正常运行

    React router官网文档中有说明The good news is that React Router v5 is compatible with React >= 15,理论上router5.2应该能和react 17.0匹配使用,待验证。

    1. 测试useReducer能否正常调用;
    2. 逐步放开业务代码中的其它组件及插件调用代码,保证能正常调用;

    保证整个项目所有的业务代码放开,能正常运行不报错,能打开登录界面正常运行,基本完成了整个项目的运行环境升级;

  6. 与后端联调,调用接口获取数据,测试单个页面的功能,按模块划分逐步测试升级。

问题记录

现将升级过程遇到的问题记录如下:

步骤2出现的问题

1. npm ERR! ERESOLVE unable to resolve dependency tree

运行安装命令npm install react-dom@17.0.0出现如下报错:

npm ERR! code ERESOLVE  
npm ERR! ERESOLVE unable to resolve dependency tree

根据提示,安装命令改为npm install react@17.0.0 --legacy-peer-deps,终于安装成功,同样的react-dom,也采用这个命令安装npm install react-dom@17.0.0 --legacy-peer-deps

  • [todo:后续有时间需要补充这个命令的说明]

步骤4出现的问题

查阅了官文档antd4.0支持React>=16.9.0,理论上antd 4.2.0应该能在React17.0.0正常使用,所以升级完react及react-dom后,未将antd升级至最新版。

React>=16.9.0的信息是在node_modules下的antd文件夹下的package.json文件中查到的:

"peerDependencies": {  
    "react": ">=16.9.0",  
    "react-dom": ">=16.9.0"  
  },
1.Invalid hook call

放开antd的调用代碼,报错如下:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:  
1. You might have mismatching versions of React and the renderer (such as React DOM)  
2. You might be breaking the Rules of Hooks  
3. You might have more than one copy of React in the same app  
See <https://reactjs.org/link/invalid-hook-call> for tips about how to debug and fix this problem.

通过这个报错去reactjs.org/link/invali… 查阅资料,查看是不是有react版本冲突问题:

npm ls react-dom

通过这个命令,查到我前面安裝的是react 17.0.0antd依賴的react的版是17.0.1,于是将项目的react,react-dom升级至17.0.1,瞬间解决了这个报错问题,哇0.0,真的是卡了好久。

image.png (图是后补的,你也可以根据你的情况,查询对应的依赖)

解决完上述问题,来到下一个坑。

2. Type ‘{}‘ is not assignable to type ‘ReactNode‘
TypeScript error in C:/work/.../InputField.tsx(17,10):  
Type '{ meta: FieldMetaProps<any>; prefix: ReactNode; children?: ReactNode; value: any; name: string; multiple?: boolean | undefined; checked?: boolean | undefined; onChange: { ...; }; onBlur: { ...; }; }' is not assignable to type 'InputProps'.  
  Types of property 'children' are incompatible.  
    Type 'React.ReactNode' is not assignable to type 'import("C:/work/.../node_modules/@types/react/index").ReactNode'.
 ...
Type ‘{}‘ is not assignable to type ‘ReactNode

查阅了相关文档,说这个报错是因为@types/react校验与当前react版本的匹配问题。

查看当前typescript版本,找出react17对应的校验版本,以保证原有的代码顺利运行不报类型错误。

npm view typescript version

显示当前的版本是4.8.4。

访问这两个网址

www.npmjs.com/package/@ty…

www.npmjs.com/package/@ty…

点击image.png,找出对应的版本进行安装。

image.png

查找到17.0.1较近的对应版本号3.5,尝试將typescript安裝为3.5 npm install typescript@3.5 安装后还是有新的报错出来:

TypeScript error in C:/../DateField.tsx(17,14):
'DatePicker' cannot be used as a JSX component.

安装至最新的@types/react:18.0.0,@types/react-dom:18.0.0也还是有类型错误校验报错:

C:/work/../adaContext.tsx
TypeScript error in C:/adaContext.tsx(30,41):
Property 'children' does not exist on type '{}'.  TS2339

    28 | });
    29 | 
  > 30 | export const AdaProvider: React.FC = ({ children }) => {
       |                                         ^
    31 |     const [ada, dispatch] = useReducer(AdaReducer, initalAdaState);
    32 | 
    33 |     // console.log(ada)

只好又回到前面,看Type ‘{}‘ is not assignable to type ‘ReactNode‘类型报错,翻看代码,尝试将有这种报错的尝试注释,看是否能成功跑动项目。

还是不行,只好将这两个依赖升级到这两个版本,

"@types/react": "^17.0.6",  
"@types/react-dom": "^17.0.9",

再次启动项目,终于成了,项目正常启动,目前还只走到5步骤,后续会持续更新。

React16直升18两个升级方案参考

这是在任务一开始的两个方案分析,也许对你的项目升级有参考意义,记录在此处。

react 16的老项目升到18,不止是react版本升级问题,还有依赖升级的问题,同时还有ant组件版本升级的问题,原有的router, reducer的使用都要升级,基本上是需要对原有的所有代码全部重新梳理,修复。

方案一:

  1. 先保证原项目正常运行,所有的安装环境,还原到没有升级的情况;
  2. 再注释所有的业务的代码,保证整个项目能最低限度的正确跑起来;
  3. 再干掉原来已有的各项插件依赖,归零的状态安装最新版本,如react,react-dom,保证主页能正常使用
  4. 这样,才能保证config文件是能正常使用的
  5. 但是新的问题来了,他现有的项目是通过什么创建的?create-react-app吗,是否需要升级CRA?

方案二:

采用CRA创建新的项目,再将这个项目的代码及结构再贴过去?单个页面的逐步替换,这样的好处是,项目依赖是最新的,不是裸配,只需要考虑代码在新的项目中的运行问题,逐步替换,是否能正确运行。 这个方案可行性更高。

方案二的实