1.Umi 是什么?
Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
总之就是一句话,Umi是基于 React 的企业级前端框架。
2.路由系统实践(wrappers中间件配置)
Umi有内置的路由插件,不再和React需要引入安装react-router-dom,而是在启动好项目之后自动在.umirc.ts中配置好了约定性路由。所以的路由默认在layout的子路由展示。如果希望单独页面,就需要添加属性layout:false这样就是一个单独页面,这一块没什么好说的,我们知道不加layout就不是独立页面那么我们就可以在/路径下直接写嵌套路由,那么里面的路由都会成为layout的子路由。
import { defineConfig } from "umi";
export default defineConfig({
routes: [
{ path: "/login", component: "@/pages/login", layout: false }, // 独立页面
{ path: "/userinfo", component: "@/pages/userinfo", layout: false }, // 独立页面
{
path: "/",
// component: "@/layouts/index", // layout 包裹的页面
routes: [
{ index: true, component: "@/pages/index" },
{
path: "/dashboard",
component: "@/pages/Dashboard",
wrappers: ["@/warppers/auth"],
},
{
path: "/docs/:id",
component: "@/pages/docs",
wrappers: ["@/warppers/auth"],
},
],
},
{ path: "/*", component: "@/pages/404", layout: false },
],
});
就像这样。路由注册就完毕了。在umijs路由中,如果我们希望设置一些权限,比如登录之后有token,有了token才会展示一些路由组件的话,那么我们就需要嵌套一层wrappers组件。
编辑
这里我们就实现了有token才可以访问子路由。也就是让wrappers组件包裹一下。
3.路由切换时候的参数传递
很多时候包括请求或者切换组件的时候,路径的变化我们有时候会希望携带参数,这里有两种方式。
编辑
这里两种不同的跳转携带参数的方式对应两种不同的接收参数的方法。第一种注册路由的时候不需要添加东西,只需要在对应的路由组件用useSearchParams接收参数就可以了。
import { useSearchParams } from "umi";
const Dashboard = () => {
const [searchParams] = useSearchParams();
console.log("searchParams", searchParams.get("id")); // 888
const id = searchParams.get("id");
return (
<>
<div>我是工作台组件</div>
<h1>
这是通过单纯的?id=888zhi直接传递然后通过searchParams传递的id--{id}
</h1>
</>
);
};
export default Dashboard;
第二种需要用useParams接收参数,但是这里并没有用key=value的方式,只是传递了一个value那么我们需要在注册的路由的时候声明我们这个位置将要接收value的key。
编辑
import { useParams } from "umi";
const DocsPage = () => {
const params = useParams();
console.log("params", params);
return (
<div>
<p>This is umi docs.</p>
<h1>
这是通过params也就是路由声明/:id然后跳转时候/666携带过来的---{params.id}
</h1>
</div>
);
};
export default DocsPage;
这样两种路由传递参数的方式就在这里了。
4.useContext钩子的使用
组件通讯有很多种方式,父与子,子与父,以及状态管理工具,现在用react自带的钩子函数去让父组件传递给子组件数据,并且子组件修改数据之后可以变相影响到父组件实现数据共享。
import { Link, Outlet } from "umi";
import styles from "./index.less";
import { useEffect, useRef } from "react";
import { createContext, useState } from "react";
interface MyContextType {
name: string;
age: number;
changeName: () => void;
}
export const myContext = createContext<MyContextType>({
name: "lichengyang",
age: 18,
changeName() {
this.name = "lisi";
},
});
export default function Layout() {
console.log(" 这是我的布局组件");
const [name, setName] = useState("lichengyang");
const age = 18;
const changeName = () => {
setName("lisi");
};
return (
<div>
我是布局组件
<Link to="/dashboard?id=888">
<h1>点击我跳转到工作台</h1>
</Link>
<Link to="/docs/666">
<h1>点击我跳转到docs</h1>
</Link>
<p>{name}</p>
<myContext.Provider value={{ name, age, changeName }}>
<Outlet />
</myContext.Provider>
</div>
);
}
这样我们提供给子组件数据之后,子组件通过useContext钩子获取。
import yayJpg from "../assets/yay.jpg";
import { useContext } from "react";
import { myContext } from "@/layouts";
export default function HomePage() {
const { name, age, changeName } = useContext(myContext);
return (
<div>
<h2>Yay! Welcome to umi!</h2>
<p>
<img src={yayJpg} width="388" />
</p>
<p>
To get started, edit <code>pages/index.tsx</code> and save to reload.
这是我从useContext钩子获取到的数据 {name}----{age}
</p>
<button onClick={changeName}>点我修改名字</button>
</div>
);
}
这样就实现了共享组件通讯。
5.调用mock接口并且对数据进行CRUD
mock接口的定义也是约定型的,我们只需要在对应文件夹里面设置了那么就可以直接访问这个接口,
编辑
这里用的axios,然后增删改查不再叙述,无非就是对数组的各种遍历以及筛选。
import React from "react";
import axios from "axios";
import styles from "./userinfo.module.less";
export default function userinfo() {
interface Todo {
id: number;
title: string;
completed: boolean;
}
const [data, setData] = React.useState<Todo[]>([]);
const [title, setTitle] = React.useState("");
const [search, setSearch] = React.useState("");
const [changeTitle, setChangeTitle] = React.useState("");
const fetchData = async () => {
const res = await axios.get("/api/todos");
setData(res.data);
console.log("res", res);
};
React.useEffect(() => {
fetchData();
}, []);
const handAdd = async () => {
if (!title) {
alert("请输入待办事项");
return;
}
const obj = { id: Math.random(), title, completed: false };
setData([...data, obj]);
setTitle("");
};
const handleDel = (id: number) => {
setData(data.filter((item) => item.id !== id));
};
const update = (item: { id: number }) => {
const newData = data.map((todo) => {
if (todo.id === item.id) {
return { ...todo, title: changeTitle };
}
return todo;
});
setData(newData);
};
const handleSearch = () => {
const filteredData = data.filter((item) => item.title.includes(search));
setData(filteredData);
setSearch("");
};
const changeState = (id: number) => {
const newData = data.map((todo) => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
setData(newData);
};
return (
<>
<input
type="text"
placeholder="输入待办事项"
onChange={(e) => {
setTitle(e.target.value);
}}
value={title}
/>
<button onClick={handAdd}>新增</button>
{data.map((item: any) => (
<div className={styles.container} key={item.id}>
{item.id}---{item.title}---{item.completed ? "已完成" : "未完成"}
<button
onClick={() => {
handleDel(item.id);
}}
>
删除
</button>
<button
onClick={() => {
update(item);
}}
>
更新
</button>
<input
style={{ width: "80px", marginLeft: "10px" }}
type="text"
placeholder="更新标题"
onChange={(e) => {
setChangeTitle(e.target.value);
}}
/>
<button
onClick={() => {
changeState(item.id);
}}
>
改变状态
</button>
</div>
))}
<input
type="text"
placeholder="搜索"
onChange={(e) => {
setSearch(e.target.value);
}}
value={search}
/>
<button onClick={handleSearch}>搜索</button>
</>
);
}