一、HTML、JS、CSS
1、环境准备
(1、)安装NVM
(2、)检查NPM
(3、)搭建简单服务
二、框架
1、VUE2
(1、)环境准备
1、)安装脚手架
npm install -g @vue/cli
-
-g 参数表示全局安装,这样在任意目录都可以使用 vue 脚本创建项目
2、)创建项目
vue ui
使用图形向导来创建 vue 项目,如下图,输入项目名
选择手动配置项目
添加 vue router 和 vuex
选择版本,创建项目
3、)安装 devtools
-
devtools 插件网址:Installation | Vue Devtools
4、)运行项目
进入项目目录,执行
npm run serve
5、)修改端口
前端服务器默认占用了 8080 端口,需要修改一下
-
文档地址:DevServer | webpack
-
打开 vue.config.js 添加
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ // ... devServer: { port: 7070 } })
6、)添加代理
为了避免前后端服务器联调时, 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 } } } })
2、VUE3
(1、)环境准备
1、)创建项目
采用 vite 作为前端项目的打包,构建工具
npm init vite@latest
按提示操作
cd 项目目录 npm install npm run dev
2、)编码 IDE
推荐采用微软的 VSCode 作为开发工具,到它的官网 Visual Studio Code - Code Editing. Redefined 下载安装即可
要对 *.vue 做语法支持,还要安装一个 Volar 插件
3、)安装 devtools
-
devtools 插件网址:Installation | Vue Devtools
4、)修改端口
打开项目根目录下 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 } })
5、)配置代理
为了避免前后端服务器联调时, 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 } } } })
3、REACT
(1、) 环境准备
1、)创建项目
首先,通过 react 脚手架创建项目
npx create-react-app client --template typescript
-
client 是项目名
-
目前 react 版本是 18.x
2、)运行项目
cd client npm start
-
会自动打开浏览器,默认监听 3000 端口
3、)修改端口
在项目根目录下新建文件 .env.development,它可以定义开发环境下的环境变量
PORT=7070
重启项目,端口就变成了 7070
4、)浏览器插件
插件地址 New React Developer Tools – React Blog (reactjs.org)
VSCode
推荐安装 Prettier 代码格式化插件
三、组件库
1、Element-UI
2、Ant Design
(1、)react 组件库
-
官网地址: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
(2、)安装
npm install antd
-
目前版本是 4.x
引入样式,在 css 文件中加入
@import '~antd/dist/antd.css';
引入 antd 组件
import { Button } from "antd"; export default function A1() { return <Button type='primary'>按钮</Button> }
(3、)国际化
试试其它组件
import { Button, Modal } from "antd"; export default function A1() { return <Modal open={true} title='对话框'>内容</Modal> }
发现确定和取消按钮是英文的,这是因为 antd 支持多种语言,而默认语言是英文
要想改为中文,建议修改最外层的组件 index.tsx
// ...
import { ConfigProvider } from 'antd'
import zhCN from 'antd/es/locale/zh_CN'
root.render(
<ConfigProvider locale={zhCN}>
<A1></A1>
</ConfigProvider>
)
(4、)图标
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 <Icon></Icon> }
四、扩展
1、跨域问题
(1、)后端解决
@CrossOrigin(“允许路径”)
(2、)前端解决(代理)
2、TypeScript
(1、)环境准备
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 属于编译时实施类型检查(静态类型)的技术
五、数据共享工具
1、VUEX
2、PINIA
3、MOBX
(1、)文档
(2、)安装
npm install mobx mobx-react-lite
-
mobx 目前版本是 6.x
-
mobx-react-lite 目前版本是 3.x
(3、)名词
-
Actions 用来修改状态数据的方法
-
Observable state 状态数据,可观察
-
Derived values 派生值,也叫 Computed values 计算值,会根据状态数据的改变而改变,具有缓存功能
-
Reactions 状态数据发生变化后要执行的操作,如 react 函数组件被重新渲染
(4、)使用
首先,定义一个在函数之外存储状态数据的 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<R<Student>>( `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 ( <div> <Search placeholder='input search text' onSearch={(v) => studentStore.fetch(Number(v))} style={{ width: 100 }} /> <h3>组件0 {studentStore.student.name}</h3> <A71></A71> <A72></A72> </div> ) } export default observer(A7)
其它组件
import { observer } from 'mobx-react-lite' import studentStore from '../store/StudentStore' const A71 = () =>{ return <h3 style={{color:'red'}}>组件1 {studentStore.student.name}</h3> } export default observer(A71)import { observer } from 'mobx-react-lite' import studentStore from '../store/StudentStore' const A72 = () =>{ return <h3 style={{color:'red'}}>组件1 {studentStore.student.name}</h3> } export default observer(A72)
(5、)注解方式
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<R<Student>>(`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 } }
六、请求
1、Fetch-API
2、Axios
首先来学习 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 返回结果
使用组件
<P4 id={1}></P4>
在控制台上打印
{ "id": 1, "name": "宋远桥", "sex": "男", "age": 40 }
当属性变化时,会重新触发 P4 组件执行,例如将 id 从 1 修改为 2
执行流程
-
首次调用函数组件,返回的 jsx 代码会被渲染成【虚拟 dom 节点】(也称 Fiber 节点)
-
根据【虚拟 dom 节点】会生成【真实 dom 节点】,由浏览器显示出来
-
-
当函数组件的 props 或 state 发生变化时,才会重新调用函数组件,返回 jsx
-
jsx 与上次的【虚拟 dom 节点】对比
-
如果没变化,复用上次的节点
-
有变化,创建新的【虚拟 dom 节点】替换掉上次的节点
-
-
-
由于严格模式会触发两次渲染,为了避免干扰,请先注释掉 index.tsx 中的
<React.StrictMode>
七、Router
1、Vue Router
2、React Router
(1、)安装
npm install react-router-dom
-
目前版本是 6.x
(2、)使用
新建文件 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 <Page></Page> } 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: <Navigate to={'/404'}></Navigate> }, ] 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( <ConfigProvider locale={zhCN}> <BrowserRouter> <Router></Router> </BrowserRouter> </ConfigProvider> )
A8Main 的代码
import { Layout } from "antd"; import { Link, Outlet } from "react-router-dom"; export default function A8Main () { return <Layout> <Layout.Header>头部导航</Layout.Header> <Layout> <Layout.Sider>侧边导航 <Link to='/student'>学生管理</Link> <Link to='/teacher'>教师管理</Link> <Link to='/user'>用户管理</Link> </Layout.Sider> <Layout.Content> <Outlet></Outlet> </Layout.Content> </Layout> </Layout> }
-
Navigate 的作用是重定向
-
load 方法的作用是懒加载组件,更重要的是根据字符串找到真正的组件,这是动态路由所需要的
-
children 来进行嵌套路由映射,嵌套路由在跳转后,并不是替换整个页面,而是用新页面替换父页面的 Outlet 部分
(3、)动态路由
路由分成两部分:
-
静态路由,固定的部分,如主页、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<R<MenuAndRoute>>( `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: <Navigate to={'/404'}></Navigate> }, ] 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 <Page></Page> } // 路由对象 function MyRouter() { const router = useRoutes(RoutesStore.routes) return router } export default observer(MyRouter)
注意导入 router 对象时,用 observer 做了包装,这样能够在 store 发生变化时重建 router 对象