手动搭webpack + React Hooks + TypeScript + Antd(二)

1,105 阅读4分钟

手动搭webpack + React Hooks + TypeScript + Antd(一)

本章主要讲react react-hot-loader react-router loadable typescript的引入

新建App.jsx 

将src/index.js中的App函数移出至src/App.js

src/App.js

import React from 'react'; 

function App() {
   return (
       <div>==start??、==</div>
   )
}

export default App;

src/index.js

import App from './App';

run的时候会报错

需要改成App.jsx才能跑
import App from './App.jsx';
如果不想每次都写后缀,可以在webpack.common.js中引入即可
// ...
module: { ... },

+ resolve: {
+   extensions: ['.jsx', '.js'],
+ },

plugins: [ ... ]
就会自动去找js jsx后缀的文件
目前修改index文件保存,浏览器会刷新更新,实现无刷新更新可以安装react-hot-loader

安装react-hot-loader

yarn add react-hot-loader -D

babel.config.js

const plugins = [
+    ['react-hot-loader/babel'],
];

webpack.dev.js 配置hot: true

devServer: {    
        contentBase: './dist',    
        host: '0.0.0.0',   
        public: `${process.env.DEV_HOST || localhost}:8080`,    
  +     hot: true,  
},

src/App.js 新增

import { hot } from 'react-hot-loader/root';

export default hot(App);
重新run则会在浏览器打印

此时无刷新更新已经成功

开始安装配置typescript

yarn add typescript -D
再安装@babel/preset-typescript去编译ts   [参见地址](https://babeljs.io/docs/en/babel-preset-typescript)
yarn add @babel/preset-typescript -D
根目录新增tsconfig.json文件,用于告诉ts怎么编译文件

tsconfig.json

{  
  "compilerOptions": {    
    "jsx": "react",    
    "outDir": "./dist/",    
    "isolatedModules": true,    
    "strict": true,    
    "noUnusedLocals": true,    
    "noUnusedParameters": true,    
    "noFallthroughCasesInSwitch": true,    
    "moduleResolution": "node",    
    "esModuleInterop": true,    
    "allowSyntheticDefaultImports": true,    
    "allowJs": true,    
    "baseUrl": "./",    
    "target": "es5",    
    "module": "ESNext",    
    "noImplicitAny": true  
 },  
 "include": ["src/**/*", "types/*"]
}

将入口文件后缀改成tsx

build/webpack.commom.js修改

entry: './src/index.tsx',

module: {
    rules: [
     {
        test: /\.(jsx?|tsx?)$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
   ],
},

resolve: {
    extensions: ['.tsx', '.ts', '.js'],
},
会发现index.tsx和App.tsx文件报错,[参见](https://www.jianshu.com/p/35742227738e/) TypeScript 引入第三方包,报无法找到模块错误

有两种方法解决
  1. 根据报错提示尝试安装该库的TypeScript版本 (该库的 ts 声明文件),也就是在该库的名称前加上 @types/

  2. 建立xxx.d.t申明文件

此处选方法二

在根目录新建types文件夹,新建index.d.ts文件

types/index.d.ts

declare module 'react';
declare module 'react-dom';
此时报错已经解决

但是使用Hooks需要确定组件返回

所以去掉方法一的配置直接安装

安装@types/react @types/react-dom

yarn add @types/react @types/react-dom -D

然后所有报错已经解决,至此typescript已经配置好

安装 react-router-dom

yarn add react-router-dom

在src下新建pages和routes两个文件夹,pages下新建home和about文件夹,分别新建index.tsx文件 加入Home和About

src/pages/home/index.tsx

import React from 'react'; 

const Home: React.FC = () => {
    return (
        <div>Home</div>
    )
}

export default Home;

src/pages/about/index.tsx

import React from 'react'; 

const About: React.FC = () => {    
    return (
        <div>About</div>
    )
}

export default About;

webpack.common.js中配置别名alias

build/webpack.common.js

resolve: {    
    extensions: ['.tsx', '.ts', '.js'],
+   alias: {      
+    '@pages': resolve('../src/pages'),   
+   },  
},

routes 文件夹下新建config.ts 和 index.tsx, 在config.ts中引入Home 和 About 但是报错

src/routes/config.ts

ts会导致报错找不到模块“@pages/home” 需要在tsconfig中配置相同路径,在tsconfig.ts中的compilerOptions中配置

tsconfig.ts

{  
  "compilerOptions": {    
    ...
+   "paths": {
+      "@pages/*": ["./src/pages/*"],
+   }
 },  
 "include": ["src/**/*", "types/*"]
}

如果以后引入其他alias 也需要配置tsconfig文件,错误已经解决

然后写入routes

src/routes/config.ts

import Home from '@pages/home';
// ...其他Compont

export interface IRouteItem {  
    name: string;  
    path: string;  
    exact: boolean;  
    component: React.FC;  
    title?: string;  
    meta?: any;  
    needAuth?: boolean; // 是否需要登录
};

export const routes: IRouteItem[] = [
    {
        name: 'Index',
        path: '/',
        exact: true,
        component: Home,
        title: '主页',
        meta: {},
    },
    {...},
];

src/routes/index.tsx

import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { routes, IRouteItem } from './config';

export default function Routes() {
  return (
    <BrowserRouter>
      <Switch>
        {routes.map(route => {
          const { name, path, exact, component: Component, ...rest } = route;
          return (
            <Route
              key={name}
              path={path}
              exact={exact}
              render={routeProps => {
                return (
                  <Component {...routeProps} />
                );
              }}
            ></Route>
          );
        })}
      </Switch>
    </BrowserRouter>
  );
}

在页面中url后加上/home 发现页面404了

react-router设置path无效,错误信息Cannot GET /xxx

在devServer里加上

build/webpack.dev.js

devServer: {    
  contentBase: './dist',    
  host: '0.0.0.0',   
  public: `${process.env.DEV_HOST || localhost}:8080`,    
  hot: true,  
+ historyApiFallback: true,
},

但是在VScode中,Component会报错

这是因为在Home和About里面的组件是继承React.FC

const About: React.FC = () => {

routeProps是react router返回的router

可以从react-router中引入RouteComponentProps或者RouteProps

src/routes/config.ts

+ import { RouteComponentProps, RouteProps } from "react-router";

所以IRouteItem中将component:React.FC改写成写入React.ComponentType<RouteProps>;

或者component: React.ComponentType<RouteComponentProps>;

参考 TypeScript + React最佳实践-第一节:Component类型化

src/routes/config.ts    IRouteItem 修改

export interface IRouteItem {  
    name: string;  
    path: string;  
    exact: boolean;  
-   component: React.FC; 
+   component: React.ComponentType<RouteProps>;       
    title?: string;  
    meta?: any;  
    needAuth?: boolean; // 是否需要登录
};
这样ts报错即解决

但是这样直接引入所有的Component会导致打包过大,所以需要引入react-loadable做按需加载

安装react-loadable

yarn add react-loadable -D

src/routes/config.ts

+ import Loadable from 'react-loadable';
但是会报TS错误

安装loadable ts声明文件

yarn add @types/react-loadable -D

src/routes/config.ts     中引入loadable

import Loadable, { LoadingComponentProps } from 'react-loadable';
再写一个loading过渡函数
function LoadPage() {
    return (
        <div>loading...</div>
    );
}
查询loadable nonde_modules里面源码引入泛型,修改引入内容

src/routes/config.ts   新增

type ComponentType = React.ComponentType<RouteComponentProps> | 
                        React.ComponentType<any>;

 interface ILoadable<Props> {
    loader(): Promise<React.ComponentType<Props> | 
                { default: React.ComponentType<Props> }>;
    loading?: React.ComponentType<LoadingComponentProps>;
 }

function loadable<Props>(props: ILoadable<Props>) {
    return Loadable({
        loading: LoadingPage,
        delay: 300,
        ...props,
    });
}
最后修改引入的Home和About

src/routes/config.ts

-  import Home from '@pages/home';
-  import About from '@pages/about';

const Home: ComponentType = loadable({
    loader: () => import('../pages/home'),
 });

const About: ComponentType = loadable({
    loader: () => import('../pages/about'),
});

src/pages/home/index.tsx

+  import { Link } from "react-router-dom";const Home: React.FC = () => {
    return (
        <>
            <div>Home</div>
+           <Link to="/about">to about</Link>
        </>
    )
}

export default Home;

浏览器中发现Home和About已经被分别打包成了

点击 to about

就成功进行懒加载了(*^▽^*)

通常在页面中会把页面中的search当做query直接传入

将url的参数改成object形式

src/routes/index.tsx

const query = Object.fromEntries(new URLSearchParams(location.search.substr(1)));
但是ts报错

因为在tsconfig.ts中为了考虑兼容性问题就把target指定为较低版本"target": "es5",如果使用了较高的es版本则ts会有报错提示。可以改成ESNext,具体哪些配置参见ts编译选项 修改后ts不会再报错,但是query依然报错

那是因为Route的Component的Prop上并没有query,可以查看RouteComponentProps源码👇

所以在config.ts中可以重写一个interface继承RouteComponentProps

src/routes/config.ts

interface IComponentProps extends RouteComponentProps {  
    query?: {    
        [key: string]: any;  
    };
}

type ComponentType = React.ComponentType<IComponentProps> | 
                        React.ComponentType<any>;
报错已经消失

页面中也能通过props直接获取query,而不需要每次都解析url参数一遍, Home或者About的props可打印出

至此react react-hot-loader react-router loadable typescript已经配置完成,接下来继续配置css less css-modules