一、创建umi应用

1、新建umi应用并启动

mkdir umi && cd umi
yarn create @umijs/umi-app
yarn
yarn start

2、umi应用的基本目录结构如下:

✓表示创建时已经拥有此文件,✕表示该文件需要手动创建.
✓   ├── package.json
✓   ├── .umirc.ts.
✕   ├── .env
✕   ├── dist
✓   ├── mock
✕   ├── public
✓   └── src
✓       ├── .umi
✕       ├── layouts/index.tsx
✓       ├── pages
✓           ├── index.less
✓           └── index.tsx
✕       └── app.ts
✕   └── config
✕       ├── config.ts
✕       ├── routes.ts

其中.umirc.ts为配置文件,路由默认在这里配置,但为了不让该文件过于庞大,在根目录下创建 config/config.ts ,并将路由拆分出来成 routes.ts

BUG: 如果config.ts和.umirc.ts同时存在,在修改这两个文件的时候不会更新,且在有路由嵌套的时候父组件刷新不出来(未解决),我目前只能把.umirc.ts删掉来解决这个问题

// config/routes.ts
export default [{ exact: true, path: '/', component: 'index' },
];// config/config.ts
import { defineConfig } from 'umi';
import routes from './routes';export default defineConfig({routes: routes,
});

二、配置式路由和约定式路由

配置式路由比较简单,好上手,所以在这里只讲配置式路由

1、基本属性
path:路径
component:匹配的组件
exact:是否开启精确匹配
routes:子路由
redirect:重定向
title:标题
wrappers:配置路由的高阶组件封装(该属性我也不太了解)

当前路由结构:

// config/routes.ts
// pages目录下新建count和person文件夹,person文件夹下新建male、female文件夹,文件名均为index.tsx。路由配置如下:
export default [{ exact: true, path: '/', component: 'index', title: '主页' },{ exact: true, path: '/count', component: 'count', title: '计数器' },{path: '/person', component: 'person', routes: [{ path: 'male', component: 'person/male', title: '男人' },{ path: 'female', component: 'person/female', title: '女人' }], title: '一堆人'},
];

2、向子路由传参(Person组件向Male组件传参)
1)params

// config/routes.ts
{ path: 'male/:id', component: 'person/male', title: '男人' },// Person组件
import { history } from 'umi'const Person = (props:any) => {const id = 1const showMale = () => {// history.push('/person/male')history.push(`/person/male/${id}`)}const showFemale = () => {history.push('/person/female')}return (<><h1>我是Person</h1><button onClick={showMale}>show Male</button>&nbsp;&nbsp;<button onClick={showFemale}>show Female</button>{ props.children }</>)
}export default Person

在子路由的props.match.params中取

2)search

// config/routes.ts
{ path: 'male', component: 'person/male', title: '男人' },// Person组件
history.push(`/person/male/?id=${id}`)

在子路由的props.location.search中取

3)query

// config/routes.ts
{ path: 'male', component: 'person/male', title: '男人' },// Person组件
history.push({pathname: '/person/male', query: { name: String(id) }})

在子路由的props.location.query中取,在umi中search和query特别像,一般不用search

4)state

// config/routes.ts
{ path: 'male', component: 'person/male', title: '男人' },// Person组件
history.push({pathname: '/person/male', state: { name: id }})

在子路由的props.location.state中取,这样做的好处是参数不会在地址栏中显示出来

5)通过cloneElement( Umi 3 官方推荐)

// Person组件
import React from 'react'
import { history } from 'umi'const Person = (props:any) => {const id = 1const showMale = () => {history.push('/person/male')// history.push(`/person/male/${id}`)// history.push(`/person/male/?id=${id}`)// history.push({pathname: '/person/male', query: { name: String(id) }})// history.push({pathname: '/person/male', state: { name: id }})}const showFemale = () => {history.push('/person/female')}return (<><h1>我是Person</h1><button onClick={showMale}>show Male</button>&nbsp;&nbsp;<button onClick={showFemale}>show Female</button>{/* { props.children } */}{React.Children.map(props.children, child => {return React.cloneElement(child, { foo: {name: id} });})}</>)
}export default Person

在子路由的props中直接可以取到foo

三、请求Mock数据

//安装mockjs用于模拟数据
yarn add @types/mockjs//安装axios用于请求数据
yarn add axios
//在mock文件夹下新建mockData.ts
// 引入 Mock
import Mock from 'mockjs'// 定义数据类型
export default {'GET /api/tags': Mock.mock({// 3条数据"info|3": [{// 商品种类"goodsClass": "女装",// 商品Id"goodsId|+1": 1,//商品名称"goodsName": "@ctitle(10)",//商品地址"goodsAddress": "@county(true)",//商品等级评价★"goodsStar|1-5": "★",//商品图片"goodsImg": "@Image('100x100','@color','小甜甜')",//商品售价"goodsSale|30-500": 30,// 邮箱:"email": "@email",// 颜色"color": "@color",// name"name": "@name",//img,参数1:背景色,参2:前景色,参3:图片的格式,默认png,参4:图片上的文字"img": "@image('100*100','@color')",//英文文本(句子)参1:句子的个数,参2:句子的最小个数  参3:句子的最大个数,没有参1时,参2参3才会生效"Etext": "@paragraph(1,1,3)",//中文文本(句子)参1:句子的个数,参2:句子的最小个数  参3:句子的最大个数,没有参1时,参2参3才会生效"Ctext": "@cparagraph(1,1,3)",//中国大区"cregion": "@region",// 省"cprovince": "@province",//市"ccity": "@city",//省 + 市"ss": "@city(true)",//县"country": "@county",//省市县"countrysx": "@county(true)",//邮政编码"code": "@zip"}]})
}

1、通过axios请求(Male组件)

import axios from 'axios'const Male = () => {const getData = () => {axios.get('/api/tags').then(res => {//在res.data.info中取到数据console.log(res.data.info);})}return (<><h2>我是Male</h2><button onClick={getData}>axios请求mock数据</button></>)
}export default Male

2、通过fetch请求(Female组件)

const Female = () => {const getData = async () => {try {const res = await fetch('/api/tags')const data = await res.json()//在data.info中取到数据console.log(data.info);} catch (error) {console.log(error);}}return (<><h2>我是Female</h2><button onClick={getData}>fetch请求mock数据</button></>)
}export default Female

四、在umi中使用dva

1、在src下新建models文件夹(这个名字不能随便取,umi约定/src/models下的文件为dva模块),在models下新建store.ts(名字随意且可以有多个文件),我打算在Male组件和Female组件中增加男人和女人,然后在Count组件中可以得到男人和女人的总人数

// store.ts
export default {//有多个文件时命名空间不能重复namespace: 'store',//state中保存状态state: {male: [],female: [],count: 0},//reducers对比vuex的mutations,用于同步修改reducers: {addMale(state: { [propName: string]: any }, action: { [propName: string]: any }) {//注意:state.male必须通过这种方式修改,通过push方式会导致页面不更新state.male = [...state.male, action.payload]state.count++return { ...state }},addFemale(state: { [propName: string]: any }, action: { [propName: string]: any }) {state.female = [...state.female, action.payload]state.count++return { ...state }},}
}
//Male组件
import { useRef } from 'react'
import axios from 'axios'
//引入dva中的connect模块
import { connect } from 'dva'
//nanoid用于生成随机字符串
import { nanoid } from 'nanoid'const Male = (props: any) => {const nameInput = useRef<HTMLInputElement>(null)const ageInput = useRef<HTMLInputElement>(null)const getData = () => {axios.get('/api/tags').then(res => {//在res.data.info中取到数据console.log(res.data.info);})}//点击按钮,若name和age不为空,则调用addMale用于修改state的值const addPerson = () => {const name = nameInput.current?.valueconst age = ageInput.current?.valueconst id = nanoid()if (name !== '' && age !== '') {props.addMale({ id, name, age });(nameInput.current as HTMLInputElement).value = '';(ageInput.current as HTMLInputElement).value = ''} else {alert('姓名和年龄不能为空')}}return (<><h2>我是Male</h2><button onClick={getData}>axios请求mock数据</button><br /><div><span>姓名:</span><input ref={nameInput} type="text" placeholder="请输入姓名" /></div><div><span>年龄:</span><input ref={ageInput} type="text" placeholder="请输入年龄" /></div><button onClick={addPerson}>添加男人</button><ul>{props.maleList.map((item: any) => {return <li key={item.id}>{item.name} : {item.age}</li>})}</ul></>)
}const actionCreator = {addMale: (payload: any) => ({ type: 'store/addMale', payload })
}//connect与react-redux类似
export default connect((state: any) => ({ maleList: state.store.male }), actionCreator)(Male)

Female组件和Male组件代码几乎一样,所以不做展示。

//count组件比较简单
import { history } from 'umi'
import { connect } from 'dva'const Count = (props: any) => {const goIndex = () => [history.push('/')]return (<><div><h1>我是Count</h1><button onClick={goIndex}>GO Index</button><p>男女总人数为:{ props.count }</p></div></>)
}export default connect((state: any) => ({count: state.store.count}))(Count)

以上代码即可完成在Male组件与Female组件中添加男(女)人后,去到Count组件可以看到男女总人数,这就已经实现了数据共享

2、dva中的effect

1)put,用于触发action,我打算在Count组件中添加一个清零按钮,点击后清除已经添加的男人和女人,总人数清零

//store.tseffects: {*clear(_: any, { put }: any) {yield put({type: 'clearState',})}}
//在reducers中新增吃clearState
clearState() {return {male: [],female: [],count: 0}
}
//Count组件
import { history } from 'umi'
import { connect } from 'dva'const Count = (props: any) => {const goIndex = () => [history.push('/')]return (<><div><h1>我是Count</h1><button onClick={goIndex}>GO Index</button><p>男女总人数为:{props.count}</p><button onClick={() => { props.clear() }}>清零</button></div></>)
}const actionCreator = {clear: () => ({ type: 'store/clear' })
}export default connect((state: any) => ({ count: state.store.count }), actionCreator)(Count)

2)call,用于调用异步逻辑,现在我想在点击清零按钮之后等5秒再将人数清零,那么我们仅需要修改一下effects。注意:call函数的第一个参数需返回一个Promise对象

  effects: {*clear(_: any, { put, call }: any) {yield call(() => {return new Promise((resolve,reject) => {setTimeout(resolve,5000)})})yield put({type: 'clearState',})}}

3)select,用于从state里获取数据。这里有一个大坑,会报一个错:‘yield’ expression implicitly results in an ‘any’ type because its containing generator lacks a return-type annotation. ts(7057),我们把它翻译过来:“yield”表达式隐式生成“any”类型,因为其包含的生成器缺少返回类型注释。贴一个讲此问题的帖子:点我去看
具体到这个项目来说就是添加一个返回类型限制,代码如下:

  effects: {*clear(_: any, { put, call, select }: any): Generator {yield call(() => {return new Promise((resolve, reject) => {setTimeout(resolve, 5000)})})//就是下面这句话报的错,原因是ts判断不出来*函数的返回类型,所以我们要给*函数加一个Generator返回类型限制const payload = yield select((state: any) => state.store.male)//我们就可以在清空数据之前获取一次数据console.log('会被清除的男人',payload);yield put({type: 'clearState',})}}

3、dva中的subscription。subscriptions 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。

我想每次进count页面我都在控制台打印当前的男人和女人数据,最终完整的store.ts文件如下

export default {namespace: 'store',state: {male: [],female: [],count: 0},reducers: {addMale(state: { [propName: string]: any }, action: { [propName: string]: any }) {state.male = [...state.male, action.payload]state.count++return { ...state }},addFemale(state: { [propName: string]: any }, action: { [propName: string]: any }) {state.female = [...state.female, action.payload]state.count++return { ...state }},clearState() {return {male: [],female: [],count: 0}},showPerson(state: { [propName: string]: any }) {console.log('@',state.male);console.log('@@',state.female);return state}},effects: {*clear(_: any, { put, call, select }: any): Generator {yield call(() => {return new Promise((resolve, reject) => {setTimeout(resolve, 1000)})})//就是下面这句话报的错,原因是ts判断不出来*函数的返回类型,所以我们要给*函数加一个Generator返回类型限制const payload = yield select((state: any) => state.store.male)console.log('会被清除的男人',payload);yield put({type: 'clearState',})}},subscriptions: {setup({ dispatch, history }: any) {history.listen(({ pathname }: any) => {if (pathname === '/count') {dispatch({type: 'showPerson'})}})}}
}

五、总结

至此,一个简要的umi+dva应用就搭建完成了。其中用到了umi的路由、数据模拟,用到了dva的状态管理,掌握了这些知识就可以自己开发项目啦。我把demo放在了git仓库,需要自取:戳我下载

使用umi快速搭建项目以及如何在umi中使用dva进行状态管理相关推荐

  1. JavaEE企业级快速开发平台jeesite4的使用和快速搭建项目

    场景 JeeSIte是一个JavaEE企业级快速开发平台,基于经典技术组合(SpringBoot.Apache Shiro .MyBatis.Beetl.Bootstrap)在线代码生成工具,支持Sp ...

  2. 【入门】React 17 + Vite + ECharts 实现疫情数据可视化「02 快速搭建项目」

    往期文章目录: [入门]React 17 + Vite + ECharts 实现疫情数据可视化「01 项目介绍篇」 文章目录 快速搭建项目 介绍 Vite Vite 特点 搭建第一个 Vite 项目 ...

  3. 如何在JavaScript中直观地设计状态

    by Shawn McKay 肖恩·麦凯(Shawn McKay) 如何在JavaScript中直观地设计状态 (How to visually design state in JavaScript) ...

  4. android togglebutton 动画,如何在Android中使用ToggleButton多状态按钮控件

    如何在Android中使用ToggleButton多状态按钮控件 发布时间:2020-12-05 16:53:37 来源:亿速云 阅读:84 作者:Leah 这篇文章给大家介绍如何在Android中使 ...

  5. nodejs快速搭建项目

    创建一个node文件夹,下载安装nodejs,傻瓜式操作 打开命令行检查node版本 node -v 快速搭建前端项目框架 全局下载vue vue-cli (npm install vue vue-c ...

  6. vue-cli3+cubeUI快速搭建项目

    想开发一个web项目,选择vue.js框架,如何使用vue-cli3帮我们快速的搭建项目,选择合适的参数配置呢? vue-cli3脚手架可以快速的帮我们完成搭建,此外,也需要考虑选择一个合适的UI库, ...

  7. 修正《用Docker快速搭建Go开发环境》文章中的一处错误

    上周写的文章<五分钟用Docker快速搭建Go开发环境>,文章发出去后有不少阅读量,而且从后台看的数据 60%的人都读完了.今天我自己用下面命令往 容器里的 Go 项目里下载包时发现了一处 ...

  8. 在vue项目中引入vuex(全局状态管理器)

    目录 Vuex是什么? State Getter Mutation Action Module 项目结构 Vuex是什么? Vuex是一个专为Vue.js应用程序开发的状态管理模式.它采用集中式存储管 ...

  9. #yyds干货盘点# 如何在 Kubernete 中做日志收集与管理(14)

    说到日志,你应该不陌生.日志中不仅记录了代码运行的实时轨迹,往往还包含着一些关键的数据.错误信息,等等.日志方便我们进行分析统计及监控告警,尤其是在后期问题排查的时候,我们通过日志可以很方便地定位问题 ...

最新文章

  1. 介绍一个很好用的Rsa加解密的.Net库 Kalix.ApiCrypto
  2. 砂石到芯片转变旅程:一千多道工序,数百英里
  3. Python的Django框架中forms表单类的使用方法详解
  4. 【Pytorch神经网络实战案例】09 使用卷积提取图片的轮廓信息(手动模拟Sobel算子)
  5. appium和airtest_关于Airtest自动化测试工具
  6. ubuntu下gvim启动出现gtk warning Invalid input string
  7. Ps 初学者教程,如何在图片中创建新背景?
  8. Rabbitmq消息队列(二) Hello World! 模拟简单发送接收
  9. NFS在Centos 6.3下的安装
  10. 580刷590bios_AMD rx470/480/570/580/590高端技术公版/非公强刷BIOS教程教学-没差老师出品...
  11. 计算机网络自顶向下方法 习题参考答案 第一章
  12. imdisk虚拟光驱安装linux,imdisk使用教程_Imdisk工具使用方法介绍_imdisk_imdisk虚拟光驱...
  13. c1侧方停车技巧图解解析停车要点
  14. 你也可以掌控EMI:EMI基础及无Y电容手机充电器设计
  15. 2022《中国企业敏捷实践白皮书》调研全面启动
  16. 软件测试以bug数来考核,软件测试能力提升及其思考
  17. 前端知识体系思维导图
  18. vue首次加载生命周期
  19. idea2021版本添加上一步和下一步操作到工具栏
  20. android笔记:长按APP图标弹出快捷方式(shortcuts)

热门文章

  1. 【MP4格式转换成MP3教程】
  2. Python 文件I/Oday14
  3. 【排序】图解基数排序
  4. 最简单的代码【数字图像处理】计算rice.png中米粒个数
  5. 虚拟机克隆后MAC地址IP地址修改
  6. Matlab|基于时间序列预测的粒子群优化混合非线性回归和自回归技术的比较
  7. 虚拟机安装linux(centos),详细
  8. 完整的连接器设计手册_深度解析特斯拉的电池快充连接器技术|附视频
  9. 北航生医数值分析学习心得6
  10. 二阶龙格库塔公式推导_二阶龙格—库塔公式.PPT