构建react+react-redux+ts项目框架

一、使用create-react-app生成基于ts的项目框架

npm create-react-app "myReactProgram" --template typescript

备注:若是已有项目想要引入ts的话
安装:npm install typescript --save-dev
初始化配置文件:npx tsc --init (会生成tsconfig.json文件)
配置tsconfig.json 文件

{
  // 编译选项
  "compilerOptions": {
    // 生成代码的语言版本:将我们写的 TS 代码编译成哪个版本的 JS 代码
    // 命令行: tsc --target es5 11-测试TS配置文件.ts
    "target": "es5",
    // 指定要包含在编译中的 library
    "lib": ["dom", "dom.iterable", "esnext"],
    // 允许 ts 编译器编译 js 文件
    "allowJs": true,
    // 跳过类型声明文件的类型检查
    "skipLibCheck": true,
    // es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异
    "esModuleInterop": true,
    // 允许通过 import x from 'y' 即使模块没有显式指定 default 导出
    "allowSyntheticDefaultImports": true,
    // 开启严格模式
    "strict": true,
    // 对文件名称强制区分大小写
    "forceConsistentCasingInFileNames": true,
    // 为 switch 语句启用错误报告
    "noFallthroughCasesInSwitch": true,
    // 生成代码的模块化标准
    "module": "esnext",
    // 模块解析(查找)策略
    "moduleResolution": "node",
    // 允许导入扩展名为.json的模块
    "resolveJsonModule": true,
    // 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件
    "isolatedModules": true,
    // 编译时不生成任何文件(只进行类型检查)
    "noEmit": true,
    // 指定将 JSX 编译成什么形式
    "jsx": "react-jsx"
  },
  // 指定允许 ts 处理的目录
  "include": ["src"]
}

二、路由配置(路由表)

1、安装
npm install --save react-router-dom
2、使用

在index.tsx文件中用HashRouter包裹

import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import Root from './root';

ReactDOM.render(
  <HashRouter>
    <Root />
  </HashRouter>,
  document.getElementById('root')
)

路由表配置(src/routes/index.tsx)

import { Navigate } from 'react-router-dom';
import Login from '../views/Login';
import Home from '../views/Home';

const routes = [
  {
    path: '/login',
    auth: false,
    component: <Login />,
  },
  {
    path: '/home',
    auth: true,
    component: <Home />,
  },
  {
    path: '/*',
    component: <Navigate to='/login' replace={true} />,
  },
];

export default routes;

在root.tsx文件中注册
注: 生成路由时遍历做了路由拦截–未登录时则跳转回登录页,导出路由

// 路由守卫-未登录则跳转至登录页
  const RouteNav = (param: any) => {
    return param.map(
      (item: {
        path: string;
        auth: boolean;
        component: ReactNode;
        child?: any;
      }) => {
        return (
          <Route
            path={item.path}
            element={
              item.path === pathname && item.auth && !isLogin ? (
                <Navigate to={'/login'} replace={true}></Navigate>
              ) : (
                item.component
              )
            }
            key={item.path}
            >
            {item?.child && RouteNav(item.child)}
          </Route>
        );
      }
    );
  };

return (
    <Fragment>
      {pathname === '/login' ? null : <Header />}
      <div className='main'>
        {pathname === '/login' ? null : (
          <SideMenu menuData={_sideMenuData}></SideMenu>
        )}
        <div>
          <Routes>{RouteNav(routes)}</Routes>
        </div>
      </div>
    </Fragment>
  );

三、axios请求处理

1、安装
yarn add axios
2、axios封装

request.ts文件

import axios from 'axios';

const BASE_ERROR_MSG = '数据请求失败';

// 创建 axios 实例
const request = axios.create({
  baseURL: 'https://trct.test.yhjcare.com', //请求公共地址
  timeout: 10000,
});

// 请求拦截
request.interceptors.request.use((config: any) => {
  const token = window.localStorage.getItem('token');
  if (token) {
    config.headers['Authorization'] = token;
  }
  // 租户ID
  config.headers['tenantId'] = '1';
  return config;
});

// 响应拦截
request.interceptors.response.use(
  (res) => {
    const { status, data: { errorCode = BASE_ERROR_MSG } = {} } = res;
    // 请求成功
    if (status === 200) {
      // 返回数据
      return res.data;
    } else {
      // 请求异常
      throw res;
    }
  },
  (err = {}) => {
    // 统一提示异常 --之后补充
    const { message = '' } = err;
  }
);

export default request;

3、请求封装

api.ts文件

import request from './request';

export const getUserInfo = ({ method = 'post', url = '', data = {} } = {}) =>
  request({
    url,
    method,
    // params,
    data: data,
  });

const BASE_ERROR_MSG = '数据请求失败';

// 请求队列
const _task: any = {};
export const api = async ({
  type = '',
  body: { method = 'post', url = '', data = {}, params = {} },
  then = () => {},
}: any) => {
  if (_task[type]) {
    return {};
  }
  try {
    _task[type] = 1;
    const res: any = await request({ url, method, data, params });
    const { errorCode = '', errorMessage = BASE_ERROR_MSG } = res;
    // 统一提示日常
    if (errorCode !== '0000') {
      // 待补充
    }
    then(res);
    _task[type] = 0;
    return res;
  } catch (error) {
    _task[type] = 0;
    return {};
  }
};

4、使用
import { api } from '../../utils/api';

async function getUserData() {
    const res: any = await api({
      type: 'getUserData',
      body: {
        url: '',
        data: { username: '', password: '',},
      },
    });
    console.log(res);
  }

四、redux环境配置

1、安装
yarn add react-redux @reduxjs/toolkit
2、实现

store/index.ts

/* app/store.ts */
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import getUserInfoReducer from './asyncSlice';

export const store = configureStore({
  reducer: {
    userInfo: getUserInfoReducer
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action<string>>;

store/hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './index';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

异步请求发送及获取结果更改redux数据中状态
asyncSlice.ts

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { api } from '../utils/api';

export interface CounterState {
  isLogin: number;
}
const initialState: CounterState = {
  isLogin: 0,
};

export const getUserInfo = createAsyncThunk(
  'fetchUserInfo/getUserInfo',
  async (params: any) => {
    const reponse = await api({
      type: 'getUserInfo',
      body: { url: '/meet/backend/admin/login', data: params },
    });
    return reponse.data;
  }
);

export const getUserInfoSlice = createSlice({
  name: 'login',
  initialState,
  reducers: {
    changeLoginState: (state) => {
      state.isLogin = 1;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(getUserInfo.pending, (state) => {
        state.isLogin = 0;
      })
      .addCase(getUserInfo.fulfilled, (state, action) => {
        const token = action.payload?.token;
        window.localStorage.setItem('token', token);
        if (token) {
          state.isLogin = 1;
        }
      })
      .addCase(getUserInfo.rejected, (state) => {
        state.isLogin = 0;
      });
  },
});

// export const { changeLoginState } = getUserInfoSlice.actions;
export default getUserInfoSlice.reducer;

五、iconfont引入antd中

1、public文件夹下放入以下图标文件

构建react+react-redux+ts项目框架_第1张图片

2、react组件中引入
import { createFromIconfontCN } from '@ant-design/icons';
const CustomIcon = createFromIconfontCN({
    scriptUrl: '/static/iconfont.js', //图标路径
 });
 
 <CustomIcon style={{ fontSize: '18px' }} type='icon-refresh'/ />

六、使用dotenv-cli在不同环境下打包

1、安装
yarn add dotenv-cli -D
2、根目录下新建三个文件

.env.development

NODE_ENV="development"
REACT_APP_ENV="development"
REACT_APP_API='https://api.development.com'

.env.test

NODE_ENV="test"
REACT_APP_ENV='test'
REACT_APP_API='https://api.test.com'

.env.production

NODE_ENV="production"
REACT_APP_ENV='production'
REACT_APP_API='https://api.production.com'
3、packjson.json修改
"scripts": {
    "start": "react-app-rewired start",
    "start:test": "dotenv -e .env.test react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject",
    "build:mock": "dotenv -e .env.development react-app-rewired build",
    "build:test": "dotenv -e .env.test react-app-rewired build",
    "build:prod": "dotenv -e .env.production react-app-rewired build"
  },
4、封装的请求路径

封装的axios请求中,如request.ts文件,请求的公共地址记得改

const request = axios.create({
  baseURL: process.env.REACT_APP_API, //请求公共地址
  timeout: 10000,
});

七、config-overrides.js文件参考配置的东西

const path = require('path');
const paths = require('react-scripts/config/paths');
// 非侵入式修改打包配置插件
const { override, addBabelPlugin, addWebpackAlias, overrideDevServer } = require('customize-cra');

// 打包环境和接口环境
let [env, api = ''] = process.env.npm_lifecycle_event.split(':');

// 开发环境打包
const start = env === 'start';
// 生产环境打包
const babel = env === 'babel';

// 修改输出文件夹为相对路径
paths.publicUrlOrPath = start ? '/' : './';

module.exports = {
  webpack: override(
    // 配置路径别名
    addWebpackAlias({
      '@': path.join(__dirname, 'src'),
      components: path.join(__dirname, 'src/components'),
      css: path.join(__dirname, 'src/assets/css'),
      img: path.join(__dirname, 'src/assets/img')
    }),
    addBabelPlugin(['@babel/plugin-proposal-decorators', { legacy: true }]),

    (config) => {
      // ES6语法加强编译
      config.entry = [
        require.resolve('core-js/stable'),
        require.resolve('regenerator-runtime/runtime'),
        // MOCK数据
        ...(api === 'mock' ? [require.resolve('./mock')] : []),
        config.entry
      ];

      // 开发环境配置
      if (start) {
        // 可追踪源文件
        config.devtool = 'eval-source-map';
      } else {
        // 生产环境不要开发调试工具
        config.devtool = false;

        // 设置输出的JS文件只生成一个文件
        config.optimization.splitChunks = {};
        config.optimization.runtimeChunk = false;

        // 设置不要生成 xxx.js.LICENSE.txt
        config['optimization']['minimizer'][0].options.extractComments = false;

        // 打包到线上环境使用,压缩图片和清理调试信息
        if (babel) {
          // 清理日志
          const compress = config['optimization']['minimizer'][0].options.minimizer.options.compress;
          compress.drop_console = true;
          compress.drop_debugger = true;
        }
      }

      return config;
    }
  ),
  devServer: overrideDevServer((config) => {
    return {
      ...config,
      proxy: {
        '/api': {
          target: 'http://xx.xxxx.cn/api',
          changeOrigin: true,
          secure: false,
          pathRewrite: {
            '^/api': ''
          }
        }
      }
    };
  })
};

八、使用mockjs完成前端独立请求假数据

1、安装
yarn add mockjs
2、根目录下新建mock文件夹

index.js

// 首先引入Mock
const Mock = require('mockjs');

// 设置拦截ajax请求的相应时间
Mock.setup({
  timeout: '200-1000',
});

let configArray = [];

// 使用webpack的require.context()遍历所有mock文件
const files = require.context('.', true, /\.js$/);
files.keys().forEach((key) => {
  if (key === './index.js') return;
  configArray = configArray.concat(files(key).default);
});

// 注册所有的mock服务
configArray.forEach((item = '') => {
  for (const [path, target] of Object.entries(item)) {
    const protocol = path.split('|');
    Mock.mock(new RegExp('^' + protocol[1] + '\\b'), protocol[0], target);
  }
});

模拟接口文件,如login.js

export default {
  'post|/user/grantAuth/login': () => {
    return {
      data: {
        token:
          'eyJhjHzREza1_yH_u49vweNlN5Ef3QEnafKM5-sKnse0a9X9Q',
      },
      errorCode: '0000',
      errorMessage: 'success',
      success: true,
    };
  },
};
3、组件使用

九、其它环境配置

1、安装 sass
yarn add sass -D
2、安装 eslint
yarn add eslint -D

eslint-plugin-react用于react项目中eslint规则的配置

yarn add eslint-plugin-react -D

将package.json中的eslintConfig修改为一下内容

  "eslintConfig": {
    "root": true,
    "env": {
      "browser": true,
      "es6": true,
      "node": true
    },
    "extends": "react-app",
    "rules": {
      "indent": [
        "error",
        2,
        {
          "SwitchCase": 1,
          "flatTernaryExpressions": true,
          "ignoredNodes": [
            "JSXElement *"
          ]
        }
      ],
      "linebreak-style": [
        "error",
        "unix"
      ],
      "quotes": [
        "warn",
        "single"
      ],
      "semi": [
        "warn",
        "always"
      ],
      "no-alert": "off",
      "no-console": "off",
      "import/no-anonymous-default-export": [
        2,
        {
          "allowObject": true
        }
      ]
    },
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },

五、引入antd

1、按需引入css
yarn add react-app-rewired@
2.1.8 babel-plugin-import customize-cra -D

六、问题汇总

1、只能在使用 “esModuleInterop” 标志时进行默认导入

构建react+react-redux+ts项目框架_第2张图片
解决办法:
tsconfig.json文件中添加配置

{
  "compilerOptions": { 
     "esModuleInterop": true,
  },
}
2、安装开发依赖与生产依赖
--save 简写 -S	安装生产环境下的依赖包
--save-dev 简写 -D 安装在开发环境下的依赖包
3、配置路由表时报错

“Login”表示值,但在此处用作类型。是否指“类型 Login”?
构建react+react-redux+ts项目框架_第3张图片
解决办法:
此处路由表我用的是.ts文件,修改为.tsx文件之后就好了(原理不太懂)
在这里插入图片描述

你可能感兴趣的:(react.js,javascript,前端)