



npm install -g @vue/cli
-g 参数表示全局安装,这样在任意目录都可以使用 vue 脚本创建项目
vue ui
使用图形向导来创建 vue 项目,如下图,输入项目名

选择手动配置项目

添加 vue router 和 vuex

选择版本,创建项目

devtools 插件网址:Installation | Vue Devtools

进入项目目录,执行
npm run serve
前端服务器默认占用了 8080 端口,需要修改一下
文档地址:DevServer | webpack
打开 vue.config.js 添加
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
port: 7070
}
}) 为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理
文档地址同上
打开 vue.config.js 添加
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}) 采用 vite 作为前端项目的打包,构建工具
npm init vite@latest
按提示操作
cd 项目目录 npm install npm run dev
推荐采用微软的 VSCode 作为开发工具,到它的官网 Visual Studio Code - Code Editing. Redefined 下载安装即可

要对 *.vue 做语法支持,还要安装一个 Volar 插件

devtools 插件网址:Installation | Vue Devtools

打开项目根目录下 vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 7070
}
})
为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理,同样是修改项目根目录下 vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
首先,通过 react 脚手架创建项目
npx create-react-app client --template typescript
client 是项目名
目前 react 版本是 18.x
cd client npm start
会自动打开浏览器,默认监听 3000 端口

在项目根目录下新建文件 .env.development,它可以定义开发环境下的环境变量
PORT=7070
重启项目,端口就变成了 7070
插件地址 New React Developer Tools – React Blog (reactjs.org)

VSCode
推荐安装 Prettier 代码格式化插件

官网地址:Ant Design - The world's second most popular React UI framework
镜像地址1:Ant Design - The world's second most popular React UI framework
镜像地址2:Ant Design - The world's second most popular React UI framework
npm install antd
目前版本是 4.x
引入样式,在 css 文件中加入
@import '~antd/dist/antd.css';
引入 antd 组件
import { Button } from "antd";
export default function A1() {
return
}
试试其它组件
import { Button, Modal } from "antd";
export default function A1() {
return 内容
}
发现确定和取消按钮是英文的,这是因为 antd 支持多种语言,而默认语言是英文
要想改为中文,建议修改最外层的组件 index.tsx
// ...
import { ConfigProvider } from 'antd'
import zhCN from 'antd/es/locale/zh_CN'
root.render(
)
1、)图标要独立安装依赖
npm install @ant-design/icons
2、)图标组件,用来将字符串图标转换为标签图标
import * as icons from '@ant-design/icons'
interface Module {
[p: string]: any
}
const all: Module = icons
export default function Icon({ name }: { name: string }) {
const Icon = all[name]
return
}

@CrossOrigin(“允许路径”)

1、)安装 typescript 编译器
npm install -g typescript
2、)编写 ts 代码
function hello(msg: string) {
console.log(msg)
}
hello('hello,world')
3、)执行 tsc 编译命令
tsc xxx.ts
4、)编译生成 js 代码,编译后进行了类型擦除
function hello(msg) {
console.log(msg);
}
hello('hello,world');
5、)再来一个例子,用 interface 定义用户类型
interface User {
name: string,
age: number
}
function test(u: User): void {
console.log(u.name)
console.log(u.age)
}
test({ name: 'zhangs', age: 18 })
6、)编译后
function test(u) {
console.log(u.name);
console.log(u.age);
}
test({ name: 'zhangs', age: 18 });
可见,typescript 属于编译时实施类型检查(静态类型)的技术
npm install mobx mobx-react-lite
mobx 目前版本是 6.x
mobx-react-lite 目前版本是 3.x

Actions 用来修改状态数据的方法
Observable state 状态数据,可观察
Derived values 派生值,也叫 Computed values 计算值,会根据状态数据的改变而改变,具有缓存功能
Reactions 状态数据发生变化后要执行的操作,如 react 函数组件被重新渲染
首先,定义一个在函数之外存储状态数据的 store,它与 useState 不同:
useState 里的状态数据是存储在每个组件节点上,不同组件之间没法共享
而 MobX 的 store 就是一个普通 js 对象,只要保证多个组件都访问此对象即可
import axios from 'axios'
import { makeAutoObservable } from 'mobx'
import { R, Student } from '../model/Student'
class StudentStore {
student: Student = { name: '' }
constructor() {
makeAutoObservable(this)
}
async fetch(id: number) {
const resp = await axios.get>(
`http://localhost:8080/api/students/${id}`
)
runInAction(() => {
this.student = resp.data.data
})
}
get print() {
const first = this.student.name.charAt(0)
if (this.student.sex === '男') {
return first.concat('大侠')
} else if (this.student.sex === '女') {
return first.concat('女侠')
} else {
return ''
}
}
}
export default new StudentStore()
其中 makeAutoObservable 会
将对象的属性 student 变成 Observable state,即状态数据
将对象的方法 fetch 变成 Action,即修改数据的方法
将 get 方法变成 Computed values
在异步操作里为状态属性赋值,需要放在 runInAction 里,否则会有警告错误
使用 store,所有使用 store 的组件,为了感知状态数据的变化,需要用 observer 包装,对应着图中 reactions
import Search from 'antd/lib/input/Search'
import { observer } from 'mobx-react-lite'
import studentStore from '../store/StudentStore'
import A71 from './A71'
import Test2 from './Test2'
const A7 = () => {
return (
studentStore.fetch(Number(v))}
style={{ width: 100 }}
/>
组件0 {studentStore.student.name}
)
}
export default observer(A7)
其它组件
import { observer } from 'mobx-react-lite' import studentStore from '../store/StudentStore' const A71 = () =>{ return{color:'red'}}>组件1 {studentStore.student.name}
} export default observer(A71)import { observer } from 'mobx-react-lite' import studentStore from '../store/StudentStore' const A72 = () =>{ return{color:'red'}}>组件1 {studentStore.student.name}
} export default observer(A72)
import { R, Student } from "../model/Student";
import { action, computed, makeAutoObservable, makeObservable, observable, runInAction } from 'mobx'
import axios from "axios";
class StudentStore {
// 属性 - 对应状态数据 observable state
@observable student: Student = { id: 0, name: '' }
// 方法 - 对应 action 方法
@action setName(name: string) {
this.student.name = name
}
@action async fetch(id: number) {
const resp = await axios.get>(`http://localhost:8080/api/students/${id}`)
runInAction(() => {
this.student = resp.data.data
})
}
// get 方法 - 对应 derived value
@computed get displayName() {
const first = this.student.name.charAt(0)
if (this.student.sex === '男') {
return first + '大侠'
} else if (this.student.sex === '女') {
return first + '女侠'
} else {
return ''
}
}
// 构造器
constructor() {
makeObservable(this)
}
}
export default new StudentStore()
需要在 tsconifg.json 中加入配置
{
"compilerOptions": {
// ...
"experimentalDecorators": true
}
}

首先来学习 axios,作用是发送请求、接收响应,从服务器获取真实数据
安装
npm install axios
定义组件
import axios from 'axios'
export default function P4({ id }: { id: number }) {
async function updateStudent() {
const resp = await axios.get(`http://localhost:8080/api/students/${id}`)
console.log(resp.data.data)
}
updateStudent()
return <>>
}
其中 /api/students/${id} 是提前准备好的后端服务 api,会延迟 2s 返回结果
使用组件
在控制台上打印
{
"id": 1,
"name": "宋远桥",
"sex": "男",
"age": 40
}
当属性变化时,会重新触发 P4 组件执行,例如将 id 从 1 修改为 2
执行流程
首次调用函数组件,返回的 jsx 代码会被渲染成【虚拟 dom 节点】(也称 Fiber 节点)
根据【虚拟 dom 节点】会生成【真实 dom 节点】,由浏览器显示出来
当函数组件的 props 或 state 发生变化时,才会重新调用函数组件,返回 jsx
jsx 与上次的【虚拟 dom 节点】对比
如果没变化,复用上次的节点
有变化,创建新的【虚拟 dom 节点】替换掉上次的节点
由于严格模式会触发两次渲染,为了避免干扰,请先注释掉 index.tsx 中的
npm install react-router-dom
目前版本是 6.x
新建文件 src/router/router.tsx
import { lazy } from 'react'
import { Navigate, RouteObject, useRoutes } from 'react-router-dom'
export function load(name: string) {
const Page = lazy(() => import(`../pages/${name}`))
return
}
const staticRoutes: RouteObject[] = [
{ path: '/login', element: load('A8Login') },
{
path: '/',
element: load('A8Main'),
children: [
{ path: 'student', element: load('A8MainStudent') },
{ path: 'teacher', element: load('A8MainTeacher') },
{ path: 'user', element: load('A8MainUser') }
],
},
{ path: '/404', element: load('A8Notfound') },
{ path: '/*', element: },
]
export default function Router() {
return useRoutes(staticRoutes)
}
index.tsx 修改为
import ReactDOM from 'react-dom/client';
import './index.css';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN'
import { BrowserRouter } from 'react-router-dom';
import Router from './router/router';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
)
A8Main 的代码
import { Layout } from "antd";
import { Link, Outlet } from "react-router-dom";
export default function A8Main () {
return
头部导航
侧边导航
学生管理
教师管理
用户管理
}
Navigate 的作用是重定向
load 方法的作用是懒加载组件,更重要的是根据字符串找到真正的组件,这是动态路由所需要的
children 来进行嵌套路由映射,嵌套路由在跳转后,并不是替换整个页面,而是用新页面替换父页面的 Outlet 部分
路由分成两部分:
静态路由,固定的部分,如主页、404、login 这几个页面
动态路由,变化的部分,经常是主页内的嵌套路由,比如 Student、Teacher 这些
动态路由应该是根据用户登录后,根据角色的不同,从后端服务获取,因为这些数据是变化的,所以用 mobx 来管理
import axios from 'axios'
import { makeAutoObservable, runInAction } from 'mobx'
import { Navigate, RouteObject } from 'react-router-dom'
import { MenuAndRoute, R, Route } from '../model/Student'
import { load } from '../router/MyRouter'
class RoutesStore {
dynamicRoutes: Route[]
async fetch(username: string) {
const resp = await axios.get>(
`http://localhost:8080/api/menu/${username}`
)
runInAction(() => {
this.dynamicRoutes = resp.data.data.routeList
localStorage.setItem('dynamicRoutes', JSON.stringify(this.dynamicRoutes))
})
}
constructor() {
makeAutoObservable(this)
const r = localStorage.getItem('dynamicRoutes')
this.dynamicRoutes = r ? JSON.parse(r) : []
}
reset() {
this.dynamicRoutes = []
localStorage.removeItem('dynamicRoutes')
}
get routes() {
const staticRoutes: RouteObject[] = [
{ path: '/login', element: load('A8Login') },
{ path: '/', element: load('A8Main') },
{ path: '/404', element: load('A8Notfound') },
{ path: '/*', element: },
]
const main = staticRoutes[1]
main.children = this.dynamicRoutes.map((r) => {
console.log(r.path, r.element)
return {
path: r.path,
element: load(r.element),
}
})
return staticRoutes
}
}
export default new RoutesStore()
其中用 localStorage 进行了数据的持久化,避免刷新后丢失数据
MyRouter 文件修改为
import { observer } from 'mobx-react-lite'
import { lazy } from 'react'
import { Navigate, RouteObject, useRoutes } from 'react-router-dom'
import RoutesStore from '../store/RoutesStore'
// 把字符串组件 => 组件标签
export function load(name: string) {
// A8Login
const Page = lazy(() => import(`../pages/${name}`))
return
}
// 路由对象
function MyRouter() {
const router = useRoutes(RoutesStore.routes)
return router
}
export default observer(MyRouter)
注意导入 router 对象时,用 observer 做了包装,这样能够在 store 发生变化时重建 router 对象