Umi3快速上手(四)

写在前面

本人也是近期开始接触的umi3的,看了很多文档,自己也做了些总结,本文也主要借鉴了官方文档,还有一篇语雀上的教程

dva

umi3整合 dva 数据流,在 umi@3 中要使用 dva 的功能很简单,只要使用安装 @umijs/plugin-dva 并在 配置文件中开启 dva 配置。

$ yarn add @umijs/plugin-dva
# npm install @umijs/plugin-dva --save

如果你是通过脚手架创建的项目,则不用执行上面的步骤!

在config/config.ts中配置就可以了:

export default defineConfig({
  ...
	dva: {
    hmr: true,//表示是否启用 dva model 的热更新。
    immer: true//表示是否启用 immer 以方便修改 reducer
  },
  ...
})

介绍

包含以下功能,

  • 内置 dva,默认版本是 ^2.6.0-beta.20,如果项目中有依赖,会优先使用项目中依赖的版本。
  • 约定是到 model 组织方式,不用手动注册 model
  • 文件名即 namespace,model 内如果没有声明 namespace,会以文件名作为 namespace
  • 内置 dva-loading,直接 connect loading 字段使用即可
  • 支持 immer,通过配置 immer 开启

约定式的 model 组织方式

符合以下规则的文件会被认为是 model 文件,

  • src/models 下的文件
  • src/pages 下,子目录中 models 目录下的文件
  • src/pages 下,所有 model.ts 文件

比如:

+ src
  + models/a.ts
  + pages
    + foo/models/b.ts
    + bar/model.ts

其中 a.tsb.tsmodel.ts 如果其内容是有效 dva model 写法,则会被认为是 model 文件。

dva model 校验

默认,上一小节的找到的文件,会做一次校验,校验通过后,才会被添加到最终到 dva model 列表。

一些示例,

// 通过
export default { namespace: 'foo' };
export default { reducers: 'foo' };

// 通过
const model = { namespace: 'foo' };
export default model;

// 通过,支持 dva-model-extend
import dvaModelExtend from 'dva-model-extend';
export default dvaModelExtend(baseModel, {
  namespace: 'foo',
});

// 通过
export default <Model>{ namespace: 'foo' };

// 不通过
export default { foo: 'bar' };

配置

比如:

export default {
  dva: {
    immer: true,
    hmr: false,
  },
}

skipModelValidate

  • Type: boolean
  • Default: false

是否跳过 model 验证。

extraModels

  • Type: string[]
  • Default: []

配置额外到 dva model。

immer

  • Type: boolean
  • Default: false

表示是否启用 immer 以方便修改 reducer。

hmr

  • Type: boolean
  • Default: false

表示是否启用 dva model 的热更新。

umi 接口

常用方法可从 umi 直接 import。

比如:

import { connect } from 'umi';

接口包含,

  • connect

绑定数据到组件。

  • getDvaApp

获取 dva 实例,即之前的 window.g_app

  • useDispatch

hooks 的方式获取 dispatch,dva 为 2.6.x 时有效。

  • useSelector

hooks 的方式获取部分数据,dva 为 2.6.x 时有效。

  • useStore

hooks 的方式获取 store,dva 为 2.6.x 时有效。

命令

  • umi dva list model

查看项目中包含了哪些 model。

$ umi dva list model

类型

通过 umi 导出类型:ConnectRCConnectPropsDispatchActionReducerImmerReducerEffectSubscription,和所有 model 文件中导出的类型。

model 用例

import { Effect, ImmerReducer, Reducer, Subscription } from 'umi';

export interface IndexModelState {
  name: string;
}

export interface IndexModelType {
  namespace: 'index';
  state: IndexModelState;
  effects: {
    query: Effect;
  };
  reducers: {
    save: Reducer<IndexModelState>;
    // 启用 immer 之后
    // save: ImmerReducer<IndexModelState>;
  };
  subscriptions: { setup: Subscription };
}

const IndexModel: IndexModelType = {
  namespace: 'index',

  state: {
    name: '',
  },

  effects: {
    *query({ payload }, { call, put }) {
    },
  },
  reducers: {
    save(state, action) {
      return {
        ...state,
        ...action.payload,
      };
    },
    // 启用 immer 之后
    // save(state, action) {
    //   state.name = action.payload;
    // },
  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname }) => {
        if (pathname === '/') {
          dispatch({
            type: 'query',
          })
        }
      });
    }
  }
};

export default IndexModel;

page 用例

import React, { FC } from 'react';
import { IndexModelState, ConnectProps, Loading, connect } from 'umi';

interface PageProps extends ConnectProps {
  index: IndexModelState;
  loading: boolean;
}

const IndexPage: FC<PageProps> = ({ index, dispatch }) => {
  const { name } = index;
  return <div >Hello {name}</div>;
};

export default connect(({ index, loading }: { index: IndexModelState; loading: Loading }) => ({
  index,
  loading: loading.models.index,
}))(IndexPage);

或者

import React from 'react';
import { IndexModelState, ConnectRC, Loading, connect } from 'umi';

interface PageProps {
  index: IndexModelState;
  loading: boolean;
}

const IndexPage: ConnectRC<PageProps> = ({ index, dispatch }) => {
  const { name } = index;
  return <div >Hello {name}</div>;
};

export default connect(({ index, loading }: { index: IndexModelState; loading: Loading }) => ({
  index,
  loading: loading.models.index,
}))(IndexPage);

FAQ

  • import { connect 等 API } from umi 无效?

检查:

  1. dva 配置有没有开启,该插件是配置开启的
  2. 有没有有效的 dva model,可通过执行 umi dva list model 检查,或者执行 umi g tmp 后查看 src/.umi/plugin-dva/dva.ts 中检查 model 注册情况

以及 tsconfig.json 等定义问题,参考 FAQ#import from umi 没有定义怎么办?

  • 我的 model 写法很动态,不能被识别出来怎么办?

配置 dva: { skipModelValidate: true } 关闭 dva 的 model 校验。

HTTP请求umi-request

在 umi 项目中并没有规定一定要使用某种 http 请求方式,我们可以根据实际项目,选择自己最熟悉和服务端交互最优的 http 请求方式,一般项目中我们比较常用的就是 fetch 和 axios ,这两者的优缺点比较,可以查阅其他资料,但是无论你选择哪一种,在你需要更换成另一种时,只需要修改几行代码,因为它们在使用上,只有一点点参数结构的差异。

umi中集成了umi-request插件,所以我就选择了使用umi-request

废话不多说,直接看例子,在service.ts中这么写

import request from '@/utils/request';
import { MoreListParams } from './data.d'

export async function getChannelData (params?:MoreListParams){
    return request(`api/getChannelData`,{
        method:'GET',
        data:{...params}
    })
}

model.ts这么调用

import { Effect, Reducer } from 'umi';
import { getChannelData } from './_service';
import { MoreListItem } from './data.d'

export interface MoreModelState {
    listData: MoreListItem[];
}

export interface MoreModelType {
    namespace: 'more';
    state: MoreModelState;
    effects: {
        getListdata: Effect;
    };
    reducers: {
        channelData: Reducer<{}>;
    };
}
const MoreModel: MoreModelType = {
    namespace: 'more',
    state: {
        listData: []
    },
    effects: {
      //这里调用请求
        *getListdata({ payload }, { call, put }) {
            const response = yield call(getChannelData, payload);
            yield put({
                type: 'channelData',
                payload: response
            })
        }
    },
    reducers: {
        channelData(state, action) {
            const datas = action.payload.data;
            return {
                ...state,
                listData:[...datas]
            };
        }
    }
}

export default MoreModel;

很多人可能会疑惑,你那个service.js中应用的那个request是怎么设置的呢?如果你是通过脚手架创建的项目,在你的src/utils的目录下有个,request.ts的文件,其中是umi给你集成好的(当然,你也可以自己修改):

/**
 * request 网络请求工具
 * 更详细的 api 文档: https://github.com/umijs/umi-request
 */
import { extend } from 'umi-request';
import { notification } from 'antd';

const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。',
};

/**
 * 异常处理程序
 */
const errorHandler = (error: { response: Response }): Response => {
  const { response } = error;
  if (response && response.status) {
    const errorText = codeMessage[response.status] || response.statusText;
    const { status, url } = response;

    notification.error({
      message: `请求错误 ${status}: ${url}`,
      description: errorText,
    });
  } else if (!response) {
    notification.error({
      description: '您的网络发生异常,无法连接服务器',
      message: '网络异常',
    });
  }
  return response;
};

/**
 * 配置request请求时的默认参数
 */
const request = extend({
  errorHandler, // 默认错误处理
  credentials: 'include', // 默认请求是否带上cookie
});

export default request;

proxy 请求代理

关于proxy的问题,我相信熟悉vue的同学应该都遇到这个,在前后端联调的时候,经常会遇到跨域的问题,这个时候我们的解决办法就是在vue.config.js文件中配置devServer来解决跨域的问题!

详细的 proxy 技术可以查阅其他资料,这里简要说明下:之所以会出现跨域访问问题,是因为浏览器的安全策略。所以我们预想是不是有一种方式,能够绕过浏览器的安全策略?

那就是先请求一个同源服务器,再由服务器去请求其他的服务器。比如:

  • 我们本来是要请求 https://pvp.qq.com 服务器,但是它存在跨域。
  • 所以我们先请求了 http://localhost:3000 (假设的),它不存在跨域问题,所以它受理了我们的请求,并且我们可以取得它返回的数据。
  • 而由 http://localhost:3000 返回的数据,又是从真实的 https://pvp.qq.com 获取来的,因为服务端不是在浏览器环境,所以就没有浏览器的安全策略问题。
  • 因为 http://localhost:3000 (假设的)这个服务器,它只是把我们请求的参数,转发到真实服务端,又把真实服务端下发的数据,转发给我们,所以我们称它为代理。

umi 提供了 proxy 来处理这个问题。

配置proxy.ts

proxy

  • ‘/api/’ 设置了代理的请求头,比如这里定义了 /api ,当你访问如 /api/abc 这样子的请求,就会触发代理

  • target 设置代理的目标,即真实的服务器地址

  • changeOrigin 设置是否跨域请求资源

  • pathRewrite 表示是否重写请求地址,比如这里的配置,就是把 /api 替换成空字符

Mock 数据

  1. 在实际项目开发中,我们经常会遇到和服务端同步开发的情况。这时候我们就可以要求服务端先输出接口文档,前后端都根据接口文档输出。在项目交付之前一段时间,再进行前后端连调。

  2. 在demo开发学习的过程中,我们也常常遇到需要静态数据的情况

  3. 公司网络限制了只能访问部分外网,是的,墙里墙,还是满多的

  4. 没有网络的情况,呵呵,想到咖啡店敲个代码,悠哉悠哉,没有公用Wi-Fi,接口都挂了,页面渲染不出来

umi 中使用 mock

  • 约定式 Mock 文件

Umi 约定 /mock 文件夹下所有文件为 mock 文件。

比如:

.
├── mock
    ├── api.ts
    └── users.ts
└── src
    └── pages
        └── index.tsx

/mock 下的 api.tsusers.ts 会被解析为 mock 文件。

  • 编写 Mock 文件

如果 /mock/api.ts 的内容如下,

export default {
  // 支持值为 Object 和 Array
  'GET /api/users': { users: [1, 2] },

  // GET 可忽略
  '/api/users/1': { id: 1 },

  // 支持自定义函数,API 参考 express@4
  'POST /api/users/create': (req, res) => {
    // 添加跨域请求头
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.end('ok');
  },
}

然后访问 /api/users 就能得到 { users: [1,2] } 的响应,其他以此类推。

  • 配置 Mock

详见配置#mock

  • 如何关闭 Mock?

可以通过配置关闭,

export default {
  mock: false,
};

也可以通过环境变量临时关闭,

$ MOCK=none umi dev
  • mock使用的另一种方式

你可以在对应的页面文件夹下,例如下图就是在/pages/more目录下创建了一个_mock.ts的文件,也可以达到mock数据的效果

mock.ts

代码:

import { Request, Response } from 'express';

import { MoreListItem } from './data.d'
const channelTaleData:MoreListItem[] = [];

for(let i = 0; i < 20 ; i++){
    channelTaleData.push({
        id:i,
        name:`名字${i}`,
        age:i,
        city:`城市${i}`
    })
}

const searchChannelData = (name?:string) => {
    let result:MoreListItem[]
    if(name){
        result = channelTaleData.filter(item => {
            return item.name.indexOf(name) > -1;
        })
    }else{
        result = channelTaleData;
    }
    return result;
}

export default{
    'GET /api/getChannelData':{
        data:[...channelTaleData]
    },
    'POST /api/searchChannelData':(req: Request, res: Response) => {
        res.send({
            status:'ok',
            data:searchChannelData(req.body.name),
        })
    }
}

到此这个低配版的umi3的教程就结束了,
当然了,本人也在不断的踩坑中,无论是typescript,react,umi3…还有很多的坑要踩。文中有什么不妥之处,也欢迎广大网友,批评指正,如有需要提供某一模块更详细的教程的,欢迎留言。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值