UmiJS4学习笔记
起步
官网学习:https://umijs.org/
开发环境
Umi.js 需要使用 Node.js来进行开发,因此请先确保电脑已经安装了 Node.js 且版本在 14 以上。
安装pnpm:npm install pnpm -g
创建项目
Umi 官方提供了一个脚手架 ,可以轻松快速创建一个项目:
pnpm dlx create-umi@latest
创建时会进行三个选择:
Pick Umi App Template ?> Simple App // 普通项目> Ant Design Pro // 使用了umi max> Vue Simple App // vue项目
Pick Npm Client ?> npm> cnpm> tnpm> yarn> pnpm (推荐)
Pick Npm Registry ?> npm> taobao (推荐)
选择完成 -> 等待项目创建。
路由
配置路由
在config/config.ts或者umirc.ts中通过routes进行配置:
export default {routes: [{ path: '/', component: 'index' }, { path: '/user', component: 'user' },],
}
path
只支持两种占位符
:id 的形式
*通配符,只能出现路由字符串的最后。
component
用于匹配成功后渲染的 React 组件路径。如果是相对路径,会从 src/pages 开始找起。
如果指向 src 目录的文件,可以用 @,也可以用 ../。比如 component: '@/layouts/basic',或者 component: '../layouts/basic',推荐用前者。
routes
export default { routes: [ { path: '/login', component: 'login' }, { path: '/', component: '@/layouts/index', routes: [ // 子路由 { path: '/list', component: 'list' }, { path: '/admin', component: 'admin' }, ], }, ], }
通过**<Outlet/ >**进行子路由渲染
rediret
export default { routes: [ { path: '/', redirect: '/list' }, // 访问 / 会跳转到 /list,并由 src/pages/list 文件进行渲染。 { path: '/list', component: 'list' }, ], }
wrappers
export default { routes: [ { path: '/user', component: 'user', wrappers: [ '@/wrappers/auth', ], }, { path: '/login', component: 'login' }, ] }
然后在 src/wrappers/auth 中,
import { Navigate, Outlet } from 'umi' export default (props) => { const { isLogin } = useAuth(); if (isLogin) { return <Outlet />; } else{ return <Navigate to="/login" />; } }
title
配置路由的标题。
加载组件
在项目 src 目录下创建 loading.tsx 或者 loading.jsx 或者 loading.js 文件,默认导出的组件会在组件加载的时候渲染。
const Loading:React.FC = () => { return( <div> <h1>正在加载中。。。</h1> </div> ) } export default Loading;
可以将开发者模式的网络切换为3G慢速查看效果
路由传参
Umi4 使用 react-router@6 作为路由组件,路由参数的获取使其 hooks。(参考react-router@6)
数据加载
在路由文件中,除了默认导出的页面组件外,再导出一个 clientLoader 函数,并且在该函数内完成路由数据加载的逻辑。
// pages/.../some_page.tsx import { useClientLoaderData } from 'umi'; export default function SomePage() { const data = useClientLoaderData(); return <div>{data}</div>; } export async function clientLoader() { const data = await fetch('/api/data'); return data; }
如上代码,在 clientLoader 函数返回的数据,可以在组件内调用 useClientLoaderData 获取。
Mock
Umi 约定 /mock 目录下的所有文件为 Mock 文件
Mock 文件默认导出一个对象,而对象的每个 Key 对应了一个 Mock 接口,值则是这个接口所对应的返回数据
export default { // 返回值可以是数组形式 'GET /api/users': [ { id: 1, name: 'foo' }, { id: 2, name: 'bar' } ], // 返回值也可以是对象形式 'GET /api/users/1': { id: 1, name: 'foo' }, }
自定义函数
export default { 'POST /api/users/create': (req, res) => { // 添加跨域请求头 res.setHeader('Access-Control-Allow-Origin', '*'); res.end('ok'); } }
关于 req 和 res 的 API 可参考 Express@4 官方文档 来进一步了解。
关闭 Mock
Umi 默认开启 Mock 功能,如果不需要的话可以从配置文件关闭:
// .umirc.ts export default { mock: false, };
引入 Mock.js
生成随机的模拟数据
import mockjs from 'mockjs'; export default { // 使用 mockjs 等三方库 'GET /api/tags': mockjs.mock({ 'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }], }), };
代理
只需在配置文件中加上以下代码:
proxy: { '/api': { 'target': 'http://jsonplaceholder.typicode.com/',proxy: { '/api': { 'target': 'http://jsonplaceholder.typicode.com/', 'changeOrigin': true, 'pathRewrite': { '^/api' : '' }, }, },
将 /api 前缀的请求,代理到 http://jsonplaceholder.typicode.com/
将请求来源修改为目标url
替换请求地址中的 /api 为 ''
▲ proxy 暂时只能解开发时(dev)的跨域访问问题,生产上的发生跨域问题的话,可以将类似的配置转移到 Nginx 容器上。
样式
CSS Modules
在 js 文件中引入样式时,如果赋予他一个变量名,就可以将样式以 CSS Module 的形式引入。
import styles from './index.css'; export default function () { return <div className={styles.title}> Hello World </div>; }
微生成器
使用
下面的命令会列出目前所有可用的生成器,可以通过交互式方式来选择你使用的功能,都有详细的提示。
$ umi generate # 或者 $ umi g
你也可以通过 umi g <generatorName> 的形式来使用对应的生成器。
生成器列表
参考官方文档:https://umijs.org/docs/guides/generator
数据流
model
这里以一个计数器为例
创建model
// src/models/counter.ts import { useState } from "react" export default () => { const [num, setNum] = useState(0); const increment = (num: number) => { setNum(num + 1); } const decrement = (num: number) => { setNum(num - 1); } return { num, increment, decrement } }
使用model
const Children01: React.FC = () => { const { num, increment, decrement } = useModel('counter'); return ( <h1>计数器model</h1> <h2>这是结果:{num}</h2> <button onClick={() => { increment(num) }}>+1</button> <button onClick={() => { decrement(num) }}>-1</button> <hr /> ) }
这就是一个 Model。插件所做的工作就是将其中的状态或数据变成了全局数据,不同的组件在使用该 Model 时,拿到的是同一份状态或数据。
性能优化
useModel() 方法可以接受可选的第二个参数,当组件只需要使用 Model 中的部分参数,而对其它参数的变化不感兴趣时,可以传入一个函数进行过滤。
const { add, minus } = useModel('counterModel', (model) => ({ add: model.increment, minus: model.decrement, }));
全局初始状态
全局初始状态在整个 Umi 项目的最开始创建。
编写 src/app.ts 的导出方法 getInitialState(),其返回值将成为全局初始状态。
// src/app.ts import { fetchInitialData } from '@/services/initial'; export async function getInitialState() { const initialData = await fetchInitialData(); return initialData; }
现在,各种插件和您定义的组件都可以通过 useModel('@@initialState') 直接获取到这份全局的初始状态
import { useModel } from 'umi'; export default () => { const { initialState, loading, error, refresh, setInitialState } = useModel('@@initialState'); return <>{initialState}</>; };
请求
request
request 接收的 options除了透传 axios 的所有 config 之外,
我们还额外添加了几个属性 skipErrorHandler,getResponse,requestInterceptors 和 responseInterceptors 。
request('/api/user', { params: { name : 1 }, timeout: 2000, // other axios options skipErrorHandler: true, getResponse: false, requestInterceptors: [], responseInterceptors: [], }
useRequest
插件内置了 @ahooksjs/useRequest ,你可以在组件内通过该 Hook 简单便捷的消费数据。
import { useRequest } from 'umi'; export default () => { const { data, error, loading } = useRequest(() => { return services.getUserList('/api/test'); }); if (loading) { return <div>loading...</div>; } if (error) { return <div>{error.message}</div>; } return <div>{data.name}</div>; };
request配置
在 src/app.ts 中你可以通过配置 request 项,来为你的项目进行统一的个性化的请求设定。
import type { RequestConfig } from 'umi'; export const request: RequestConfig = { timeout: 1000, // other axios options you want errorConfig: { errorHandler(){ }, errorThrower(){ } }, requestInterceptors: [], responseInterceptors: [] };
在这里配置的规则将应用于所有的 request 和 useRequest 方法。
errorConfig
接收你后端返回的数据并且需要抛出一个你自己设定的 error, 你可以在这里根据后端的数据进行一定的处理。
request 会 catch errorThrower 抛出的错误,并且执行你的 errorHandler 方法,
该方法接收两个参数,第一个参数是 catch 到的 error,第二个参数则是 request 的 opts。
requestInterceptors
为 request 方法添加请求阶段的拦截器。
传入一个数组,每个元素都是一个拦截器。
const request: RequestConfig = { requestInterceptors: [ // 直接写一个 function,作为拦截器 (url, options) => { // do something return { url, options } }, // 一个二元组,第一个元素是 request 拦截器,第二个元素是错误处理 [(url, options) => {return { url, options }}, (error) => {return Promise.reject(error)}], // 数组,省略错误处理 [(url, options) => {return { url, options }}] ] }
responseInterceptors
为 request 方法添加响应阶段的拦截器。
传入一个数组,每个元素都是一个拦截器。
const request: RequestConfig = { responseInterceptors: [ // 直接写一个 function,作为拦截器 (response) => { // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到 const { data = {} as any, config } = response; // do something return response }, // 一个二元组,第一个元素是 request 拦截器,第二个元素是错误处理 [(response) => {return response}, (error) => {return Promise.reject(error)}], // 数组,省略错误处理 [(response) => {return response}] ] }
dva
配置dva
首先你需要在umirc.ts里配置 dva: {} 打开 Umi 内置的 dva 插件。
添加model
model 的写法参考如下示例:
export default { state: { user: { name: 'zhangsan' }, }, effects: { *setName({ payload }: any, { call, put }: any) { console.log('异步访问') yield put({ type: 'update', payload: payload }) } }, reducers: { update(state: any, action: any) { return { ...state, ...action.payload } }, test(state: any) { console.log('test'); return state; }, }, }
connect
把组件和 model connect 在一起,并在组件中 dispatch 事件
const Children03 = (props: any) => { return ( <div> <h1>获取state</h1> <h2>{props.user.user.name}</h2> <button onClick={() => { props.dispatch({ type: 'user/update', payload: { user: { name: 'lisi' } } }) }}>修改name·同步</button><br /><br /> <button onClick={() => { props.dispatch({ type: 'user/setName', payload: { user: { name: 'wangwu' } } }) }}>修改name·异步</button> <hr /> </div> ) } export default connect(({ user }: any) => ({ user, }))(Children03);
文档
model
namespace # model 的命名空间,唯一标识一个 model,如果与文件名相同可以省略不写
state # model 中的数据
effects # 异步 action,用来发送异步请求
reducers # 同步 action,用来修改 state
connect
connect 的是用来将 model 和组件关联在一起的,它会将相关数据和 dispatch 添加到组件的 props 中。
dispatch
在使用 connect 将组件和 model 关联在一起的同时框架也会添加一个 this.props.dispatch 的方法,通过该方法你可以触发一个 action 到 model 中。
Reducer
reducer 是一个函数,用来处理修改数据的逻辑(同步,不能请求后端)。接受 state 和 action,返回老的或新的 state 。即:(state, action) => state。
Effects
put 用于触发 action 。
yield put({ type: 'todos/add', payload: 'Learn Dva' });
call 用于调用异步逻辑,支持 promise 。
const result = yield call(fetch, '/todos');
select 用于从 state 里获取数据。
const todos = yield select(state => state.todos);
loading 框架会默认添加一个命名空间为 loading 的 model,该 model 包含 effects 异步加载 loading 的相关信息,它的 state 格式如下:
{ global: Boolean, // 是否真正有异步请求发送中 models: { [modelnamespace]: Boolean, // 具体每个 model 的加载情况 }, effects: { [modelnamespace/effectname]: Boolean, // 具体每个 effect 的加载情况 }, }