使用umi快速搭建项目以及如何在umi中使用dva进行状态管理
一、创建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> <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> <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进行状态管理相关推荐
- JavaEE企业级快速开发平台jeesite4的使用和快速搭建项目
场景 JeeSIte是一个JavaEE企业级快速开发平台,基于经典技术组合(SpringBoot.Apache Shiro .MyBatis.Beetl.Bootstrap)在线代码生成工具,支持Sp ...
- 【入门】React 17 + Vite + ECharts 实现疫情数据可视化「02 快速搭建项目」
往期文章目录: [入门]React 17 + Vite + ECharts 实现疫情数据可视化「01 项目介绍篇」 文章目录 快速搭建项目 介绍 Vite Vite 特点 搭建第一个 Vite 项目 ...
- 如何在JavaScript中直观地设计状态
by Shawn McKay 肖恩·麦凯(Shawn McKay) 如何在JavaScript中直观地设计状态 (How to visually design state in JavaScript) ...
- android togglebutton 动画,如何在Android中使用ToggleButton多状态按钮控件
如何在Android中使用ToggleButton多状态按钮控件 发布时间:2020-12-05 16:53:37 来源:亿速云 阅读:84 作者:Leah 这篇文章给大家介绍如何在Android中使 ...
- nodejs快速搭建项目
创建一个node文件夹,下载安装nodejs,傻瓜式操作 打开命令行检查node版本 node -v 快速搭建前端项目框架 全局下载vue vue-cli (npm install vue vue-c ...
- vue-cli3+cubeUI快速搭建项目
想开发一个web项目,选择vue.js框架,如何使用vue-cli3帮我们快速的搭建项目,选择合适的参数配置呢? vue-cli3脚手架可以快速的帮我们完成搭建,此外,也需要考虑选择一个合适的UI库, ...
- 修正《用Docker快速搭建Go开发环境》文章中的一处错误
上周写的文章<五分钟用Docker快速搭建Go开发环境>,文章发出去后有不少阅读量,而且从后台看的数据 60%的人都读完了.今天我自己用下面命令往 容器里的 Go 项目里下载包时发现了一处 ...
- 在vue项目中引入vuex(全局状态管理器)
目录 Vuex是什么? State Getter Mutation Action Module 项目结构 Vuex是什么? Vuex是一个专为Vue.js应用程序开发的状态管理模式.它采用集中式存储管 ...
- #yyds干货盘点# 如何在 Kubernete 中做日志收集与管理(14)
说到日志,你应该不陌生.日志中不仅记录了代码运行的实时轨迹,往往还包含着一些关键的数据.错误信息,等等.日志方便我们进行分析统计及监控告警,尤其是在后期问题排查的时候,我们通过日志可以很方便地定位问题 ...
最新文章
- 介绍一个很好用的Rsa加解密的.Net库 Kalix.ApiCrypto
- 砂石到芯片转变旅程:一千多道工序,数百英里
- Python的Django框架中forms表单类的使用方法详解
- 【Pytorch神经网络实战案例】09 使用卷积提取图片的轮廓信息(手动模拟Sobel算子)
- appium和airtest_关于Airtest自动化测试工具
- ubuntu下gvim启动出现gtk warning Invalid input string
- Ps 初学者教程,如何在图片中创建新背景?
- Rabbitmq消息队列(二) Hello World! 模拟简单发送接收
- NFS在Centos 6.3下的安装
- 580刷590bios_AMD rx470/480/570/580/590高端技术公版/非公强刷BIOS教程教学-没差老师出品...
- 计算机网络自顶向下方法 习题参考答案 第一章
- imdisk虚拟光驱安装linux,imdisk使用教程_Imdisk工具使用方法介绍_imdisk_imdisk虚拟光驱...
- c1侧方停车技巧图解解析停车要点
- 你也可以掌控EMI:EMI基础及无Y电容手机充电器设计
- 2022《中国企业敏捷实践白皮书》调研全面启动
- 软件测试以bug数来考核,软件测试能力提升及其思考
- 前端知识体系思维导图
- vue首次加载生命周期
- idea2021版本添加上一步和下一步操作到工具栏
- android笔记:长按APP图标弹出快捷方式(shortcuts)