React+Typescript使用之:ReactRouter、@loadable/component

5,107 阅读5分钟

前言

前段时间学习了React、Typescript,所以两个一起用试试; 有点遗憾的是Redux还没学,后续学了Redux后才算完整。。。没办法,三分钟热度的我还在学flutter啊。。。

说一下调接口的问题,最近包括上一份工作都遇到过,后端接口收不到参数。。。搞半天,结果是因为他们是从URL获取的请求参数,无论请求方式是否GET,,,所以沟通还是很重要的,如果后端大佬接口测试通过(postman/swagger等),但是页面上调用还是接收不到参数,,,就要去看他们接收参数的方式了,看他们测试的请求request URL就知道了

代码:react-ts

0、创建项目

提前学习TypeScript、React相关用法

安装 create-react-app 脚手架

npm install -g create-react-app

创建 React+TypeScript 项目的命令

会自动安装一些typescript相关的东西,还有react的typescript版本,一些库的声明等

create-react-app my-app --scripts-version=react-scripts-ts

1、Typescript 校验规则:

注意修改配置需要重启项目才会生效

tslint.json:

{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts",
      "coverage/lcov-report/*.js"
    ]
  },
  "rules": {
    "no-debugger": false,
    "no-console": false,
    //声明interface是否需要首字母为大写的I
    "interface-name": false,
    //需要按字母顺序引入模块
    "ordered-imports": false,
    "object-literal-sort-keys": false,
    // jsx中匿名函数
    "jsx-no-lambda": false
  },
  "jsRules": {
    "no-debugger": false,
    "no-console": false,
    //声明interface是否需要首字母为大写的I
    "interface-name": false,
    //需要按字母顺序引入模块
    "ordered-imports": false,
    "object-literal-sort-keys": false
  }
}

2、路由

react好像没有命名式路由,只能通过path去跳转,也没有路由元信息meta;有时间看下有没有其他解决方案;
官网文档:react-router

  • <Route>有 children/render/component 三个属性决定当前路由渲染什么,exact 为true表示精确匹配路由,否则模糊匹配;
  • <Route component>,<Route render>优先级比<Route children>
    • <Route component>: 直接放组件
    • <Route render>: 直接在这里写 JSX
    • <Route children>: 与render类似;可以根据匹配(match)的url渲染,,, 这个暂时还不知道具体使用场景(可能是nav菜单栏,根据路由决定激活状态之类吧)

2.1 生成路由

<Switch>可以看作是单选框的作用,只匹配第一个符合条件的路由; 路由位置<Route>一般精确路由放前面,模糊路由放后面

// src/router.tsx
import * as React from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import Loadable from '@loadable/component';
import PageRoutes from './routes/index';

// 使用 import { lazy } from '@loadable/component';
// lazy()会有警告,跟React.lazy()一样的警告
const App = Loadable(() => import('./App'));
const ErrComp = Loadable(() => import('./views/ErrComp/index'));

// 生成 路由集合
const GetRoutes = () => {
  const AppRoute = 
    <Route 
      key='app' 
      path='/' 
      exact={true}
      component={App} 
    />;
  const ErrRoute = 
    <Route 
      key='err404' 
      exact={true} 
      path='/err404' 
      component={ErrComp} 
    />;
  const NoMatchRoute = 
    <Route 
      key='no-match' 
      component={ErrComp} 
    />;
  
  const routes = [AppRoute, ...PageRoutes, ErrRoute, NoMatchRoute];
  
  return (
    <Switch>
      {routes.map(route => route)}
    </Switch>
  );
}

export default function Routes() {
  return (
    <HashRouter>
      <GetRoutes />
    </HashRouter>
  );
}

2.2 路由拆分:

  • 2.2.1 模块路由

// src/routes/hello.tsx
import { Route } from 'react-router-dom';
import * as React from 'react';
import Loadable from '@loadable/component';

// 有子路由的话暂时这样处理吧,精确的放前面,模糊的放后面
export default [
  <Route 
    key="hello" 
    exact={true} 
    path="/hello" 
    component={Loadable(() => import('../views/Hello/index'))} 
  />,
  <Route 
    key="hello_child1" 
    exact={true} 
    path="/hello/child1" 
    component={Loadable(() => import('../views/Hello/hello_child1'))} 
  />,
  <Route 
    key="hello_id" 
    exact={true} 
    path="/hello/:id" 
    component={Loadable(() => import('../views/Hello/hello_id'))} 
  />
]
  • 2.2.2 懒加载

    图片来自@loadable/component

    • React.lazy()

    曾经我在另一个React的项目写的时候是支持 import(`../views/${name}`) 这种写法的。。。不知为何在这里(React + TypeScript)不行

    React.lazy(() => import(component));
    
    • @loadable/component
      Webpack v4+ and Babel v7+最好使用@loadable/component,而不是react-loadable,具体看 这里
    npm i @loadable/component
    

    然后在项目根目录新建一个文件 loadable.d.ts或者统一一个文件.d.ts中写, (如果不提示找不到模块,就不需要自己新建了) 写入声明 declare module '@loadable/component';

    使用:

    • import Loadable from '@loadable/component';
      const App = Loadable(() => import('./App'));
      正常使用,没有问题!!!
    • import { lazy } from '@loadable/component';
      const App = lazy(() => import('./App'));
      可以用的,但是会有警告(跟使用React.lazy()的问题一样):Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it. in Suspense
  • 2.2.3 路由集中处理

// src/routes/index.tsx
import hello from './hello';

export default [
  ...hello
]

2.3 withRouter/RouteComponentProps

  • 路由组件可以直接获取这些属性(history,location,match等),而非路由组件就必须通过withRouter修饰后才能获取这些属性了
  • 目的就是让被修饰的组件可以从props属性中获取history,location,match等方法

有两点要注意:

  1. RouteComponentProps的合成
  2. withRouter的写法: withRouter<thisProps>(Hello as any)

二者的区别也就是 RouteComponentProps的合成 方式不同而已

写法一:

import * as React from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';

export interface Props extends RouteComponentProps<any> {
  name?: string;
}

class Hello extends React.Component<Props, {}> {
  constructor(props: Props) {
    super(props);
  }

  // 方法名 需要添加 public/ private / protected
  public render() {...}
}

export default withRouter<Props>(Hello as any);

写法二:

import * as React from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';

export interface Props {
  name?: string;
}
type thisProps = Props & RouteComponentProps;

class Hello extends React.Component<thisProps, {}> {
  constructor(props: thisProps) {
    super(props);
  }

  // 方法名 需要添加 public/ private / protected
  public render() {...}
}

export default withRouter<thisProps>(Hello as any);

3、项目入口

// src/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Routes from './router';
import './index.css';
// import registerServiceWorker from './registerServiceWorker'; 

const Loading = () => <div>loading...</div>;

ReactDOM.render(
  <React.Suspense fallback={Loading}>
    <Routes />
  </React.Suspense>,
  document.getElementById('root') as HTMLElement
);

// registerServiceWorker();

4、组件

  • src/App.tsx

    // src/App.tsx
    import * as React from 'react';
    import './App.css';
    // import logo from './logo.svg';
    
    interface Props {
      [prop: string]: any
    }
    
    class App extends React.Component<Props, {}> {
      constructor(props: Props) {
        super(props);
    
        this.state = {};
      }
    
      public render() {
        const { history, location } = this.props;
        return (
          <div style={{display: location.pathname === '/' ? '' : 'none'}}>
            <p>这是app</p>
            <p>
              <a href="javascript:;" onClick={() => history.push('/hello')}>
                go hello
              </a>
            </p>
            <hr />
          </div>
        );
      }
    }
    
    export default App;
    
  • src/components/Hello.tsx

    // src/components/Hello.tsx
    import * as React from 'react';
    import { withRouter, RouteComponentProps } from 'react-router-dom';
    
    export interface Props {
      from?: string;
    }
    
    export interface States {
      times: number;
    }
    
    type thisProps = Props & RouteComponentProps;
    
    class Hello extends React.Component<thisProps, States> {
      constructor(props: thisProps) {
        super(props);
    
        this.state = {
          times: 0
        }
    
        this.bclick = this.bclick.bind(this);
      }
    
      public bclick() {
        this.setState({
          times: this.state.times+1
        })
      }
    
      // 方法名 需要添加 public/ private / protected
      public render() {
        const { from } = this.props;
        const { times } = this.state;
    
        return (
          <div>
            <h4>Hello { from }</h4>
            点击了 {times}
            <p>
              <button onClick={this.bclick}>点击</button>
            </p>
          </div>
        )
      }
    }
    
    export default withRouter<thisProps>(Hello as any);