1)、简介
UmiJS
读音:(乌米)
UmiJS
是一个可插拔的企业级React
应用框架。官网地址是:https://umijs.org/zh/
特点:
-
插件化
umi
的整个生命周期都是插件化的,甚至其内部实现就是由大量插件组成,比如:pwa、按需加载、一键切换preact
、一键兼容 ie9 等等,都是由插件实现。 -
开箱即用
你只需一个umi
依赖就可启动开发,无需安装react
、preact
、webpack
、react-router
、babel
、jest
等等。 -
约定式路由
类似next.js
的约定式路由,无需再维护一份冗余的路由配置,支持权限、动态路由、嵌套路由等等。
2)、安装
2.1)、安装 nodejs
要使用UmijS
首先要安装nodejs
环境,在Mac下安装nodejs
:
brew install nodejs
windows下安装,需要到官网下载安装程序,然后下一步下一步完成即可,也非常简单。这里不介绍了,也可自行百度,网上有很多。
2.2)、安装 yarn
可以把yarn
看做了优化了的npm
,其中tyarn
使用的是npm.taobao.org的源,速度要快一些。平常使用的话使用tyarn
即可。
npm i yarn tyarn -g --registry=https://registry.npm.taobao.org
2.3)、安装 umi
tyarn global add umi
查看是否安装成功:
umi -v
如果出现 'umi' 不是内部或外部命令,也不是可运行的程序 或批处理文件
或者提示 umi: command not found
3)、使用 umi
3.1)、创建项目
mkdir ~/Documents/umi-demo
3.2)、初始化
进入到项目文件夹下
cd ~/Documents/umi-demo
再创建umi
其他默认的目录,这些默认的目录名字不能写错。
mkdir -p {config,mock,src/pages,src/models,src/layouts}
config/
: 该目录下的config.js
默认是全局的配置
mock/
: 模拟后端请求数据的js目录
src/
: 代码目录
pages/
: 为前端页面, 页面的后缀名可以是.js或者.jsx
models/
: 为数据层, 处理数据的 js 文件
layouts/
: 默认是整个项目的基础布局文件
通过初始化命令将生成package.json
文件,它是NodeJS
约定的用来存放项目的信息和配置等信息的文件。
tyarn init -y
为项目设置一个默认的展开页面,因为umiJS
在后台启动时会默认加载src/pages/index.js
文件。使用下面命令可以通过umi命令创建index.js文件。
umi g page index
可以看到在pages下创建好了index.js和index.css文件。
3.3)、开发工具打开
目录结构
3.4)、运行项目
umi dev
4)、分层开发
4.1)、过程图示
说明:
上图中,左边是用户,中间为前端,右边为后端。我们对前端进行分层,可以分为Page
、Model
、Service
3层。
Page
负责与用户直接打交道:渲染页面、接受用户的操作输入,侧重于展示型交互 性逻辑。Model
负责处理业务逻辑,为 Page 做数据、状态的读写、变换、暂存等。Service
负责与 HTTP 接口对接,进行纯粹的数据读写。
其中 Page
层通过UmiJS
的umi-plugin-react
插件的dva
功能,可以调用Model
层定义的数据和方法;Model
层通过import
定义的异步请求函数request.js
来调用Service
层;而Service
层就是去后端请求数据。
4.2)、开始开发
4.2.1)、添加依赖
添加umi
的依赖
tyarn add umi --dev
添加umi-plugin-react
插件
tyarn add umi-plugin-react --dev
添加.gitignore
文件
node_modules
dist
.umi
在config/config.js
配置中引入umi-plugin-react
插件
export default {
plugins: [
['umi-plugin-react', {
dva: true,
antd: true
}]
]
};
4.2.2)、antd基本布局
添加基本布局和样式:
在layouts
文件目录下创建index.js
文件,在index.js
中我们写入:
import { Component } from 'react';
import { Layout } from 'antd';
// Header, Footer, Sider, Content组件在Layout组件模块下
const { Header, Footer, Sider, Content } = Layout;
class BasicLayout extends Component {
render() {
return (
<Layout>
<Sider width={256} style={{ minHeight: '100vh', color: 'white' }}>
Sider
</Sider>
<Layout >
<Header style={{ background: '#fff', textAlign: 'center', padding: 0 }}>Header</Header>
<Content style={{ margin: '24px 16px 0' }}>
<div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
{this.props.children}
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
</Layout>
</Layout>
)
}
}
export default BasicLayout;
上面代码中,我们创建了一个三部分的基本布局:Header 、Content 、Footer。然后我们将 Content 替换成 { this.props.children },这样之后我们设置的路由会通过替换 children 变量实现内容的切换。
上面需要引入的组件都安装好了之后,我们就可以来编写我们的前端代码了。我们可以根据上面的图,按照从下到上的顺序进行编写,也就是Service
→Model
→Page
来进行。
4.2.3)、Service异步请求数据
在 src 目录下创建 utils 目录, 创建 request.js 文件
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
return await response.json();
}
4.2.4)、Mock模拟数据
在 mock目录下创建UserList.js文件,用来模拟数据。因为有了umi默认集成了mock功能,所以只要编写mock数据即可。
export default {
'get /ds/list' : function (req, res) {
res.json({
data: ['zhangsan','lisi','wangwu']
})
}
}
4.2.5)、Model层中引入该 js 文件用于异步请求
import request from '../../utils/request'
export default {
namespace: 'user',
//该模型中的一些属性
state: {
data: []
},
//一些正常的同步方法
reducers: {
//state是原先的数据,result是effets中异步调用返回的数据
save(state, result){
//如果 result.data中存在数据,表示该函数是被异步调用初始化。直接返回
if (result.data){
return result.data;
}
let list = [...state.data, 'freeman'];
//返回更新后的state对象
return {
data: list
}
}
},
effects: {
// 这里定义异步方法
*initData(params, sagaEffects) { //定义异步方法
const {call, put} = sagaEffects; //获取到call、put方法
const url = "/ds/list"; // 定义请求的url
let result = yield call(request, url); //执行请求
yield put({ // 调用reducers中的方法
type : "save", //指定方法名
data : result //传递ajax回来的数据, 注意 put 会指定调用的同步方法[reducers 中定义的方法],
//该调用的方法会在定义的方法的入参添加一个参数(result), 使用该参数才能获取到put方法,取到的值
});
}
}
}
DVA 的 model 对象有几个基本的属性,需要大家了解。
namespace
:model
的命名空间,只能用字符串。一个大型应用可能包含多个model
,通过namespace
区分。state
:当前model
状态的初始值,表示当前状态。reducers
:用于处理同步操作,可以修改state
,由action
触发。reducer
是一个纯函数,它接受当前的state
及一个数据体(payload
)作为入参,返回一个新的state
。effects
:用于处理异步操作(例如:与服务端交互)和业务逻辑,也是由action
触发。但是,它不可以修改 state,要通过触发action
调用reducer
实现对state
的间接操作。action
:是reducers
及effects
的触发器,一般是一个对象,形如{ type: 'add', payload: todo }
,通过type
属性可以匹配到具体某个reducer
或者effect
,payload
属性则是数据体,用于传送给reducer
或effect
。
4.2.6)、Page层引入Model层的数据和方法
dva是基于 redux、redux-saga 和 react-router 的轻量级前端框架。官 网:https://dvajs.com/
@connect(mapStateToProps, mapDispatchToProps)
需要2个参数:
-
mapStateToProps
:是一个方法,该方法的返回值是一个属性对象{},它的作用是将这个包含state属性的对象注入到this.props中。组件通过this.props.xx的方式即可获取到model中的数据。- ① 、umi框架启动,会自动读取models目录下所有model文件,(如:user/List.js中的数据 )
- ②、这些model数据 会进入到
mapStateToProps
方法中 - ③、在全局的数据中,会有很多,所以需要通过namespace进行区分,所以通过
state[namespace]
进行获取数据 - ④、拿到model数据中的data,也就是
['zhangsan','lisi','wangwu']
数据,进行包裹{}后返回 - ⑤、返回的数据,将被封装到
this.props
中,所以通过this.props.listData
即可获取到 model中的数据。
-
mapDispatchToProps
: 是一个方法,它的返回值是一个函数对象{}, 它的作用是将这些函数注入到this.props中。- ①、所以可以把
Model
中暴露的方法绑定到当前的组件中,定义一个方法接收,然后可以绑定到onClick
事件上,就可以实现点击操作;也可以在页面加载完的时候拿到数据对页面进行渲染的操作(只需要绑定到生命周期函数上即可)。 - ②、dispatch 函数就是和 dva model 打交道的唯一途径。 dispatch 函数接受一个 对象 作为入参,在概念上我们称它为 action,唯一强制要包含的是 type 字段,string 类型,用来告诉 dva 我们想要干什么。我们可以选择给 action 附着其他字段,这里约定用 payload字段表示额外信息。
- ①、所以可以把
在 pages
目录下新建user/List.js
(或List.jsx
)页面,使用快捷键rcc
或rccp
可以快速生成react组件。
import React, {Component} from 'react';
import { connect } from 'dva';
const namespace = 'user';
const mapStateToProps = (state)=>{
let listData = state[namespace].data;
return {listData}
};
const mapDispatchToProps = (dispatch) => {
// 定义方法,dispatch是内置函数
return { //返回的这个对象将绑定到this.props对象中
addUser : () =>{
// 定义方法
dispatch({ // 通过调用dispatch()方法,调用model中reducers的方法
type: `${namespace}/save` // 指定方法,格式: namespace/方法名
});
},
userList : () => { //新增初始化方法的定义
dispatch({
type: `${namespace}/initData`
});
}
}
}
@connect(mapStateToProps, mapDispatchToProps)
class List extends Component {
componentDidMount() {
this.props.userList();
}
render() {
return (
<div>
<ul>
{
this.props.listData.map(
(v, i) => {return <li key={i}>{v}</li>}
)
}
</ul>
<button onClick={() => {this.props.addUser()}}>
添加
</button>
</div>
);
}
}
export default List;
4.3.)、umi-plugin-react插件升级
在运行 umi dev
或 umi build
运行或部署应用是,有时候会出现 “Path must be a string”错误。解决方法:
按照官网升级umi-plugin-react的版本。
- ①、 package.json文件
{
"devDependencies": {
- "umi-plugin-react": "^1"
+ "@umijs/preset-react": "^1"
}
}
- ②、 config/config.js文件
export default {
- plugins: [
- ['umi-plugin-react', {
- dva: {},
- antd: {},
- ...
- }]
- ],
+ dva: {},
+ antd: {},
+ ...
}
可以参考:https://umijs.org/docs/upgrade-to-umi-3#升级-umi-plugin-react-为-umijspreset-react