columns={columns}
dataSource={data}
bordered
/>
)
}
六、高阶组件处理路由权限
在src/components/RouterAuth.jsx中
import React, { Component } from "react";
import RouterConfig from "../config/routerConfig";
import { Redirect, Route } from "react-router-dom";
export default class RouterAuth extends Component {
render() {
//(1)默认当前获取到访问路由地址
const { pathname } = this.props.location;
//(2)判断token是否存在
const token = localStorage.token;
// (3)你访问路由地址是否在我们映射文件中
const targetRouterConfig = RouterConfig.find((item) => {
// 去除所有空格之后,item.path和pathname相等则返回
return item.path.replace(/\s+/g, "") === pathname;
});
// (4)判断你访问地址在配置文件中,进入下一步
if (targetRouterConfig) {
if (targetRouterConfig.auth) {
if (token) {
return (
path={targetRouterConfig.path}
component={targetRouterConfig.component}
>
);
} else {
return ;
}
} else {
return (
path={targetRouterConfig.path}
component={targetRouterConfig.component}
>
);
}
} else {
return ;
}
}
}
在src/config/routerConfig.js中
import Home from "../pages/Home";
import Login from "../pages/Login";
import Register from "../pages/Register";
import NotFind from "../pages/NotFind";
// 用于存路由相关的数据,(以后该数据从后端来)
let routers = [
{ path: "/login", name: "Login", component: Login },
{ path: "/register", name: "Register", component: Register },
{ path: "/404", name: "NotFind", component: NotFind },
{ path: "/home", name: "Home", component: Home, auth: true },
{ path: "/home/main", name: "Main", component: Home, auth: true }, //这里必须是一级路由对应的组件
{ path: "/home/user", name: "User", component: Home, auth: true },
{ path: "/home/role", name: "Role", component: Home, auth: true },
{ path: "/home/shop", name: "Shop", component: Home, auth: true },
{ path: "/home/charts/salary", name: "Salary", component: Home, auth: true },
{ path: "/home/charts/sale", name: "Sale", component: Home, auth: true },
{ path: "/home/product/list", name: "List", component: Home, auth: true },
{
path: "/home/product/category",
name: "Category",
component: Home,
auth: true,
},
];
export default routers;
routes里面没有配置的路径,就代表用户没有权限访问。auth属性代表必须token登录后才能加载的组件
在App.jsx中引用 高阶组件
import React, { Component } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
// import Home from './pages/Home'
// import Login from './pages/Login'
// import Register from './pages/Register'
// import ForgetPassword from './pages/ForgetPassword'
// import Todolist from './pages/Todolist'
import RouterAuth from "./components/RouterAuth";
export default class App extends Component {
render() {
return (
}>
{/* 跟Vue的路由渲染出口时一个意思。采用高阶组件 */}
{/* */}
{/*
import("./pages/Register"))}>
*/}
);
}
}
路由渲染出口由RouterAuth来确定。
七、echarts图表统计
(1)下载依赖包
yarn add echarts-for-react
yarn add echarts
(2)在项目中引入echarts-for-react
import ReactEcharts from "echarts-for-react"
(3)在项目中引入组件并设置图表类型
import React from 'react'
import ReactEcharts from "echarts-for-react"
export default function Salary() {
const getOption = () => {
return {
xAxis: {},
yAxis: {},
series: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.4, 6.81],
[10.0, 6.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12.0, 6.26],
[12.0, 8.84],
[7.08, 5.82],
[5.02, 5.68]
],
type: 'scatter'
}
]
}
}
return (
option={getOption()}
style={{ height: "350px", width: "50%" }}
className="react_for_echarts"
>
)
}
通过发异步请求获取数据,然后渲染到echart中
import React,{useState,useEffect} from 'react'
import ReactEcharts from "echarts-for-react"
import {findAllCategroyApi} from "../../../apis/categoryApis"
export default function Salary() {
const [categoryData,setCategoryData] = useState([])
const [categoryXData,setCategoryXData] = useState([])
const [categoryYData,setCategoryYData] = useState([])
useEffect(()=>{
fetchCategoryData()
},[])
const fetchCategoryData = async() =>{
const res = await findAllCategroyApi({parentId:0})
setCategoryData(res.data)
}
useEffect(()=>{
console.log(categoryData);
let arrX = []
let arrY = []
categoryData.forEach(item=>{
arrX.push(item.value)
arrY.push(item.children.length)
})
setCategoryXData(arrX)
setCategoryYData(arrY)
},[categoryData])
const chartData = () =>{
return (
{
xAxis: {
type: 'category',
data: categoryXData
},
yAxis: {
type: 'value'
},
series: [
{
data: categoryYData,
type: 'bar'
}
]
}
)
}
return (
option={chartData()}
style={{width:"350px",height:"300px"}}
className={"react_for_echarts"}
>
)
}
八、搭建状态机(最后)
React10-路由环境搭建
React10-路由环境搭建
一、概念
React这个库官方并没有提供路由。推荐我们使用一个第三方的路由库。React-Router
使用React-Router这个库里面提供的组件来完成路由搭建。
访问路由库的文档:https://reactrouter.com/web/api/Redirect
React-Router路由模式:
我们采用的是编程式路由,就需要你们自己在组件中引入路由模块,自己搭建对应路由映射。
Vue这个框架配置式路由,你只需要在配置文件中,把映射关系设置,自动生成路由代码
const routes = [
{
path:"/",
redirect:"/home"
}
{
path:"/home",
component:Home
}
]
new VueRouter({
routes
})
二、搭建路由环境
(1) 下载依赖
yarn add react-router-dom@5
(2)需要在项目中引入路由组件
import {Route,Link,HashRouter} from "react-router-dom"
react-router-dom:提供了分成多的组件,利用这些组件我们可以搭建路由配置
我们需要在App.js里面搭建路由映射
import React, { Component } from 'react'
import { HashRouter, Switch, Route } from "react-router-dom"
import Login from './pages/login/Login'
import Register from './pages/register/Register'
import Home from './pages/home/Home'
/**
* 搭建一级路由
* 以后访问App.js 这个组件的时候,根据你们访问地址来决定在Appjs显示不同页面
*/
export default class App extends Component {
render() {
return (
)
}
}
三、解析路由配置
路由配置中的一些组件
路由器组件 :默认采用的window.location.hash来实现路由的映射。默认会在地址栏URL里添加#来代表hash映射 :默认采用HTML5 history api来实现的路由,地址栏里面不会有# 配置路由映射,必须先指定路由器。而且,整个代码有且仅有一个路由器。 路由匹配器 :类似于你们编程中switch语句,默认代表每次匹配一个路由就返回 :代表路由映射组件,你需要将url地址和代码中组件建立映射关系 导航组件 :Link组件相当于Vue中router-link :页可以进行页面导航跳转,可以动态添加class样式 :可以重定向到指定的路由
四、路由匹配细节
Switch下面Route在进行匹配的时候默认是模糊匹配
{/* */}
exact:可以设置Redirect组件,也可以设置Route组件上。精确匹配
Link组件和NavLink使用
import React, { Component } from 'react'
import {Link,NavLink} from "react-router-dom"
export default class Login extends Component {
render() {
return (
)
}
}
所有的路由组件,都必须在路由器的管辖范围
React11-路由跳转和参数传递
React11-路由跳转和参数传递
一、路由跳转
在React中我们使用ReactRouter这个第三方路由插件来完成路由搭建。
有两种方式:
采用超链接的方式来跳转 文本
文本
采用按钮的形式来进行跳转 使用按钮的形式来进行跳转,我们需要使用JS代码来进行路由跳转 this.props.history.replace("/home")
当你的页面是采用路由的形式来进行加载的。
路由页面props对象身上会默认新增三个对象
history:代表历史对象,里面包含页面跳转所有方法。这个对象就是H5中history
location:包含的路由的路径和路由传递的数据
match:也包含路由的路径和传递的数据
history对象跳转的时候特点:
history.push()跳转到指定的页面,会more你保存当前历史记录,可以在浏览器返回
history.replace()跳转过后不会记录历史,无法返回到之前页面
history.go()可以指定返回到指定的层级
二、非路由组件
我们在进行路由跳转的时候,路由映射组件默认props身上会出现路由跳转对象,路由参数传递对象。
但是如果一个非路由组件,你们直接import导入到页面中组件。props身上并没有history、location对象
ReactRouter给我们提供了一个高阶组件,可以解决这个问题
import React, { Component } from 'react'
import {withRouter} from "react-router-dom"
class SelectComp extends Component {
typeChange = (event) => {
const value = event.target.value
if(value=="a"){
console.log(this.props);
this.props.history.replace("/login")
请选择
注销
修改密码
)
}
}
//const newComp = withRouter(SelectComp)
export default withRouter(SelectComp)
高阶组件时React中提出的一个概念。还有一个名字HOC
高阶组件本质上就是一个高阶函数(JS概念)
高阶函数:英文叫Higher-order function。JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
数组里对数据进行遍历的方法很多都是高阶函数
const withRouter = (Component)=>{
//默认获取路由对象
const history = {}
const location = {}
const match = {}
//判断Component身上是否已经存在,不存在
return
}
const newComponent = withRouter ()
面试题:
请说一下React中HOC? HOC代表的React中高阶组件,高阶组件本质上就是一种高阶函数。我们引入一个高阶组件,接受一个组件,对当前这个组件进行动态功能叠加,最后返回新的组件。这个就是高阶组件。 在React我们用到官方高阶组件withRoter
三、路由参数传递
Link进行跳转
当我们使用Link标签来进行跳转的时候
没有注册?去注册
在另外一个组件中接受传递过来 值
this.props.location.query
按钮跳转的时候
gotoRegister = ()=>{x
this.props.history.push({
pathname:"/register",
query:{
id:2
}
})
}
在另外一个页面中获取
this.props.location.query
拼接参数到地址栏
没有注册?去注册
gotoRegister = ()=>{x
this.props.history.push("/register?name=xiaofeifei")
}
获取参数
this.props.location.search===> ?name=xiaowang
采用动态路由来传递参数
参数的路径上面,作为路由访问一部分
传递参数的时候
this.props.history.replace("/home/xiaowang")
获取参数
this.props.match.params ==> {name:"xiaowang"}
你可以在路由路径上面动态参数部分添加?代表这个动态部分可以不传递
React12-路由懒加载
React12-路由懒加载
一、路由懒加载
路由懒加载也称为路由延迟加载
当你在App.js中引入你页面,在启动项目加载的时候,引入页面就被被加载
增加启动时间,对内存进行消耗。
我们针对不常用的路由组件进行懒加载(延迟加载)
二、官方推荐的方案
以前我们还可以使用第三方的路由懒加载工具 Loadable
开发步骤:
需要在路由器外面增加一个React.Suspence
import React, { Component } from 'react'
import { HashRouter, BrowserRouter, Switch, Route, Redirect, Link } from "react-router-dom"
import Login from './pages/login/Login'
// import Register from './pages/register/Register'
import Home from './pages/home/Home'
import Loading from './components/Loading'
/**
* 搭建一级路由
* 以后访问App.js 这个组件的时候,根据你们访问地址来决定在Appjs显示不同页面
*/
export default class App extends Component {
render() {
return (
{/* 搭建路由映射关系 */}
}>
import("./pages/register/Register"))}>
)
}
}
fallback: 代表回调,当你页面加载很慢的情况,我们会使用fallback动画加载
React.lazy() 可以在你访问这个路由的时候,才动态加载你们的组件。
// import Register from './pages/register/Register'
二级路由懒加载
我们需要在二级路由映射关系外面再增加一个加载动画
二级路由懒加载。。。}>
import("../subs/product/Product"))}>
二级路由懒加载,用的里面的动画
React13-hook编程
React13-hook编程
一.入门概念
函数组件目前在企业开发中使用非常多,但是函数组件没有提供完整生命周期\内部状态等等
我们在React16.8过后推出一些api函数,这些api函数称为hook.
在hook没有出现之前我们开发一直都是类组件编程.函数组件只负责一些基础页面布局
二.常用hook函数
useState函数
用于定义函数组件内部状态.
你引入这个hook后将数据定义到这个函数中,页面能够实现响应式数据变化
import React, { useState } from 'react'
export default function Hook() {
// 只要你定义组件内部的数据,都需要解构一个变量,和一个函数
const [username,setUsername] = useState("xiaowang")
const [password,setPassword] = useState("password")
const [user,setUser] = useState({id:1,name:"xiaowang"})
const changeUsername = ()=>{
setUsername("xiaofeifei")
setPassword("123456")
}
const changeUser = ()=>{
setUser({...user,name:"xiaoliu"})
}
const changePwd = ()=>{
setPassword(()=>{
// 执行一段代码,返回修改数据
return "0000"
})
}
return (
{username}
{password}
{user.id}
{user.name}
修改
修改user
修改password
)
}
useState是属于React官方提供的hook函数.我们引入的时候
import {useState} from "react"
我们在定义useState参数的时候
const [username,setUsername] = useState("xiaowang")
修改username的函数,命名必须按照set+变量名字—小驼峰的方式
修改我们useState的数据,你可以调用修改函数来修改,可以传递一个回调函数
setPassword(()=>{
// 执行一段代码,返回修改数据
return "0000"
})
修改数据的函数是同步函数异步?异步
目前暂时无法获取修改后的值,需要借助于其他的hook来解决
可以用useEffect来监控被修改的数据,在useEffect中来获取这个值。
useMemo\useCallback函数
这个函数是我们hook编程里面提供的一个计算属性的api
你可以在函数组件中使用他来进行计算属性
import React, { useState,useMemo,useCallback } from 'react'
export default function Hook() {
// 只要你定义组件内部的数据,都需要解构一个变量,和一个函数
const [username,setUsername] = useState("xiaowang")
const [password,setPassword] = useState("password")
const [user,setUser] = useState({id:1,name:"xiaowang"})
const fullName = useMemo(()=>{
return username + password
},[username,password])
const newUser = useMemo(()=>{
return user.name + "-----"
},[user])
const newUser2 = useCallback(()=>{
return user.name + "-----"
},[user])
const changeUsername = ()=>{
setUsername("xiaofeifei")
setPassword("123456")
}
const changeUser = ()=>{
setUser({...user,name:"xiaoliu"})
}
const changePwd = ()=>{
setPassword(()=>{
// 执行一段代码,返回修改数据
return "0000"
})
}
return (
{fullName}
{newUser}
{newUser2()}
修改user
修改password
)
}
在进行计算属性的时候,[监控的属性],一旦属性值发生变化,页面就会立即更新
useMemo和useCallback区别:
useMemo返回的计算属性的结果,是一个变量
useCallback返回的函数,我们需要调用一下这个函数,当你的变量没有发生变化的时候,返回的永远都是缓存的函数
useEffect函数
在函数组件中默认没有生命周期函数,我们可以执行useEffect完成副作用.
useEffect来模拟生命周期.
主要你的代码中使用useEffect,组件产生副作用.
模拟componentDidMount过程
import React, { useEffect,useState } from 'react'
export default function Hook2() {
// 这个代码就模拟了componentDidMount
const [username,setUsername] = useState("xiaowang")
useEffect(()=>{
console.log("componentDidMount");
},[])
const changeUsername = ()=>{
setUsername("setUsername")
}
return (
Hook2
{username}
修改username
)
}
模拟componentDidUpdate
useEffect(()=>{
console.log("------");
})
第二个参数不传递的时候,页面上任何的数据发生变化,我们都需要执行回调函数,传参时,就监听该参数。
useEffect(()=>{
console.log("componentDidUpdate");
console.log(username);
},[username])
useEffect(()=>{
console.log("componentDidUpdate");
console.log(username);
},[username,password])
模拟componentWillUnmount销毁
import React, { useEffect } from 'react'
export default function MyDemo({ username, callback }) {
useEffect(() => {
console.log("mydemo---componentDidMount");
return ()=>{
console.log("mydemo---componentWillUnmount");
}
},[])
return (
)
}
useHistory函数
可以用于页面跳转(非官方的方法)
//先引入useHistory
import {useHistory} from "react-router-dom"
export default function Hook2(props) {
// 定义history
const history = useHistory();
// 点击跳转添加页面
const toAddProduct = () => {
//方法一
// props.history.push("/home/product/addProduct");
// 方法二
history.push("/home/product/addProduct")
};
return (
)
}
模拟componentDidUpdate
React14-hook扩展
React14-hook扩展
一、第三方hook
路由提供的常用hook函数
import React,{useRef} from 'react'
import {useHistory} from "react-router-dom"
export default function Sale() {
const history = useHistory()
const getValue = () => {
history.push("/home/shop")
}
return (
)
}
useHistory我们可以进行页面跳转。属于路由组件提供的hook,称为第三方hook
接下来是useLocation这个hook
import React,{useEffect} from 'react'
import {useLocation} from "react-router-dom"
export default function Shop() {
const location = useLocation()
useEffect(()=>{
console.log(location.query);
},[location])
return (
)
}
useParams:这个hook专门用于获取到我们页面传递的参数。通过动态路由来进行传递
history.push("/home/shop/xiaowang")
const params = useParams() ===>{name:"xiaowang"}
学习状态机的时候,也会接收到第三方的hook
二、自定义hook
在我们函数组件中我们需要配合hook来进行使用,而且用了hook过后发现非常方便。
按照每个组件业务来决定我需要引入哪些hook
我们可以将自己代码中某些业务提取出去封装了常见的hook函数,以后你们可以在任何一个组件中使用自己封装的hook,将这个过程称为自定义hook
自定义组件:将页面公共的部分提取出去
自定hook:将业务的逻辑部分提取出去
自定义hook要求:
自定义hook的作用是提取公共逻辑,所以一般不会返回JSX对象,根据需要返回对应的结果 自定义hook要求名字必须是use开头的函数,采用小驼峰命名
多个组件都想要从本地存储获取指定的某个数据。需要将这个业务流程封装到hook里面。但凡遇到这个业务我们就不用重复的写本都存储代码
在src目录下面创建hooks文件夹/myhook.js文件
import React from "react"
//获取本地存储的数据
export const useGetStorage = () => {
const routes = JSON.parse(localStorage.getItem("routes") || "[]")
return routes.filter(item=>item.auth)
}
//将数据保存到本地存储
export const useSetStorage = (array) => {
localStorage.setItem("routes",JSON.stringify(array))
}
将本地存储的操作封装为hook,以后再任何一个页面中都可以使用这个hook
页面中使用这个hook函数
import React,{useRef} from 'react'
import {useHistory} from "react-router-dom"
import {useGetStorage,useSetStorage} from "../../../hooks/MyHook"
export default function Sale() {
useSetStorage([
{id:1,path:"/home/main",auth:true},
{id:1,path:"/home/user",auth:true},
{id:1,path:"/home/shop",auth:false}
])
const array = useGetStorage()
const getData = ()=>{
console.log(array);
}
return (
)
}
这个hook函数必须再组件最前面调用,否则React检测到hook不规范会抛出警告
自定义hook参数传递,和返回函数的封装
//将数据保存到本地存储
export const useSetStorage = () => {
// localStorage.setItem("routes",JSON.stringify(array))
return (params)=>{
console.log(params);
}
}
面试题题:
你知道如何自定义hook吗? 你再项目中使用自定义hook来做了什么工作
React15-Redux的介绍
React15-Redux的介绍
一、概念
redux是一个专门用于做状态管理的第三方js库。并不是插件也不是组件。
可以集中管理我们项目中的状态(数据)
在我么React中组件通信我们可以使用父子通信、通信的技术来解决。如果遇到比较复杂的跨组件通信,可以引入状态机来进行数据管理
redux并不是只能应用react中。可以用到其他框架,包括vue也可以
组件通信流程:
状态机使用了过后,肯定增加代码的繁琐。
但是在复杂的业务场景下面,有了状态机能更加方便的进行数据管理。
当你不知道要不要用状态机的时候,就不要用。
但是数据比较多,通信比较复杂的时候,必须引入状态机来进行数据集中管理
二、redux的工作流程
官方提供了一张图片,可以查看redux的完整流程
从仓库中获取数据,研究如何在页面中获取数据 修改仓库中的数据,一旦变化页面就会更新
redux和react没有任何关系,我们可以在react引入redux来进行状态管理
单独研究redux工作流程 将redux和react结合起来使用
三、搭建环境
(1)在开发过程中要单独使用redux需要下载对应依赖
yarn add redux
(2)在src目录下面创建一个文件夹redux/store.js文件
/**
* 这redux仓库代码
*/
import {legacy_createStore as createStore} from "redux"
// 创建一个仓库。里面存放我们的数据
const store = createStore(function(state={count:10}){
return state
})
// 如何获取仓库数据
// 如何修改仓库数据
创建仓库createStore,在老版本里面可以直接使用,但是在新版本中createStore已经被废弃了。
通过legacy_createStore创建仓库。
(3)打印store这个仓库我们可以获取到两个非常重要api
getState:store仓库提供给我们进行仓库数据获取的api
dispatch:用于修改仓库数据的api
subscribe:一个监听函数,只要数据发生变化,可以监控到
(4)需要加载store.js这个文件
在index.js文件中引入store.js,项目启动加载仓库
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// import 'antd/dist/antd.less';
import "./redux/store"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
(5)获取仓库数据
/**
* 这redux仓库代码
*/
import { legacy_createStore as createStore } from "redux"
// 创建一个仓库。里面存放我们的数据
const store = createStore(function(state={count:10}){
return state
})
// (一)打印store
console.log(store);
// (二)如何获取仓库数据
console.log(store.getState());
(6) 修改仓库数据
/**
* 这redux仓库代码
*/
import { legacy_createStore as createStore } from "redux"
// 创建一个仓库。里面存放我们的数据
const store = createStore(reducer)
// (一)打印store
console.log(store);
// (二)如何获取仓库数据
console.log(store.getState());
// (三)如何修改仓库数据
// 修改仓库数据
/**
* 流程:调用dispatch传递一个action通知对象给reducer,reducer进行修改
* reducer会将修改好的数据,返回给store
*/
const action = {
// type属性是必须写的。值由用户自己来定
type: "INCREMENT",
// payload名字是我们自己取的。值就是你要修改的内容
payload: 10
}
// 创建一个reducer
// reducer本身是一个函数,而且是一个纯函数
// 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数
function reducer(state = { count: 1 },action) {
switch(action.type){
// 这个case进行累加的过程
case "INCREMENT":
state.count+=action.payload
return state
default:
return state
}
}
// 通过dispatch进行仓库数据修改
store.dispatch(action)
store.dispatch(action)
// 获取到修改后结果
console.log(store.getState());
React16-Redux三大核心对象
React16-Redux三大核心对象
一、store
store是我们redux中非常重要的一个模块,代表仓库数据。
store仓库要创建新版本里面我们需要用到import { legacy_createStore as createStore } from "redux"
createStore 在创建过程中我们需要传递一个纯函数(reducer)
获取到一个仓库对象store
const store = createStore(reducer)
store.getState()获取仓库数据
store.dispatch()修改仓库数据
二、action
从开发的角度来说action就是一个普通对象。
action在redux中代表通知对象,这个通知对象里必须要由一个属性type
const action = {
type:"名字自己定",
payload:"传递参数"
}
payload代表参数传递,名字可以自定义。约定就用payload代表处参数名字
以后只要页面数据要发送到redux修改都必须要用action来进行传递
三、reducer
仓库在创建的时候,第一次需要初始化,reducer就可以完成仓库初始化的任务
当我们有数据需要修改的时候,reducer也可以进行数据更新。得到结果返回给store仓库
function reducer(state = { count: 1 },action) {
switch(action.type){
// 这个case进行累加的过程
case "INCREMENT":
state.count+=action.payload
return state
default:
return state
}
}
你们在进行仓库修改的时候,会有很多种修改方案。我们匹配你的方案来执行代码
修改仓库数据的唯一方案就是reducer的case来进行处理
特点:
reducer是一个纯函数,需要在创建仓库放在方法里面 reducer里会执行很多数据修改,每次修改完成后一定要返回一个newstate给仓库
修改后的reducer代码
function reducer(state = { count: 1 },action) {
const {count} = state
switch(action.type){
// 这个case进行累加的过程
case "INCREMENT":
count+=action.payload
return {
...state,count
}
default:
return state
}
}
React17-Redux优化
React17-Redux优化
一、文件拆分
三大核心对象,我们需要在redux文件夹中进行拆分
在redux中创建reducers文件夹,设计一个CountReducer
// 创建一个reducer
// reducer本身是一个函数,而且是一个纯函数
// 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数
export default function reducer(state = { count: 1 }, action) {
let { count } = state
switch (action.type) {
// 这个case进行累加的过程
case "INCREMENT":
count += action.payload
return {
...state, count
}
case "DECREMENT":
count -= action.payload
return {
...state,count
}
default:
return state
}
}
创建actions文件夹,里面设计CountAction.js文件
export const action = {
// type属性是必须写的。值由用户自己来定
type: "INCREMENT",
// payload名字是我们自己取的。值就是你要修改的内容
payload: 10
}
export const action2 = {
type: "DECREMENT",
payload: 5
}
在仓库中我们需要用到内容引入
/**
* 这redux仓库代码
*/
import CountReducer from "./reducers/CountReducer"
import { legacy_createStore as createStore } from "redux"
import {action} from "./actions/CountAction"
// 创建一个仓库。里面存放我们的数据
const store = createStore(CountReducer)
store.dispatch(action)
console.log(store.getState());
二、reducer拆分合并
我们在项目开发过程中reducer会有很多业务,不可能将所有的业务都写到一个reducer中。
将不同业务放在不同的reducer中去
在reducers文件夹里面创建不同的reducer文件
比如:CountRedcuer、UserRedcuer
// 创建一个reducer
// reducer本身是一个函数,而且是一个纯函数
// 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数
export default function reducer(state = { count: 1}, action) {
let { count } = state
switch (action.type) {
// 这个case进行累加的过程
case "INCREMENT":
count += action.payload
return {
...state, count
}
case "DECREMENT":
count -= action.payload
return {
...state,count
}
default:
return state
}
}
userRedcuer
// 创建一个reducer
// reducer本身是一个函数,而且是一个纯函数
// 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数
export default function reducer(state = { user:{id:1,name:"xiaowang"}}, action) {
const {user} = state
switch (action.type) {
// 这个case进行累加的过程
case "UPDATENAME":
user.name = action.payload
return {
...state, user
}
default:
return state
}
}
createStore这个函数在创建仓库的时候,默认只能接受一个redcuer
将拆分完成后的reducer进行合并
在redcuers文件夹下面index.js
import {combineReducers} from "redux"
import UserReducer from "./UserReducer"
import CountReducer from "./CountReducer"
/**
* 将多个reducer合并在一起
*/
export default combineReducers({
UserRD:UserReducer,CountRD:CountReducer
})
UserRD就是命名空间名字.以后再页面上要获取这个参数的时候,我们需要指定这个名字才能获取值
在store.js中引入合并后reducer
/**
* 这redux仓库代码
*/
import { legacy_createStore as createStore } from "redux"
import {changeUsername,action,action2} from "./actions"
import bigReducer from "./reducers"
// 创建一个仓库。里面存放我们的数据
const store = createStore(bigReducer)
// store.dispatch(action)
console.log(store.getState().UserRD);
store.dispatch(changeUsername)
console.log(store.getState().UserRD);
三、action进行拆分合并
我们actions文件夹下面会定义很多的action的js文件,为了管理方便,我们可以将所有的action整合在一起
actions文件夹下面创建index.js将所有action合并在一起
export {action,action2} from "./CountAction"
export {changeUsername} from "./UserActions"
以后页面上要使用任何一个action
import {action,changeUsername} from "./actions"
四、ActionCreator
我们在action中无法接受外部传递进来的参数.
暴露出去的内容需要换成actionCreator函数
/**
* actionCreator
* @param {*} num
* @returns
*/
export const incrementAC = (num)=>{
return {
// type属性是必须写的。值由用户自己来定
type: "INCREMENT",
// payload名字是我们自己取的。值就是你要修改的内容
payload: num
}
}
/**
* action
*/
export const decrementAC = (num)=>{
return {
type: "DECREMENT",
payload: num
}
}
React18-函数组件使用redux
React18-函数组件使用redux
授课进程
一、在组件中使用react-redux
(1)载依赖
yarn add react-redux
(2)创建主仓库(在src/store/store文件中),将store仓库暴露出去
import {createStore} from "redux"
import reducer from "./reducers/countReducer"
const store = createStore(reducer);
// React组件需要使用store仓库
export default store
(3)在index.js入口文件里面引入store
相当于Vuex暴露出去一个仓库,必须在main.js中注入到Vue对象
import store from "./store"
new Vue({
store
})
在React中也是一样的设计
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 导入仓库加载一次
import store from "./store/store"
// Provider称为容器组件,直接和redux通信 UI无法直接和redux通信
import { Provider } from "react-redux"
ReactDOM.render(
,
document.getElementById('root')
);
提供一个Provider这个组件,将store仓库注入到Provide组件身上,目的是为了让所有组件都能够获取到store仓库的数据。
(4)来到组件中,引入hook函数
import React from 'react'
import {useDispatch,useSelector} from "react-redux"
import {incrementAC} from "../../../redux/actions"
export default function ReduxComp() {
const dispatch = useDispatch()
// 获取仓库数据
const {user} = useSelector(state=>{
return state.UserRD
})
const {count} = useSelector(state=>{
return state.CountRD
})
const add = ()=>{
dispatch(incrementAC(20))
}
const reduce = ()=>{
}
return (
ReduxComp
{user.id}
count:{count}
-
+
)
}
useDispatch:我们可以使用这个hook来获取dispatch对象
useSelector:可以获取仓库的数据。你需要进行解构
二、类组件要获取仓库数据
函数组件中我们可以使用第三方的hook
import {useDispatch,useSelector} from "react-redux"
但是类组件要获取redux仓库的数据,我们需要使用它connect高阶组件来实现
import React, { Component } from 'react'
// connect是一个高阶
import { connect } from "react-redux"
class ReduxClassComp extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
return (
)
}
}
const mapStateToProps = (state) => {
console.log("state",state);
return {
mycount: state.CountRD.count
}
}
export default connect(mapStateToProps)(ReduxClassComp)
connect采用函数柯里化的形式进行参数传递。
第一个参数传递的回调函数,这个回调函数我们可以获取到仓库的数据
第二个参数传递当前组件,进行props内容赋值,当前组件props上面默认新增dispatch属性
React19-redux的中间件
React19-redux的中间件
一、redux目前问题
目前redux在使用过程中我们发现的一个问题:
redux默认无法处理异步请求的。
我们可以在页面中发送请求,将数据更新到redux中,实现其他页面的数据共享。
这种方式需要我们页面自己来维护异步请求。
二、中间件概念
redux这个js库专门用于状态管理的一个库。
目前提供的核心业务可以满足我们对状态的管理。
功能还不止于此,还采用中间件的设计方式来对redux功能进行加强/
目前在市面上,我们使用redux的时候,除了使用redux提供核心流程来处理业务,你还可以给redux新增第三方中间件来加强他的功能。
当我们redux没有使用中间件的时候流程:
在redux状态机中新增中间件流程如下:
mid1:我就发送异步请求。得到结果后,交给dispatch进行派发给reducer
在redux中常见的中间件有下面几种:
redux-logger:记录状态机数据修改的日志中间件 redux-thunk:让我们在dispatch派发请求到reducer的时候,执行异步请求 redux-saga:可以在redux-saga中间件里面执行异步请求,得到结果后结果dispatch派发reducer (迭代器和生成器)
学习redux-thunk,这个也是目前在企业开发过程中经常使用的一个中间件
三、使用中间件
redux-logger
用于记录日志的。只要你的redux数据发生变化,我们可以使用这个中间件来获取数据
(1)下载中间件依赖
yarn add redux-logger --dev
(2)在store仓库中配置中间件
/**
* 这redux仓库代码
*/
import { legacy_createStore as createStore,applyMiddleware } from "redux"
import {changeUsername,incrementAC,decrementAC} from "./actions"
import bigReducer from "./reducers"
import logger from "redux-logger"
// 创建一个仓库。里面存放我们的数据
// 创建仓库的时候,可以指定加载哪些中间件
const store = createStore(bigReducer,applyMiddleware(logger))
export default store
使用这个中间件过后,我们默认可以在控制台上面打印你们每次redux数据操作的日志
下载redux-logger插件依赖的时候,—dev
这个包只能在开发过程中,打包后这个包不需要。
redux-thunk
可以在redux中处理异步请求
以后你可以将页面异步请求代码交给redux-thunk来进行处理,更新到store仓库中
类似于vue中的actions板块
(1)下载对应依赖
yarn add redux-thunk
(2)在我们store.js中引入我们中间件,进行加载
/**
* 这redux仓库代码
*/
import { legacy_createStore as createStore,applyMiddleware } from "redux"
import {changeUsername,incrementAC,decrementAC} from "./actions"
import bigReducer from "./reducers"
import logger from "redux-logger"
import thunk from "redux-thunk"
// 创建一个仓库。里面存放我们的数据
// 创建仓库的时候,可以指定加载哪些中间件
// 按照配置的顺序来加载,先进入第一个,执行完毕后再进入第二个
const store = createStore(bigReducer,applyMiddleware(logger,thunk))
export default store
多个中间件执行顺序是按照我们配置的顺序来进行的。
(3)页面上完成业务开发
没有redux-thunk中间件的,我们disptach只能接受一个action对象作为传递参数。
有了redux-thunk中间件,页面dispatch可以接受一个函数作为我们参数进行派发。
在函数中间进行异步请求
import React from 'react'
import { useDispatch, useSelector } from "react-redux"
import {initRoleAC,asyncInitRoleAC} from "../../../redux/actions"
export default function ReduxRolesComp() {
const dispatch = useDispatch()
const {roles} = useSelector(state =>{
return state.RoleRD
})
/**
* redux-thunk这个中间件运行流程
* 页面派发disptach,需要一个action
*/
const fetchRole = ()=>{
dispatch(asyncInitRoleAC())
}
return (
ReduxRolesComp
{roles.length}
获取所有的role
)
}
asyncInitRoleAC代码如下:
import {findAllRoles} from "../../apis/roleApi"
// 这个action用于通知reducer进行数据更新
export const initRoleAC =(array)=>{
return {
type:"INITROLE",
payload:array
}
}
// 这个actionCreator可以支持异步请求
export const asyncInitRoleAC = ()=>{
return async (dispatch)=>{
const res = await findAllRoles()
dispatch(initRoleAC(res.data))
}
}
有了redux-thunk这个中间,针对dispatch对象进行了增强,可以让dispatch接受一个函数来执行异步请求
传递进去dispatch,在内部调用,再将结果传递给reducer
类似于Vuex中提供了actions模块,异步请求都放在这个模块中进入。统一管理所有异步请求
React20-redux调试工具安装
React20-redux调试工具安装
针对React组件开发,我们可以安装一个chrome工具来调试组件的数据
针对Redux开发,我们也可以安装一个Redux-dev-tooks工具来进行调试
开发步骤: (1)需要在浏览器中将dev-tools插件安装完成
将插件拖入chrome的扩展程序里面
安装完毕后,并不能redux插件中查询到store数据
(2)在代码中下载插件,用于显示数据到浏览器dev-tools工具
yarn add redux-devtools-extension
需要在我们的代码中引入这个插件,并加载这个插件
/**
* 这redux仓库代码
*/
import { legacy_createStore as createStore,applyMiddleware } from "redux"
import bigReducer from "./reducers"
import logger from "redux-logger"
import thunk from "redux-thunk"
import { composeWithDevTools } from 'redux-devtools-extension';
// 创建一个仓库。里面存放我们的数据
// 创建仓库的时候,可以指定加载哪些中间件
// 按照配置的顺序来加载,先进入第一个,执行完毕后再进入第二个
const store = createStore(bigReducer,composeWithDevTools(applyMiddleware(logger,thunk)))
export default store
需要在创建仓库的时候,将插件放到第二个参数中。中间件也应该放在插件中
所有的数据操作,我们都可以浏览器中进行检测。
React21-性能优化
React21-性能优化
一、函数组件的更新
在父子组件中,React默认父组件更新过后,子组件也会跟着更新
某些情况父组件更新,子组件没有数据更新,子组件还是会刷新一次,造成我们性能浪费
这种情况我们可以考虑在子组件使用React.memo来进行组件的包装
import React, { useEffect } from 'react'
import Children2 from './Children2';
function Children(props) {
// 组件第一次加载的会运行,当你组件加载完毕,不会再执行
useEffect(() => {
console.log(123);
}, [])
useEffect(()=>{
console.log(345);
})
return (
Children
{props.username}
props.changeUsername("xiaofeifei")}>修改父组件username
)
}
export default React.memo(Children)
只要子组件props没有接受到新的值我们就不会更新子组件
如果props的值发生变化,子组件肯定更新一次
但是如果父组件传递了一个函数给子组件,不管父组件那边更新的是哪个数据,子组件依然会跟着跟新
原因:父组件更新一次,函数会被重新定义,函数重新创建一次地址会发生变化,子组件检测数据变化。更新一次。
解决方案:
import React, { useState,useCallback } from 'react'
import Children from './Children'
export default function ParentComp() {
const [count,setCount] = useState(10)
const [username,setUsername] = useState("xiaowang")
const changeCount = ()=>{
setCount(30)
}
// const changeUsername = (val)=>{
// setUsername(val)
// }
// useCallback返回一个函数
// 如果检测到你监控的数据没有发生变化,不会产生一个新的函数。返回之前缓存的函数
const changeUsername = useCallback((val)=>{
setUsername(val)
},[username])
return (
)
}
我们父组件可以使用useCallback来定义函数,将useCallback定义的函数传递给子组件。
useCallback默认有缓存功能,当我监控的数据没有发生变化的时候,这个函数不会重新创建一次。
二、类组件的更新
我们类组件中可以使用shouldComponentUpdate进行数据判断
当props数据没有发生变化的时候,默认返回false,当发生变化的时候,默认返回ture
当然官方已经给我提供了一个PureComponent组件,这个组件可以满足我们页面在没有更新props的情况下,默认不会更新页面。
注意:PureComponent内部已经实现shouldComponentUpdate逻辑。但是只能浅对比。
如果传递过来的是一个对象,检测对象地址是否发生变化,地址没有变化,检测数据没有变化。地址里面的内容检测不到。
--------------------------------------------------------------------------上下合并符号--------------------------------------------------------------------------
面试题08-React性能优化方案
React中的问题
有时候组件的render会在没有必要的情况下调用就会带来性能问题。
当你state或者props没有更新操作,父组件也没有重新渲染的情况下,如果render一次,得到跟虚拟dom一样的结果。
shouldComponentUpdate
使用shouldComponentUpdate和PureComponent达到的效果是一样的。
shouldComponentUpdate(nextProps,nextState){
if(nextProps === this.props && nextState===this.state){
return false
}
return true
}
如果你的组件中不需要对比state或者props就需要更新,你将if里面的判断去掉就行。
传参优化
父子组件传递参数的时候,我习惯结构过后来使用
class Student extends Component{
render(){
const {msg} = this.props
return(
{msg}
)
}
}
父组件的定义
class Parent extends Component{
state = {
user:{
id:1,
username:"xiaowang",
password:'123'
}
}
render(){
return(
)
}
}
避免在父组件中将属性展开过后传递给子组件。这样会带来更多的性能消耗。
key定义
对于数组数据,在进行遍历渲染的时候要求对每个遍历后的节点都需要增加一个key,并且这个key必须是唯一的,而且不要用index来作为key。
虚拟DOM上面增加的标准,diff算法来进行虚拟dom对比的时候,使用这个key作为参考条件。
分片打包
路由对应组件的懒加载
loadable插件可以实现组件的懒加载,第三方 React.lazy()也可以实现组件的懒加载,官方
这两种发方式,自己能在笔记上写出来。
Fragment
当你的子组件需要一个父组件的时候,你可以使用React.Fragment来定义一个父标签。
或者使用空标签来定义父标签。进来减少额外标签的使用达到性能优化。
render(){
<>
h1
h1 >
}
render(){
h1
h1
}
Vue 的template 小程序的 block
组件卸载和加载
进来减少组件的创建和销毁,尤其是针对经常使用的组件。调整CSS样式来对组件进行隐藏,hidden属性。控制组件的css样式,display:none
计算缓存
使用函数组件的useMemo、useCallback这两个hook,也能达到计算缓存的作用。
TypeScript01-环境搭建
TypeScript01-环境搭建
TypeScript目前企业中也在逐渐使用。一般配合我们的框架来进行使用
React+TS开发、Vue3+TS开发
一、基本概念
TypeScript编程语言是微软推出的一款开源的开发语言
TypeScript是JavaScript的超集。TypeScript也是基于JavaScript来进行开发的,具备JavaScript所有特性,支持完整的ES6\ES5等等语法。
学习TypeScript就相当于在学习JavaScript,更多要学习的TypeScript中编程规范
比如TypeScript中我们可以进行数据类型约束,一旦有了约束后我们程序设计会更加规范
二、JavaScript特点
弱类型语言:
var i = 10
var k = "xiaowang"
在JS这门语言中定义变量无需,申明数据类型。数据类型是根据的值来进行确定
typeof i ===>number
typeof j ===>string
如果我对i这个变量进行了修改
i = "xiaofeiei"
typeof i ===>string
强类型语言:比如Java、C++等等
int i = 10 --->int代表数据类型,整数类型
一旦强类型语言在定义变量的时候,数据类型确定了,以后只能赋值相同数据类型的结果
i = "xiaowang" //程序会报错
强类型语法和弱类型语言区分:
类类型语法定义变量的时候,明确这个变量数据类型,弱类型语言一般不会明确他的数据类型
三、JavaScript缺点
JavaScript是弱类型语言,导致你们很多代码在运行过程中才会发生报错。
无法在写代码的时候,就检测到错误
const user = {
username:"xiaowang",
age:"12"
}
在代码中要使用user中的数据
user.username
user.classes.id //编写代码编译器不会抛出任何错误,代码必须在浏览器运行抛出错误
函数要接受外部参数
function show(array){
array.forEach(item=>{
item.state = true
})
}
show([])
show("xiaowang") //调用别人的函数,传递参数传递错误,代码只能运行的时候,抛出异常
如果我们能够在编写代码的时候,就检测到很多的错误。可以让代码设计更加严谨、代码健壮性会好
四、TypeScript能解决的问题
TS是JS的超集,完全遵循我们ES5、ES6的标准。你写TS代码就是在写JS代码。提供了更多的约束。更多规范要求我们写代码必须按照规范来写,否定编译不通过。
TypeScript代码——-编译——JavaScript
这个编译过程检测你的语法是否正确,如果语法有问题,规范不正确,那就会抛出异常。
减少代码在运行时报错
五、搭建环境
(1)保证电脑安装nodejs
(2)全局安装TypeScript
npm install typescript -g
tsc -v
(3)创建一个项目,在项目中编写ts代码
// 默认可以在TS代码中编写JS代码,能够正确识别
// 在TS规范中,我们定义变量需要明确这个变量的数据类型
// 以后在框架写的TS代码,最后打包的TS代码编译为JS代码,运行也是JS代码
var username:string = "xiaowang"
let age:number = 10
console.log(username);
console.log(age);
age = 20
(4)你可以将指定的ts文件编译为js文件
tsc index.ts --->默认同级目录下面创建index.js文件
六.配置当前项目自动编译
我们tsc可以手动将ts文件编译为js文件,但是比较麻烦.
每次写了ts后,手动tsc编译,想要自动编译.代码写完,自己生成js文件
(1)在项目中初始化一个ts配置文件
tsc --init
执行完这个命令后,我们项目中会出现一个tsconfig文件,这个文件里面存放的就是ts配置信息
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
在配置文件中将以下内容配置完成
"outDir": "./js",
你将ts编译为js代码的时候,默认将js代码放在项目js目录下面
在vscode工具中进行自动编译配置流程如下:
vscode终端——运行任务—-typescript—-监视xxx任务.
以后你这个项目中ts代码只要有更新,自动将ts代码编译为js代码.
TypeScript02-数据类型
TypeScript02-数据类型
TypeScript强类型语言,在定义变量的时候必须要指定数据类型是什么?
首先得知道目前我们TypeScript有哪些数据类型
一.数字类型
var price:number = 20
price = true //报错.必须指定price值为number才能编译通过
二.字符串类型
var username:string = "xiaowang"
username = "xiafoefie"
三.布尔类型
var flag:boolean = true
flag = false
boolean类型的值只能true\false.
四.数组类型(重要)
语法一:
// 语法一
let array:number[] = []
array.push(1)
array.push(2)
// array.push("xiaowang")
let array2:string[] = []
array2.push("xiaowang")
array2.push("1")
// array2.push(1)
定义数组的过程中,数据类型一开始就定下来,以后这个数组就只能存放对应的数据类型
语法二:
let array3:Array = []
// array3.push("xiaowang")
array3.push(1)
使用到了泛型,Array代表数组类型,``数组里面存放的number数据类型
五.undefined和null
/**
* undefined 和 null
* 在TS中,变量没有赋值之前不能使用
* a的数据类型要么number,要么undefined
*/
let a:number | undefined;
console.log(a);
a = 20
let b:string | null = null
b = "xiaowang"
在TS中我们一般用undefined和null来表现我们可能出现的特殊情况.
六.元组类型(了解)
元组类型是数组类型一种衍生,扩展.
数组类型默认只能存放统一数据类型的值.要么全是number\要么全是string类型
如果我一个数组要存放多个数据类型,可以考虑用元组来实现
/**
* 元组类型
*/
let point:[number,string,boolean] = [1,"xiaowang",true]
元组类型就是将你们数组中每一个位置的数据类型定义出来
七.枚举类型
enum就是我们TS中提供枚举类型.
场景:比如你们项目中会有订单,订单会有状态.一个订单会有很多个状态.
以前的JS代码:
const order = {
id:1,
name:"小米耳机",
state:1
}
if(order.state==1){
}else if(order.state==2){
}
上面这段代码非常不规范,用到魔法数字(魔鬼数字)
猜测1代表什么意思\2代表什么意思
相当于要将魔鬼数字1\2\3\4转化为能够可读的代码
TS代码优化
/**
* 枚举类型
*/
enum orderState {
paySuccess=1,
payError=2,
payTimeout=3
}
// 后端传递回来订单
const order = {
id:1,
name:"小米耳机",
state:1
}
if(order.state == orderState.paySuccess){
}else if(order.state == orderState.payError){
}else if(order.state == orderState.payTimeout){
}
前提:枚举类型里面状态必须是有限的.一旦 写了以后代码就可以直接用枚举中的状态.
代码解构会更加清晰
八、any类型
表达任意类型的一个基础类型
let age:any = "xiaowang"
age = 20 //any意义就不大
any代表任意类型,主要用于表达哪些无法用准确的数据类型来进行表示的场景
const element:any = document.getElementById("app")
比如我们要获取页面上某个节点,这种情况下我们不好用数据类型来表达,为了满足ts的规范,我们可以使用any来进行类型声明/
定义一个数组,这个数组里数据可以有多种类型
let array:any = [1,"xiaowangf",true] //编译没有问题
九、never类型(了解)
never类型表示哪些永远不存在的值
never类型的应用场景比较局限,主要表达哪些总会抛出异常或者不会有返回值的函数表达式或者箭头函数。
let others:never;
others = (()=>{
throw new Error("错误")
})()
十、object类型
在TS开发过程中object类型可以表达三种结果
表示值为{}、也可以[]、还可以function
/**
* object数据类型
*/
let obj1:object = {}
let obj2:object = []
let obj3:object = function(){
}
let obj4:object = {id:1}
console.log(obj4.id); //报错,obj4找不到id属性
如果我定义一个对象,明确告诉TS代码,我的对象里面有哪些东西
let student:{id: number;name: string}
student = {id:1,name:"ddd"}
TypeScript03-函数的定义
TypeScript03-函数的定义
TS中函数定义和JS是一模一样的,可以是普通函数申明,也可以函数表达式
但是在TS中针对函数做了很多的约束、函数的参数进行约束、函数的返回值进行了约束
一、函数参数
我们定义一个函数,参数要接受外部的值
// 普通函数定义
function show(params:number,params2:string){
console.log(params);
console.log(params2);
}
show(1,"xiaowang")
在函数形参进行了约束,在传递值的时候,就必须按照指定的参数类型传递
参数个数必须满足要求 每个位置参数,数据类型一定要匹配
参数是一个对象
const play = (user: { id: number }) => {
console.log(user.id);
}
play({ id: 1 })
const play2 = (stus:string[]) => {
console.log(stus);
}
play2(["1","2"])
参数是可变参数
const play3 = (params:number,...params2:number[]) => {
console.log(params); // 1
console.log(params2); // [2,3,4,5,6,7,8]
}
// 表示可变参数:参数的个数可以动态变化
play3(1,2,3,4,5,6,7,8)
二、函数的返回值
你在TS中定义每个函数都应该有返回结果。
就算函数不需要返回,我们也应该告诉调用者,当前函数没有返回结果
// void代表当前函数没有返回结果,不写默认编辑器添加
function eat(num1:number):void{
console.log(num1);
return;
}
eat(10)
function eat2(num1:number,num2:number):string{
const result:string = num1 + num2 + "";
return result
}
const total2:string = eat2(20,30)
TypeScript04-接口编程
TypeScript04-接口编程
前后端进行数据交互的时候,后端要提供数据接口。
接口本质上是一种规范,其实就算针对后端返回的数据进行了规范约定。
针对前端代码进行一些数据约束,接口编程。
配置一台电脑,首先购买主板,主板上很多接口、内存的接口、硬盘接口、CPU接口/
不同厂家或者不同生产厂商都回遵循一定的标准来设计产品。这个标准按照接口标准来设计。
在TS中接口编程主要就算针对前端的一些数据、前端一些业务进行标准约束。将这种称为接口编程
接口的分类:
属性类型接口:使用接口来约束我们的属性 函数类型接口:使用接口来约束我们函数的参数或返回值 可索引接口(扩展) 类类型接口:在面向对象中会接触到,用接口来进行面向对象开发
一、属性类型接口
定义一个对象,这个对象有指定的属性
编号:number
名字:string
年龄:number
// 使用接口来对user进行约束
// 只要interface来定义,变量名字首字母I
// 接口设计了一个公共的规范(定义了新的数据类型)
interface IUser {
id:number,
name:string,
age:number,
array:number[]
}
const user:IUser = {
id:1,
name:"xiaowang",
age:20,
array:[1,2,3]
}
function show(user:IUser):void{
console.log(user);
}
show(user)
接口里面可以设置可变的约束
/**
* 针对一些场景进行设计
*/
interface IStu {
id:number,
name:string,
address?:string //这个属性非必须,可有可无
}
const student:IStu = {
id:1,
name:"xiaowang",
address:"wuhouqu"
}
const student2:IStu = {
id:2,
name:"xiaofeifei"
}
?: 在接口中代表当前这个属性非必须的,如果你要使约束,必须按照我们类型类进行约束
二、函数类型接口
函数类型接口,我们主要针对函数的参数和函数的返回值进行约束
/**
* 使用接口来对函数进行约束
* show函数要接受一个对象
*/
interface IUser {
id:number,
name:string
}
function show(params:IUser):void{
console.log(params);
}
const user:IUser = {
id:1,
name:"xiaowang"
}
const user2:{id:number} = {
id:1
}
show(user)
//show(user2) //编译报错
/**
* 写一个函数,这个函数我要接受一个数组作为参数
* 数组里面装的对象
* 对象数组,数组里面存放指定的对象
*/
interface IClasses {
id:number,
name:string
}
function play(array:IClasses[]):IClasses{
return array[0]
}
const data = [
{id:1,name:"xiaowang"},
{id:2,name:"xiaofei"},
{id:3,name:"xiaoliu"},
]
play(data)
复杂数据类型,接口嵌套
/**
* 复杂的数据类型
* 定义一个班级,每个班级有一个老师,每个班级有多个学生
*/
// 定义一个接口约束teacher
interface ITeacher {
id:number,
name:string,
type:string
}
interface IStudent {
id:number,
name:string
}
interface IClasses2 {
id:number,
name:string,
teacher:ITeacher,
stus:IStudent[]
}
const classes:IClasses2 = {
id:1,
name:"web21",
teacher:{id:1,name:"bobo",type:""},
stus:[
{id:1,name:"小王"}
]
}
三、可索引接口(了解)
/**
* 可索引接口
*/
interface IArrindex {
// index代表数组的下标,默认为number
[index:number]:string
}
const arr:IArrindex = ["xiaowang","xiaozhang"]
四、类,类型接口
类类型接口,需要在面向对象开发过程中才能使用。
目前暂时还没有讲到面向对象。
五、在接口中定义函数类型
type successType = (msg:string)=>void
// 用于定义函数类型
type error = (error:string)=>void
interface IType {
url:string,
method?:string,
data:newData,
success:successType,
error?:()=>void
}
在接口中要定义函数类型,我们可以函数名字:error:()=>number
练习题:
定义一个数组,里面存放用户信息。两种不同类型的用户(普通管理员和超级管理员)
普通管理员(id、name、role、dept, age)
超级管理员(id、name、role、menus:[{id :1,path:"/home"}],age)
定义两个函数,传递一个数组进去,
1、返回所有的超级管理员
2、求年龄最小的用户是谁
泛型编程
TypeScript05-类型断言
TypeScript05-类型断言类型推断
一、类型断言
普通断言
TS会自动进行类型推导,比如你的一个函数没有明确声明返回结果,编辑器会自动推断你的返回结果类型
function show(){
return 12
}
自己来进行类型推断,肯定有些地方部适用于我们程序,比如下面的代码
// 普通管理员
interface IUser {
id: number,
name: string,
role: string,
dept: string,
age: number
}
interface menu {
id: number,
path: string
}
// 超级管理员
interface IManager {
id: number
name: string
menus: menu[]
age: number
role: string
}
// 将这两个约束合并在一起。同时作用域myUser
// type称为类型声明,你可以指定一个新的数据类型
type members = IUser | IManager
let myUser: members[] = [
{ id: 1, name: "xiaowang", role: "普通管理员", dept: "研发部", age: 23 },
{ id: 2, name: "xiaofei", role: "普通管理员", dept: "市场部", age: 24 },
{ id: 3, name: "xiaoliu", role: "超级管理员", age: 20, menus: [{ id: 1, path: "/home" }] }
]
// 定义一个函数,返回所有的超级管理员
function findUser(params: members[]): void {
const result = params.filter(item=>{
return item.menus //报错
})
}
item.menus获取这个属性,编辑器就会报错,推断出来item可能没有menus属性
使用类型断言
function findUser(params: members[]): void {
// 需要类型断言,告诉编辑器,我知道自己在干什么
const result = params.filter(item=>{
return (item).menus
})
}
还可以继续下面这种语法
function findUser(params: members[]): void {
// 需要类型断言,告诉编辑器,我知道自己在干什么
const result = params.filter(item=>{
return (item as IManager).menus
})
}
非空断言
?.这个运算符号是ES2020这个版本推出的一个语法,可以判断符号前面的元素是否可用
// 如果myAllState是一个字符串,任何一个字符串能够toString()
let myAllState:null | undefined | string
// 检测你myAllState是否为null或者undefined。?.就不会继续往后执行代码
myAllState?.toString()
TS也提供了这种非空判断
myAllState!.toString()
在定义函数的时候,我们可以针对函数类进行断言
type myFun = ()=>number
function show(callback:myFun | undefined){
// const num1 = callback()
const num2:number = callback!()
}
show(undefined)
show(function(){
return 123
})
?.这个才是完整的表达式,ES2020(ES11)的新增的特性,正好应用在ts里面
! 这个是可以独立使用的。这个是TS提出的概念
二、类型别名
联合类型
类型别名经常用于给一个类型起一个新的名字,一般用于联合类型
场景一:
type message = number
let username:message = "xiaowang"
场景二:
联合类型,一个类型可以将多个类型合并起来表达
type message = number | string | undefined
let password:message = "xiaowang"
password = true //报错。password无法赋值为boolean类型
以后你可以将type拿来定义一种新的数据类型,将这个类型拿去进行使用
交叉类型
如果有两个类型,在使用过程中想要合并在一起,我们可以用交叉类型
最基础的语法
type users = number & string
users的数据类型是将number和string交叉的部分合并起来
这种代码没有太多的意思,number和string本来就没有交叉的部分
真正的引用场景
/**
* 交叉类型
*/
// type users = number & string
type mixinType = {id:number} & {name:string}
// minixType最终结果等于 {id:number,name:string}
let data:mixinType = {
id:1,
name:"xiaowang"
}
还可以合并引用类型
interface IA {
obj:{
a:boolean
}
}
interface IB {
obj:{
b:string
}
}
type AB = IA & IB
let result:AB = {
obj:{
a:true,
b:"xiaowang"
}
}
思考?到底interface 和 type有什么区别?
区别:
两种方式都可以用来定义我们的数据类型,interface一般专注于对某个具体对象进行约束。
type一般用于针对多个类型可以合并的情况,重新定义新的类型。
type来管理多个interface
练习:
封装一个ajax网络请求。
ajax函数需要接受外部的参数是一个对象
ajax({
url:"地址",
method:"GET", //可有可无 ,没有传递默认GET
data:{id:1,name:"xiaowang"} | "id=1&name=xiaowang",
success(msg){
console.log(msg)
},
//可有可无。
error(){
}
})
function ajax(obj){
}
// 答案
// 成功函数类型
type SuccessType = (msg: string) => void;
interface IObj {
url: string;
method?: string;
data: IData;
success: SuccessType;
error?: (err: string) => void;
}
type IData = string | IObjData;
interface IObjData {
id: number;
name: string;
}
// 定义函数
function ajax({ url, method = "GET", data, success, error }: IObj) {
const xmlhttp = new XMLHttpRequest();
xmlhttp.open(url, method);
const res: string = changeDataToString(data);
console.log(res);
xmlhttp.send(res);
xmlhttp.onreadystatechange = function () {
if (xmlhttp.status == 200 && xmlhttp.readyState == 4) {
const res = xmlhttp.responseText;
success(res);
} else {
const res = xmlhttp.responseText;
error!(res);
}
};
}
ajax({
url: "http://127.0.0.1",
method: "GET",
data: { id: 1, name: "xiaowang" },
success(msg) {
console.log(msg);
},
error(err) {
console.log(err);
},
});
function changeDataToString(data: any) {
let str: string = "";
let arr: string[] = [];
for (let key in data) {
str = `${key}=${data[key]}`;
arr.push(str);
}
return arr.join("&");
}
TypeScript06-泛型编程
TypeScript06-泛型编程
一、TS开发过程中遇到的问题
比如我现在要设计一个函数,这个函数接受两个参数,将两个参数的累加起来,返回给外部
function computed(params1:number,params2:number):number{
const result = params1 + params2;
return result
}
又来了一个需求,将两个字符串加起来,返回到外面
function computed2(params1:string,params2:string){
const result = params1+params2
return result
}
因为我们在设计函数的时候,我们需要定义函数参数类型。这种情况下,我们只能针对不同的参数,来设计不同的函数。
泛型这个知识点就是用于解决参数无法动态变化的问题
二、函数泛型编程
采用泛型编程来解决我们上面的问题
function play(params1:number,params2:number){
return params1+params2
}
function play2(params1:T,params2:T):T{
return params1
}
play2(12,34)
play2("xiaowang","xiaozhang")
``这个代表的占位符,T代表的是数据类型。
以后你要传递参数给play函数,我们可以自己来定义参数是什么类型
T 这个名字并不是固定的,你可以换成其他名字。一把要求大写
function play3(address:K){
const newAddress:K = address
}
练习:编写一个函数,接受一个数组,返回数组中最大值
/**
* 使用泛型来解决问题
*/
function getMaxValue3(arr:T[]):T{
let max = arr[0]
arr.forEach(item => max = item > max ? item : max)
return max
}
const mydata = [1,4,7,9,0]
const value = getMaxValue3(mydata)
console.log(value);
const value2 = getMaxValue3(['a','b','c'])
console.log(value2);
当我们代码在运行过程中,我们就能够知道T到底是什么数据类型。
三、类的泛型编程
// 函数的泛型,就在调用这个函数的时候,我们可以指定函数泛型的值
// 类的泛型,在定义class的时候,也可以使用泛型来占位.创建对象在确定
/**
* 使用面向对象的语法来获取数组中最大值
*/
class Compare{
// 类里面定义了一个属性,这个属性的类型为T
list: T[] = []
// 往list数组装我们数据
add(num: T) {
this.list.push(num)
}
max(): T {
let max = this.list[0]
this.list.forEach(item => max = item > max ? item : max)
return max
}
}
const comp = new Compare()
comp.add(1)
comp.add(2)
comp.add(3)
console.log(comp.max());
const comp2 = new Compare()
comp2.add("a")
comp2.add("b")
comp2.add("c")
console.log(comp.max());
在定义类的需要加上``代表当前类接受的外部类型
let array:number[] = [1,2,3]
let array:Array = [1,2,3]
四、定义多个泛型
一个函数或者一个类,有可能会使用多种数据类型进行定义。多个数据类型都是可变的,我们可以传递多个占位
/**
* 多个泛型
*/
function computedData(arg1:T,arg2:K):T{
console.log(arg1);
console.log(arg2);
let newpar:T = arg1
return arg1
}
computedData(1,"xiaowang")
你可以在<>中定义我们的多个泛型,获取到结果后赋值内部的变量
五、泛型约束
代码如下:
/**
* 泛型约束
* T目前没有明确声明他是什么类型.导致arg获取里面内容.检测不到
* 传递一个对象进去,获取对象里面length属性
*/
function tranalate(arg:T){
console.log(arg.length);
}
interface IMyUser {
id:number,
name:string,
length:number
}
const user:IMyUser = {
id:1,
name:"xiaowang",
length:2
}
tranalate(user)
// tranalate(1)
目前遇到的问题,使用泛型来定义数据类型,我们发现无法推导出参数的类型是什么。
因为T就是属于变化的部分。
有可能在使用过程中并没有传递对象,并且还有length属性
对泛型进行一些约束
/**
* 泛型约束
* T目前没有明确声明他是什么类型.导致arg获取里面内容.检测不到
* 传递一个对象进去,获取对象里面length属性
*/
function tranalate(arg:T){
console.log(arg.length);
console.log(arg.name);
console.log(arg.id);
// 类型断言
// console.log(arg.address);
}
interface IMyUser {
id:number,
name:string,
length:number
}
interface IMyUser2 {
id:number,
name:string,
length:number,
address:string
}
const user:IMyUser = {
id:1,
name:"xiaowang",
length:2
}
const user2:IMyUser2 = {
id:1,
name:"xiaowang",
length:2,
address:"武侯区"
}
tranalate(user)
tranalate(user2)
我们在设计代码过程中,我们使用extends来进行泛型的继承
传递进来任何类型,我们都可以以IMyUser作为我们参考。
为什么不直接使用IMyUser来定义类型
直接写死这种有可能会出现类型丢失情况。不利于扩展我们代码
六、泛型工具类型
为了开发者方便使用TS,内置了一些常见的工具或者代码。
typeof类型
typeof是用来检测基本数据类型的运算符。
在js中我们可以使用typeof检测基本类型
let a = 10
typeof a ==>number
在ts中我们typeof应用范围更加广泛
interface IPeople {
name:string,
age:number
}
const polic:IPeople = {
name:"wangsir",
age:23
}
// police实例化过后的对象,我可以根据实例化后的对象,得到当前他的数据类型
type man = typeof polic //man = { name:string,age:number}
const teacher:man = {
name:"wangsir",
age:23
}
你甚至可以从一个已经实例化的对象中得到他的约束类型。作为一个新的数据类型来使用
真实业务场景:比如别人给你传递一个对象过来。你可以通过这个对象,构造一个跟他数据类型一模一样的对象。
甚至typeof还可以推断出我们复杂的对象
const mystudent = {
id:1,
name:"xiaowang",
age:20,
classes:{
id:1,
name:"五年"
}
}
type newType = typeof mystudent
typeof推断出来的数据类型
type newType = {
id: number;
name: string;
age: number;
classes: {
id: number;
name: string;
};
}
keyof
keyof这个关键字在TS中可以获取到我们指定对象中所有的属性名字。
问题:
function searchTeacher(arr:ITeacher[],key:string,value:string){
// for (let index = 0; index < arr.length; index++) {
// if(arr[index][key] == value){
// return arr[index]
// }
// }
const res = arr.filter(item=>{
if(item[key] == value){
return true
}else{
return false
}
})
}
searchTeacher(teahcer,"type","数学")
item[key]报错,当前我们key是一个变量,无法明确告诉编译器这个变量的值范围是什么。
keyof这个关键字是TypeScript2.1版本引入的一个模块,这个操作符表达意思用于获取某个数据类型的键。返回一个联合类型,这个联合类型里面存放的就是所有的key
interface IPeople {
id:number,
name:string
}
const array = [
{id:1},{id:2}
]
type K1 = keyof IPeople // id | name
type K2 = keyof IPeople[] // length | toString | push | pop 等等
type K3 = typeof array[0] // id
解决上面遇到的问题。动态获取一个对象中指定的属性。
解决方案1:
interface IObj {
id:number,
name:string,
address:string
}
//将obj对象进行断言,赋值为any.不推荐
function ObjectKey(obj:IObj,key:string){
return (obj as any)[key]
}
将obj断言为any类型,让程序能够知道我们操作的一个any类型的对象,获取any不再严格校验
这个方案我们不推荐使用
解决方案2:
interface IObj {
id:number,
name:string,
address:string
}
// 如果key值能明确下来. key = id | name | address
// key as keyof IObj = key = id | name | address
function ObjectKey(obj:IObj,key:string){
return obj[key as keyof IObj]
}
解决方案3:
function objectKey2(obj:T,key:K){
return obj[key]
}
objectKey2({id:1,name:"xiaowang",address:"wuhouqu"},"name")
objectKey2({id:1,name:"xiaowang",address:"wuhouqu"},"address")
K extends keyof Tk的结果应该来自于我们T这个约束的key。对象里面有多少属性,我们就可以传递多少的key值进行匹配
TS掌握到什么程度。写Vue3+TS
你开发过程中,如果想要看某些源码。源码采用TS里面的一些内容表达什么意思
TypeScript07-面向对象编程
TypeScript07-面向对象编程
一、类的定义
class Student{
// 类的属性必须要明确声明 属性:数据类型
id:number
name:string
constructor(id:number,name:string){
this.id = id
this.name = name
}
show(msg:string):void{
}
}
export default {}
类的属性不能动态生成,必须要提起将属性定义出来
二、类的属性和行为
静态属性和静态行为
class Student{
// 类的属性必须要明确声明 属性:数据类型
id:number
name:string
// 类的属性
static address:string = "高新区"
constructor(id:number,name:string){
this.id = id
this.name = name
}
show(msg:string):void{
}
static play(){
}
}
const stu = new Student(1,"xiaowang")
console.log(stu.name);
console.log(Student.address);
console.log(Student.play());
静态属性和静态行为,我们也称为类属性和类行为,只能通过类名来进行调用。
三、类的继承
/**
* 访问修饰符是ts提出修饰符,
*/
class People{
type:string
constructor(type:string){
this.type = type
}
}
class Police extends People{
name:string
constructor(type:string,name:string){
//执行父类的构造器
super(type)
this.name = name
}
}
const man = new Police("人类","xiaozhang")
子类继承父类的时候,子类的构造器里面第一句话必须super
访问修饰符。ts提出的一个对属性和行为进行修饰关键字。
private:用于定义我们的属性,这个属性我们可以在奔本类中访问。子类和类的外部都无法访问
public:可以在本类中访问,也可以子类中访问、类的外部访问
protected:受保护的属性买这个属性,本类可以使用,子类可以使用。类的外部不能使用
访问修饰含义范围private私有类型本类protected受保护类型本类、子类public公有类型本类、子类、外部
只读属性,readonly
class Police extends People{
// public代表公共。name属性任何地方都可以使用
public readonly name:string
constructor(type:string,name:string){
super(type)
this.name = name
}
show(){
// private设计的私有属性,在子类也调用
console.log(this.type);
console.log(this.name);
}
}
const man = new Police("人类","xiaozhang")
console.log(man.name);
man.name = "xiaofeifei"
比如在React中,我们的props属性,在TS代码中设置的就是只读。只能获取数据,不能修改这个属性
四、类类型接口(扩展)
一个类只能继承另外一个类。不能实现多继承。
但是一个类可以实现多个接口。将无法多继承用接口来解决
// 父类当成规范。
class Phone {
call() {
console.log("打电话");
}
}
class Game {
play() {
}
}
// 接口的名字,和类的名字是可以重复
interface Phone {
// 接口里面定义的一个call方法
call():void
}
// 在interface里面定义函数,只能由函数的定义,不能有函数实现代码
interface Game {
play():void
}
// 游戏设备
class PSP extends Game {
}
new PSP().play()
// 苹果手机
// implements实现这个接口,也是一种继承,这种继承可以多继承(实现接口)
class IPhone implements Phone,Game {
call(): void {
console.log("这是打电话的功能");
}
play(): void {
console.log("打游戏");
}
}
一个类实现了一个接口,那他必须要在这个中重写接口中的方法
接口中负责定义规范,不负责具体实现,具体实现需要类来完成。
React中Component类
class Component{
render(){
return
}
}
class Login extends Component{
//render方法在父类,定义出来了,并没有实际的业务。
//Component这个类定义好了规范,render方法就是渲染函数
render(){
return (
)
}
}
通过接口的方式来编程
class Component{
render(){
return
}
}
class Login extends Component{
//render方法在父类,定义出来了,并没有实际的业务。
//Component这个类定义好了规范,render方法就是渲染函数
render(){
return (
)
}
}
通过Nodejs后端代码连接数据库:
MongoDB
mysql
oracle
Nodejs要连接这些数据库,连接的代码(底层网络通信)不可能让Nodejs来实现。
目前Nodejs16这个版本,可以连接MongoDB、mysql、oracle,如果哪一天市面上多了一个数据库。redis
不可能市面上多出一个数据库,就要求我们nodejs更新一个版本,去连接这个数据库
数据库通信的代码,交给数据库产生来实现。Nodejs要做一个规范,用户在连接数据库的时候,只需要提供几个方法即可
interface DB {
connect(url:string):boolean
close():void
}
MongoDB数据库产商
class MongoDB implements DB{
connect(url:string):void{
//拿着你的url来进行网络通信
return true
}
close():void
}
连接数据库的代码实现了,Nodejs以后开发过程中
1.下载mongodb
2.Nodejs代码连接数据,mongose
import {md} from "mongodb"
const boo = md.connect("http://127.0.0.1:27017")
mysql这个数据想要跟Nodejs结合起来使用,mysql数据库产生自己实现连接数据库的代码
class Mysql implements DB{
connect(url:string):boolean{
//实现Nodejs代码中连接mysql数据
}
}
Nodejs代码中我们要连接mysql
1. 下载依赖 mysql
2. 引入依赖
import {mq} from "mysql"
mq.connect()
Vue3-01基本介绍
Vue3-01基本介绍
一、Vue背景
2018年9月份,宣布了vue3.0计划。一直到2020年4月份发布可Vue3.0的第一版本。beta版本
2020年9月正式推出了第一个版本,one piece
目前企业中已经有部分开始使用vue3来进行项目开发。
Vue2的版本采用原始js来开发。
Vue3很多公司都是基于TS来进行开发。Vue3底层全部采用TS来设计。
Vue3和Vue2对比:
性能提升:Vue3这个框架将Vue全部重构了。新的框架。底层采用TS来进行重构,性能提升能达到100% 对TS的支持,Vue3底层默认采用TS进行开发。我们Vue开发过程中,一般也会默认结合TS来使用 Vue3目前创建项目采用了新的打包工具,vite工具(xxx)团队他们自己发布的一个打包。目标干掉webpack 新增了组合式api和响应式api进行开发(hook编程)
Vue2的版本不再更新了,目前2.7版本
Vue3还在不断的更新,有些规范api还在完善/
优势:
更快:vue3重写了虚拟dom。提升提升很多
更小:新增了tree shaking技术,你们项目中没有用到的代码。打包会默认去掉。
更易于维护:Flow到TypeScript变化,让我们代码实现起来更加规范
二、搭建Vue3的环境
vite打包工具
Vue2采用 @vue/cli来创建你们的项目,基于webpack来设计的一个脚手架工具
Vue3创建项目采用vite工具,
vite的地址:Vite | 下一代的前端工具链
vite这个打包工具是一种新型的前端构建工具,目的就是提升我们前端的性能。提供开箱即用的环境。以及插件
vite的优势:
项目启动会变得非常的快,相对于webpack封装脚手架来说,效率比较高
代码在需要运行的时候进行编译。只有代码真正在屏幕展示的时候,才会编译
底层热模块的替换更新,代码变更后,热更新会更快速
三、vite来搭建项目
Vite要求Nodejs版本必须12.0以上
(1)使用npm和yarn打开创建项目界面
使用npm来搭建项目
//npm init @vitejs/app
npm init vite@latest
使用yarn来创建项目
//yarn create @vitejs/app
yarn create vite
(2)选中使用vite来创建那种项目
# 项目名称,默认 vite-project
? Project name: › vite-project
# 选择模版:Vue
? Select a framework: › - Use arrow-keys. Return to submit.
vanilla
❯ vue
react
preact
lit-element
svelte
# 选择类型 Vue
? Select a variant: › - Use arrow-keys. Return to submit.
❯ vue
vue-ts
vue这个版本,代表默认Vue3+JS
vue-ts这个版本,代表默认Vue3+TS开发
(3)项目创建只需要几秒钟。
项目创建完毕后,只是项目结果完成。但是没有依赖文件
npm install
yarn install
下载依赖。你才能启动项目
(4)运行项目
npm run dev
启动项目完成过后页面上会出现Vue3欢迎提示
如果你想要创建项目,直接指定模板。npm版本6.x的时候
npm init vite@latest my-vue-app --template vue
my-vue-app:项目名字
npm的版本是7.x的时候
npm init vite@latest my-vue-app -- --template vue
Vue3开发,我们需要在vscode中安装插件:
Vue3-02项目结构梳理
Vue3-02项目结构梳理
一、项目目录介绍
src目录
——main.ts文件,这个文件整个项目入口文件,里面也包含创建实例,挂载到页面的流程
import { createApp } from 'vue'
// 默认给整个项目设置了公共的css样式
import './style.css'
// 引入根组件,将App组件挂载到实例上面
import App from './App.vue'
// 你最好将代码拆分以下。app这个Vue实例挂载其他插件
// 无需自己在创建Vue实例,createApp封装了
const app = createApp(App)
app.mount('#app')
/**
* Vue2
* new Vue({
* router,
* store
* }).$mount("#app")
*/
—-App.vue文件为项目根目录
345
333
123
在Vue3里面,我们可以支持在template标签下面同时创建多个标签。
Vue3引入了一项技术,类似于React中Fragment,以后再template里面无需再创建根标签,默认采用Fragment来进行页面处理。
这个标签上面,lang=“ts”代表以后再这个script标签里,我们可以支持ts代码。
后续我们打包的时候,就会默认将script标签里面的内容解析为js代码
steup:Vue3采用了新的api来进行编程,这些api想要能够被我们框架识别,必须再script标签上面增加steup标志,才能让我们打包的,将vue3的语法解析渲染
.box {
.h3 {
color: red;
}
}
我们在Vue3项目中,创建项目默认不支持less和scss。要使用这两种技术,我们需要自己下来依赖包
二、项目插件和模块配置
开发Vue3,我们可以将Vue2插件关闭了,不然可能影响你们的代码编译
禁用Vetur插件
安装Vue3的插件
配置组件创建的快捷键
步骤如下:
新建一个代码片段,名字vue3
打开你们的片段文件,内容拷贝进去
{
"Print to console": {
"prefix": "vue3",
"body": [
"",
"
",
"",
"",
"",
"",
""
],
"description": "Log output to console"
}
}
Vue03-基础语法
Vue03-基础语法
一、关于组件的引入
在Vue3这个项目中我们要引入外部的组件,components文件夹组件
特点:
引入组件后,我们无需注册,直接页面中渲染 默认已经加载src/components文件夹,这个文件夹下面的组件已经可以实现全局导入。
你在组件重要无需引入,默认用了某个组件,直接去src /components寻找
这是App
.box {
.h3 {
color: red;
}
}
你以后可以在vue3的配置文件中修改他默认的组件存放目录,src/components更改为其他的文件夹。
二、Fragmenet
在template标签里面 ,我们可以不用在定义根标签,
Vue3默认已经内置了虚拟标签,相当于React中React.fragmenet来进行根标签定义
三、setup关键字
Vue3提出了组合式api来完成项目开发。这些组合式的api要运行必须要有一个环境。setup就是标志我们当前Vue3的运行环境。
以后只要想用Vue3的组合式api,那就必须将代码放在setup这个模块中。
setup用法有两种:
上一个版本官方的标准语法
export default {
setup(){
//vue3的代码写在这个地方
}
}
在没有出现新版本之前,官网还提出了另外一种语法。草案中的语法
//这里面的代码就可以用组合式api
目前官方最新的语法标准,是第二种语法。
目前企业里面大部分公司都采用第二种语法作为开发标准。
选项式API:就是Vue2这个版本里采用的语法结构。提供了一个对象,对象里包含完整的JS语法、对象
组合式API:目前在Vue3中使用的一种开发模式,按需引入。你需要哪些api就手动引入api,有点像React中hook编程。
四、Vue3中提供组合式api:内部数据
ref基础使用
ref这个组合式api需要我们引入到项目中定义组件内部数据
import {ref} from "vue"
在通过ref来定义内部数据
Reactive
{{count}}
修改
{{username}}
修改username
// 定义组件内部数据
import {ref} from "vue"
// 定义一个基本数据类型,useState
const count = ref(50)
const username = ref("xiaowang")
// 函数直接定义script标签里面
const changeUsername = ()=>{
console.log(username);
// username得到的不是一个普通数据类型,而是一个对象。
username.value = "xiaofeifei"
}
script标签里面如果要获取或者修改当前定义的变量,必须对象.value的方式来实现
但是template标签里面我们可以直接使用ref定义的变量
reactive数据定义
reactive定义数据和ref有点不一样,可以将所有的数据定义为一个对象,方便我们调用
Reactive
{{state.count}}
{{state.user}}
+
-
import { reactive } from 'vue';
/**
* reactive定义组件内部数据
* 类似于React 类组件 State
*/
const state = reactive({
count:10,
user:{
id:1,name:"xiaowang"
},
stus:[1,2,3]
})
建议:
基本数据类型我们采用ref来定义
引用类型数据我们采用reactive来定义
Reactive来定义的数据,进行修改的时候,不需要value修改,直接进行值变化就可以
五、TS的代码约束
在Vue3中我们采用TS来进行代码约束和规范。
Vue3在设计业务的过程中也采用TS的方式来进行开发。
写代码过程中定义组件内部数据,我们如果没有明确的约束我们数据结构。TS会自动进行类型推断。完成的数据的约束,如果你的数据开始是空的,需要异步请求获取数据然后更新。
提前把约束写好。不要让程序自动类型推断。
ReactiveData3
{{ state }}
请求
import { reactive, ref } from 'vue'
/**
* 针对reactive进行数据约束,以后后端返回的数据如果结构不对
*/
interface IUser {
id: number,
name: string,
address: string
}
interface IState {
users:IUser[],
array:number[]
}
// Vue3 proxy
let state = reactive({
users: [{ id: 1, name: "xiaowang", address: "武侯区" }],
array:[1,2,3]
})
// 覆盖state的值,页面检测不到
const fecthData = () => {
state.users = [
{ id: 2, name: "xiaowang", address: "高新区" }
]
}
interface代表接口,可以用reactive泛型这个模块里面。规定了组件内部数据的规范,以后从后端返回到前端的数据,就必须按照这个规范来设计。
在组件中我们为了约束代码,编写interface接口。我们将这些接口单独提取到文件中
src/types文件夹里面创建ReactiveInterface.ts文件
export interface IUser {
id: number,
name: string,
address: string
}
export interface IState {
users:IUser[],
array:number[]
}
代码中可以直接引入外部的数据
ReactiveData3
{{ state }}
请求
import { reactive, ref } from 'vue'
import {IUser,IState} from "../../types/ReactiveIterface"
/**
* 针对reactive进行数据约束,以后后端返回的数据如果结构不对
*/
// Vue3 proxy
let state = reactive({
users: [{ id: 1, name: "xiaowang", address: "武侯区" }],
array:[1,2,3]
})
// 覆盖state的值,页面检测不到
const fecthData = () => {
state.users = [
{ id: 2, name: "xiaowang", address: "高新区" }
]
}
ref进行数据约束的代码如下
// count的类型就是number
let count = ref(10)
let myuser = ref({id:1,name:"xiaowang",address:"武侯区"})
Vue3-04基础语法2
Vue3-04基础语法2
在Vue3里实现计算属性的操作。
一、计算属性
Vue2中计算属性
export default {
computed:{
fullName(){
return this.firstName + this.lastName
}
}
}
Vue3提供的所有操作都是采用组合式api的方式呈现的。
我们如果要实现计算属性,也需要导入计算属性的模块,来进行计算属性操作
computed
{{fullName}}
修改姓
import { reactive,ref,computed} from 'vue'
const state = reactive({
firstName:"小",
lastName:"王"
})
const fullName = computed(()=>{
return state.firstName + state.lastName
})
计算属性也被封装为组合式的api,我们需要引入这个api来进行计算属性业务。
computed被封装成了一个函数,需要计算属性的时候,调用这个函数获取结果
这种编程的方式更加偏向hook编程
语法2:
computed
{{fullName}}
{{upperCaseName}}
{{newValue}}
修改newValue
修改姓
import { reactive,ref,computed} from 'vue'
const state = reactive({
firstName:"小",
lastName:"wang"
})
const newValue = computed({
get(){
console.log(123);
return state.lastName
},
set(val){
console.log(val);
}
})
语法2相对来说使用比较少,计算属性的值,一般获取来使用,很少对进行修改
二、watch模块
在Vue3.0里面watch也是通过api的方式来提供的,功能跟之前vue2的侦听器一样的效果
Vue2的语法
export default {
data(){
return {
username:"xiaowang"
}
},
watch:{
username:{
//当我们监控的username发生变化的时候,默认执行handler
handler(value){
},
deep:true,
immediate:true
}
}
}
Vue3里面watch也是采用api的形式提供。
watch模块
原始数据:{{state.username}}
{{state.user}}
修改username
修改user
import { reactive,ref,watch} from 'vue'
const state = reactive({
username:"xiaowang",
user:{
id:1,name:"王二麻子"
}
})
// 监听watch模块
// 第一个回调函数,要监控的属性是哪些。
// 第二个回调函数,数据发生变化,要执行业务
watch(
()=>state.username,
(val,prevVal)=>{
console.log(val);
console.log(prevVal);
}
)
如果我们要监控user对象
当我们要监控引用类型的数据。需要深度监听和立即监听
第三个参数:提供一个对象,对象里加入deep:true、immediate:true
watch(
() => state.user,
(val, prevVal) => {
console.log(val);
console.log(prevVal);
},
{
deep:true,
immediate:true
}
)
三、watchEffect函数
响应式的在跟踪你们的依赖项,一旦检测变化,立即执行回调函数。
watchEffect和watch都是用于进行页面的监控。
watchEffect提供了一个回调函数,这个函数中使用reactive中某些属性,只要这些属性发生变化,我们都可以监控到
watchEffect(()=>{
// 里面可以执行异步请求,日志记录等等
console.log(state.username);
// 监控user这个对象地址发生变化,里面属性变化检测
console.log(state.user);
// 使用了user对象里面某个属性才能监控到
console.log(state.user.name);
})
wactEffect里面使用到的变量发生变化,我们才能监控到。
如果引用类型数据,地址发生变化你才能监控到。除非指定要监控引用类型里面某个属性
watchEffect一次性可以监控很多个属性。
四、声明周期函数
在VUE2中我们声明周期函数可以直接获取使用。
export default {
mounted(){
},
created(){
}
}
Vue3中所有声明周期函数都需要引入后使用
import { reactive,ref,onMounted} from 'vue'
onMounted(()=>{
console.log("页面挂载完成后执行");
})
采用api的进入引入,调用声明周期函数执行
对比选项式API和组合式API
下面表格就是将选项式API和Vue组合式api函数区别:
选项式 APIHook inside setupbeforeCreateNot neededcreatedNot neededbeforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpdatedbeforeDestoryonBeforeUnmountdestoryonUnmountederrorCapturedonErrorCapturedrenderTrackedonRenderTrackedrenderTriggeredonRenderTriggered
以后大家在使用这些生命周期函数的时候,需要按需引入使用
抽奖练习: https://www.17sucai.com/pins/demo-show?id=33475&st=Ji6D80pE4fsP8FtbjfiX6A&e=1661245538
要求:
1.每次中奖的两个数据不能重复。
2.中奖名单出来后(中奖名单的电话号码 隐藏中间4位),请给每个中奖名单加上点击事件,点击后标注为红色边框。并将他的电话完整的显示出来。
抽奖作业
中奖名单
:key="item.id">
{{ item.name }}
{{ item.phone }}
{{ filter(item.phone) }}
{{ status == -1 ? "开始" : "暂停" }}
antd
Primary Button
Default Button
Dashed Button
Text Button
Link Button
import { reactive, ref } from 'vue'
import { IState } from "../../types/PrizeInterface"
import { Button } from 'ant-design-vue';
const state = reactive({
users: [
{ id: 1, name: "xiaowang", phone: "12354545", check: false },
{ id: 2, name: "xiaozhang", phone: "12354545", check: false },
{ id: 3, name: "xiaomei", phone: "12354545", check: false },
{ id: 4, name: "xiaofei", phone: "12354545", check: false },
{ id: 5, name: "xiaoliu", phone: "12354545", check: false },
{ id: 6, name: "xiaowu", phone: "12354545", check: false },
{ id: 7, name: "xiaoqiang", phone: "12354545", check: false },
{ id: 8, name: "xiaoyi", phone: "12354545", check: false },
]
})
const prizeUser = reactive({
users: []
})
// timmer默认值
const status = ref(-1)
/**
* 第一次进来,先判断定时器是否为-1.没有开启定时器
*/
const begin = () => {
if (status.value == -1) {
status.value = setInterval(() => {
getPrizeValue()
}, 500)
} else {
clearInterval(status.value)
status.value = -1
}
// console.log(123);
}
const getPrizeValue = () => {
const { users } = state
const newArray = []
while (newArray.length < 4) {
// 构造每次中奖
let index = parseInt(Math.random() * users.length + "")
// 判断不存在
if (newArray.indexOf(users[index]) == -1) {
newArray.push(users[index])
}
}
prizeUser.users = newArray
}
// Vue中目前没有提供过滤器
const filter = (val: string) => {
return val.replace(val.substring(3, 7), "***")
}
const choosePrize = (id: number) => {
const item = prizeUser.users.find(item => item.id == id)
// 检测你对undefined进行处理
if (item) {
item.check = !item.check
}
}
.as {
width: 200px;
height: 25px;
border: 1px solid red;
}
.wrapper{
span{
color: tomato;
}
}
评价练习:
{{state.chooseText}}
class="box"
:class="{checked:item.check}"
v-for="item in state.comments"
:key="item.id"
@click="changeState(item.id)"
>
{{item.name}}
还剩{{state.totalText}}个字
import { reactive, ref,watchEffect } from 'vue'
// 评价的数据
const state = reactive({
stars: [
{ id: 1, text: "极差", type: 1 }, //灰色1 type:2
{ id: 2, text: "较差", type: 1 },
{ id: 3, text: "中等", type: 1 },
{ id: 4, text: "一般", type: 1 },
{ id: 5, text: "好评", type: 1 },
],
chooseText:"请选中",
comments:[
{id:1,name:"准时交付",check:false},
{id:2,name:"效果明显",check:false},
{id:3,name:"使用恰当",check:false},
{id:4,name:"质量保证",check:false},
],
totalText:140,
title:""
})
watchEffect(()=>{
state.totalText=140 - state.title.length
})
const changeStar = (index:number)=>{
// 进入函数,默认将所有的type都设置为1
state.stars.forEach(item=>item.type=1)
state.stars.slice(0,index + 1).forEach(item=>{
item.type = 2
})
state.chooseText = state.stars[index].text
}
const changeState = (id:number)=>{
const item = state.comments.find(item=>item.id==id)
if(item){
item.check = !item.check
}
}
.oul {
list-style: none;
display: flex;
.star {
width: 24px;
height: 24px;
}
}
.box{
width: 130px;
height: 25px;
border: 1px solid gray;
text-align: center;
line-height: 25px;
&.checked{
border: 1px solid tomato;
}
}
Vue3-05其他环境搭建
Vue3-05其他环境搭建
一、antd组件库
Vue2一般会结合ElementUI来使用。
Vue3一般会使用antd、或者element PLUS(TS支持)
antd提供了一个ant design vue这个版本。针对vue来使用的组件库
https://www.antdv.com/components/overview-cn
(1)下载依赖
yarn add ant-design-vue
(2)在我们main.ts文件中引入样式
import 'ant-design-vue/dist/antd.css'; // or 'ant-design-vue/dist/antd.less'
(3)在对应的组件中引入antd的模块
import { Button } from 'ant-design-vue';
antd
(4)无需引入,直接动态加载
{{ status == -1 ? "开始" : "暂停" }}
antd
Primary Button
Default Button
Dashed Button
Text Button
Link Button
(5) 按需加载
打包的时候,你用到哪些组件,我们打包对应css样式
下载插件
npm i unplugin-vue-components -save-dev
这个插件只能在开发过程中用到,打包过后就没有用,这个插件开发依赖中
vite.config.js文件中配置插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [AntDesignVueResolver()],
}),
]
})
你们就可以在main.ts中删除样式引入
import { createApp } from 'vue'
import App from './App.vue'
// import 'ant-design-vue/dist/antd.css';
const app = createApp(App)
(6)配置全局引入组件。无需手动引入
在Vue3里,你可以配置你的组件全局加载,任何一个地方直接使用。import引入也可以使用
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(), //默认的vue保留
Components({
dirs: ['src/components'],
resolvers: [AntDesignVueResolver()],
}),
]
})
dirs这个配置代表默认要加载的组件地址。
components
文件夹下面所有的组件,默认去加载。在页面中引入使用。
二、配置less插件
Vue3里面默认没有支持less的插件,如果要使用less,我们只需要下载依赖包。
无需配置,默认加载依赖包
npm i less-loader less --save-dev
三、配置scss插件
scss插件需要下载三个依赖,但是下载antd过后,scss有些依赖已经被antd下载完毕,
你们要Vue3中使用scss,只需要sass插件就可以实现效果
npm install sass --save-dev
Vue3-06组件通信
Vue3-06组件通信
Vue3中父子组件通信和我们之前Vue2是同样的规则。
Vue3引入了TS来开发,通信在TS约束下面,有一些语法不一样。
父组件传递值
import { reactive,ref} from 'vue'
const count = ref(10)
在组件上传递动态数据给子组件。
子组件获取父组件传递过来的数据
子组件
{{count}}
{{username}}
import { reactive, ref } from 'vue'
// 使用Vue3内置的一个api来获取外部传递进来的数据
defineProps < {count: number,username:string} > ()
子组件defineProps来接受外部的数据。外部数据传递进来。可以用泛型来定义外部变量,接受这个数据
defineProps这个函数需要引入,可以直接调用。
父组件传递了自定义事件给子组件
子组件
{{count}}
{{username}}
修改count
修改username
import { reactive, ref } from 'vue'
// 使用Vue3内置的一个api来获取外部传递进来的数据
defineProps < {count: number,username:string} > ()
// 需要使用Vue3里另外一个api来获取自定义事件函数
// 约束外部传递进来的函数,changeCount,这个函数有一个参数val
const emit = defineEmits<{
(e:"changeCount",val:number):void,
(e:"changeUsername",val:string):void
}>()
const changeCountChild = ()=>{
emit("changeCount",100)
}
const changeUsernameChild = ()=>{
emit("changeUsername","xiaofeifei")
}
definedProps可以用于接受组件外部的数据。在TS版本中,我们是通过泛型来获取外部的值
definedEmits这个函数可以定义组件访问自定义事件函数,emit对象触发函数,数据回传给父组件
Vue3-07路由搭建
Vue3-07路由搭建
Vue3在创建项目的时候,并没有搭建好路由环境。
路由需要我们自己手动搭建出来
将路由环境搭建出来。在引入TS来进行路由的约束
一、路由环境搭建
(1)下载路由依赖
npm i vue-router@next
@next代表目前最新的版本
(2)在src/routes/index.ts文件,设计路由映射关系
import {createRouter,createWebHashHistory,createWebHistory} from "vue-router"
import Home from "../views/Home.vue"
const routes = [
{
path:"/home",
name:"HOME",
component:Home
}
]
const router = createRouter({
routes,
// createWebHashHistory()代表路由hash模式。
// createWebHistory() 代表history模式
history:createWebHistory()
})
export default router
// Vue2代码
// const router = new VueRouter({
// router,
// mode:"hash" //"history"
// })
(3)将routes/index文件引入到我们main.ts加载
import { createApp } from 'vue'
// 默认给整个项目设置了公共的css样式
// import './style.css'
// 引入根组件,将App组件挂载到实例上面
import App from './App.vue'
// import 'ant-design-vue/dist/antd.css';
import router from "./routes"
// 你最好将代码拆分以下。app这个Vue实例挂载其他插件
// 无需自己在创建Vue实例,createApp封装了
const app = createApp(App)
// 挂载router,启动项目加载router,页面中获取router进行参数获取跳转
app.use(router)
app.mount('#app')
一定要注意,先执行app.use(router)再来执行app.mount()
(4)引入vue-router官方提供的约束,对映射关系进行内容约束
import {createRouter,createWebHashHistory,createWebHistory,RouteRecordRaw} from "vue-router"
import {Component} from "vue"
import Home from "../views/Home.vue"
// routes进行ts约束,
// routes路由目前默认是写死的,如果以后路由需要动态加载,后端将路由返回前端
// addRouter() [{},{},{}]
const routes:Array = [
{
path:"/home",
name:"HOME",
component:Home,
props:true
}
]
const router = createRouter({
routes,
// createWebHashHistory()代表路由hash模式。
// createWebHistory() 代表history模式
history:createWebHistory()
})
export default router
路由的约束,你们可以自己实现IRouter,但是内容比较多,约束写起来有点麻烦
// interface IRouter {
// path:string,
// name:string,
// component:Component,
// meta?:{},
// beforeEnter: (to:{}, from:{}, next:object) => {}
// }
最后,在App.vue中添加渲染出口即可
Vue3-08Vuex状态机搭建
Vue3-08Vuex状态机搭建
Vue3创建项目的时候,并没有搭建状态机环境。我们需要再项目中自己引入状态机
并且配合TS来进行约束
一、搭建流程
(1)下载状态机的依赖
npm i vuex@next
(2)src目录下面创建store / index.ts
store里面index.ts文件,
modules文件夹,里面存放子仓库
index.ts文件中我们要设计仓库的代码
import {createStore} from "vuex"
import {IRootState} from "../types/MainStore"
const store = createStore({
state:{
username:"xiaowang",
users:[
{id:1,name:"xiaofeiei"}
],
count:10
},
getters:{
},
mutations:{
increment(state,payload){
state.count += payload
},
decrement(state,payload){
state.count -= payload
}
},
actions:{
},
modules:{
}
})
export default store
需要引入TRootState约束
export interface IUser {
id:number,
name:string
}
export interface IRootState {
username:string,
users:IUser[],
count:number
}
(3)再main.ts文件中加载我们store仓库
import { createApp } from 'vue'
import App from './App.vue'
import router from "./routes"
import store from "./store"
const app = createApp(App)
app.use(router)
app.use(store)
//一定先use加载插件,mount来挂载到页面
app.mount('#app')
(4)再页面中引入store仓库来操作仓库
用户页面
{{store.state.count}}
-
+
import { reactive,ref} from 'vue'
import {useStore} from "vuex"
// 调用useStorehook获取仓库对象
const store = useStore()
const increment = ()=>{
// 调用仓库的mutations方法 commit
store.commit("increment",10)
}
const decrement = ()=>{
store.commit("decrement",5)
}
在Vue3里我们操作仓库,要使用vuex提供的hook函数来操作useStore
this.$store
mapState\mapMuations
都无法在Vue3中使用
(5)异步更新仓库数据
actions:{
asyncIncrement(context,payload){
setTimeout(() => {
context.commit('increment',payload)
}, 1000);
}
},
页面上
const asincrement = ()=>{
store.dispatch("asyncIncrement",3)
}
二、搭建子仓库
我们需要主仓库的models中引入外部模块
models文件下面创建product.ts
import { Module } from "vuex"
// Module 这个模块专门提供用于约束子模块
// 编写一个接口,约束state
interface IUser {
username:string
}
interface IRoot {
}
// 两个泛型:约束子仓库state,ROOT主仓库完整约束
const product:Module = {
namespaced:true,
state: {
username:"xiaowang",
},
getters: {},
mutations: {
changeUsername(state,payload){
state.username = payload
}
},
actions: {
asyncChangeUsername(context,payload){
setTimeout(() => {
context.commit("changeUsername",payload)
}, 1000);
}
},
}
export default product
在子仓库中要约束数据,需要Module模块。里面对子仓库的数据进行约束
在主仓库引入子仓库
import {createStore} from "vuex"
import productModel from "./modules/product"
const store = createStore({
modules:{
productModel
}
})
export default store
页面上执行渲染和修改
{{store.state.productModel.username}}
const changeSubStore = ()=>{
store.commit("productModel/changeUsername","华仔")
}
const aschangeSubStore = ()=>{
store.dispatch("productModel/asyncChangeUsername","雇佣者")
}
要访问子仓库,我们必须指定命令空间名字
umijs01-基本概念
umijs01-基本概念
一、概念
umijs是React体系的框架。学习umijs就在学习React编程。
umijs蚂蚁金服内部设计,并开源出来的一个React封装框架。
umijs将React框架又进了封装,常用的插件已经全部集成到我们的框架当中。
umijs就是一个开箱即用的React框架。
React项目开发,比较难的一点环境搭建,需要开发者下载很多依赖包。umijs已经将我们常用的插件配置完毕了,你只需要打开项目就能配置使用。
umijs(乌米),在蚂蚁的体系下面用的最多。外面中小型公司如果要涉及到React开发,考虑用umijs来进行研发。
开发文档:快速上手
二、项目创建
(1)先在本地创建项目名字
你可以手动创建,你也可以使用下面命令行
mkdir myapp
cd myapp
(2)执行创建
yarn create @umijs/umi-app
# 或 npx @umijs/create-umi-app
(3)下载依赖
yarn install
(4)启动项目
yarn start
三、umijs使用less
umijs内置了less开发,我们无需再配置less环境,直接使用就行
React一般配合less来开发,没有配置scss环境,如果要使用scss,你需要自己配置scss环境
import styles from './index.less';
export default function IndexPage() {
return (
);
}
四、umijs内置antd
我们想要再umijs中使用antd,无需配置,直接使用就可以了。
umijs目前这个版本,内置了antd4.x版本。
而且umi已经实现按需加载,无需配置任何内容,你引入的组件,打包的时候默认将样式打包进去
import styles from './index.less';
import {Button} from "antd"
export default function IndexPage() {
return (
);
}
如果要配置主题色,umijs也已经配置完成,我们只需要指定颜色就可以了
umirc.ts文件是我umijs非常重要的配置文件,几乎所有配置项我们以后都是再这个文件中操作
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/', component: '@/pages/index' },
],
fastRefresh: {},
//配置antd的主题色
theme: {
'primary-color': '#ffa39e'
},
});
umijs02-路由搭建
umijs02-路由搭建
一、配置
我们再umi中需要配置文件有两类:
config/config.ts文件中可以配置
umirc.ts文件也可以配置,优先级会更高。
学习过程中,配置放在umirc.ts 文件
二、路由搭建流程
配置式路由——>Vuejs这种设计靠齐。
路由的配置信息需要在umitc.ts文件中进行配置。
routes: [
{ path: '/', component: '@/pages/index' },
{ path: '/login', component: '@/pages/login/Login' },
{ path: '/register', component: '@/pages/register/Register' },
{
path: '/home', component: '@/pages/home/Home',
routes: [
{ exact: true, path: '/home', redirect: '/home/product' },
{ path: '/home/product', component: '@/pages/product/Product' },
{ path: '/home/user', component: '@/pages/user/User' },
],
},
],
特点:
无需引入组件,直接component后面写上组件地址,默认加载 一级路由配置,无需设置路由渲染出口,默认一进来加载的就是一级路由 二级路由配置,需要手动指定路由渲染出口。 {props.children}
我们也可以进行路径重定向 { exact: true, path: '/home', redirect: '/home/product' },
exact:代表设置路由是否精确匹配。umijs的路由中,默认都是精确匹配 当umi发现你有二级路由配置时候,外层路由匹配默认模糊匹配。这样设计的目的就是让我们路由能够进入到二级路由甚至更多嵌套路由
三、路由跳转
我们要进行路由跳转,需要引入跳转组件,默认umi引入组件
import React from 'react'
import { Link,NavLink } from "umi"
import {useHistory} from "umi"
export default function Product(props:any) {
const history = useHistory()
const gotoPage = ()=>{
// props.history.push("/home/user")
history.push("/home/user")
}
return (
<>
Product
>
)
}
跳转404的情况
routes: [
{ path: '/', component: '@/pages/index' },
...
{ component: '@/pages/404' },
],
当我们页面跳转过后,无法匹配路由的时候,你们可以在一级路由下面配置一个404。pages目录下面创建404的页面。
四、路由权限
访问这个路由的时候,到底能够访问取决于我们是否有权限,或者是否认证通过。
如果没有权限,无法访问这个路由
React开发过程中为了解决这个问题,自己封装了一个高阶组件。
路由提取出来,进行动态匹配。动态生成Route组件
umijs中完成对路由权限的设计,我们只需要配置就完成
routes: [
{ path: '/', component: '@/pages/index' },
{ path: '/login', component: '@/pages/login/Login' },
{
path: '/register', component: '@/pages/register/Register', wrappers: [
'@/wrappers/auth'
],
},
{
path: '/home', component: '@/pages/home/Home',
routes: [
{ exact: true, path: '/home', redirect: '/home/product' },
{ path: '/home/product', component: '@/pages/product/Product' },
{ path: '/home/user', component: '@/pages/user/User' },
],
},
{ component: '@/pages/404' },
],
在你指定的路由上面添加wrappers高阶组件。进行身份认证。
你自己要提供auth组件来进行逻辑处理。
auth组件代码为
import { Redirect } from 'umi'
export default (props: any) => {
// useAuth属于用户自定义hook。
// const { isLogin } = useAuth();
const isLogin = false
if (isLogin) {
// props.children渲染你访问组件
return
{props.children}
;
} else {
// 跳转到登录页面
return ;
}
}
五、路由参数传递
参数传递跟React中是一样的效果
// history.push("/home/user?id=1")
history.push({
pathname:"/home/user",
query:{
id:1
}
})
路径拼接和传递对象我们可以将参数传递给另外一个页面
接受参数
import React, { useEffect } from 'react'
import { useLocation, useParams } from "umi"
export default function User(props: any) {
const location = useLocation()
const params = useParams()
useEffect(() => {
console.log(location);
console.log(params);
console.log(props);
}, [])
return (
User
)
}
umi参数传递,不管是那种方式,默认已经封装为一个对象,直接获取使用
umijs03-约定式路由
umijs03-约定式路由
一、约定式路由概念
约定式路由时umi提供的一种路由方式,无需自己搭建路由映射环境。
文件系统就是你的路由系统。按照官方给定的约束,来创建项目结构。
路由会自动生成。
当你们umirc.ts 文件中的routes配置删除的时候,那我们约定式路由就生效了。
二、配置项
首先启动umi约定式路由,我们umirc中routes配置删除
文件系统就是你的路由系统
参考:约定式路由
比如以下文件结构:
.
└── pages
├── index.tsx
└── users.tsx
会得到以下路由配置,
[
{ exact: true, path: '/', component: '@/pages/index' },
{ exact: true, path: '/users', component: '@/pages/users' },
]
需要注意的是,满足以下任意规则的文件不会被注册为路由,
以 . 或 _ 开头的文件或目录 以 d.ts 结尾的类型定义文件 以 test.ts、spec.ts、e2e.ts 结尾的测试文件(适用于 .js、.jsx 和 .tsx 文件) components 和 component 目录 utils 和 util 目录 不是 .js、.jsx、.ts 或 .tsx 文件 文件内容不包含 JSX 元素
嵌套路由
Umi 里约定目录下有 _layout.tsx 时会生成嵌套路由,以 _layout.tsx 为该目录的 layout。layout 文件需要返回一个 React 组件,并通过 props.children 渲染子组件。
比如以下目录结构,
.
└── pages
└── users
├── _layout.tsx
├── index.tsx
└── list.tsx
会生成路由,
[
{ exact: false, path: '/users', component: '@/pages/users/_layout',
routes: [
{ exact: true, path: '/users', component: '@/pages/users/index' },
{ exact: true, path: '/users/list', component: '@/pages/users/list' },
]
}
]
全局 layout
约定 src/layouts/index.tsx 为全局路由。返回一个 React 组件,并通过 props.children 渲染子组件。
比如以下目录结构,
.
└── src
├── layouts
│ └── index.tsx
└── pages
├── index.tsx
└── users.tsx
会生成路由,
[
{ exact: false, path: '/', component: '@/layouts/index',
routes: [
{ exact: true, path: '/', component: '@/pages/index' },
{ exact: true, path: '/users', component: '@/pages/users' },
],
},
]
一个自定义的全局 layout 如下:
import { IRouteComponentProps } from 'umi'
export default function Layout({ children, location, route, history, match }: IRouteComponentProps) {
return children
}
不同的全局 layout
你可能需要针对不同路由输出不同的全局 layout,Umi 不支持这样的配置,但你仍可以在 src/layouts/index.tsx 中对 location.path 做区分,渲染不同的 layout 。
比如想要针对 /login 输出简单布局,
export default function(props) {
if (props.location.pathname === '/login') {
return { props.children }
}
return (
<>
{ props.children }
>
);
}
404 路由
约定 src/pages/404.tsx 为 404 页面,需返回 React 组件。
比如以下目录结构,
.
└── pages
├── 404.tsx
├── index.tsx
└── users.tsx
会生成路由,
[
{ exact: true, path: '/', component: '@/pages/index' },
{ exact: true, path: '/users', component: '@/pages/users' },
{ component: '@/pages/404' },
]
这样,如果访问 /foo,/ 和 /users 都不能匹配,会 fallback 到 404 路由,通过 src/pages/404.tsx 进行渲染。
权限路由
通过指定高阶组件 wrappers 达成效果。
如下,src/pages/user:
import React from 'react'
function User() {
return <>user profile>
}
User.wrappers = ['@/wrappers/auth']
export default User
然后在 src/wrappers/auth 中,
import { Redirect } from 'umi'
export default (props) => {
const { isLogin } = useAuth();
if (isLogin) {
return
{ props.children }
;
} else {
return ;
}
}
这样,访问 /user,就通过 useAuth 做权限校验,如果通过,渲染 src/pages/user,否则跳转到 /login,由 src/pages/login 进行渲染。
umijs04-网络请求
umi04-网络请求
umi框架内置了网络请求umi-request
我们在开发过程中可以使用内置的请求模块,也可以使用axios
内置umi-request模块
import React from 'react'
import request from "umi-request"
export default function Index() {
const fetchData = () => {
request.get("http://127.0.0.1:8002/users/getAccountList2")
.then(res=>{
console.log(res);
})
.catch(error=>{
console.log(error);
})
}
return (
)
}
umi-request是内置的网络请求,底层采用的fetch这项技术来实现请求发送。
request.post("地址",{data:{}})
axios来发送请求
内置的umi-request可以实现发送请求,fetch请求功能模块相对axios来说,没有那么丰富。比如请求拦截器、终端请求等等都没有axios这么完善,一般还是使用axios来处理请求
下载依赖
yarn add axios
示例代码
import React from 'react'
import request from "umi-request"
import axios from 'axios'
export default function Index() {
const fetchData = () => {
request.get("http://127.0.0.1:8002/users/getAccountList2")
.then(res=>{
console.log(res);
})
.catch(error=>{
console.log(error);
})
}
const axiosGetData = ()=>{
axios.get("http://127.0.0.1:8002/users/getAccountList2").then(res=>{
console.log(res);
})
}
return (
)
}
umijs05-dva数据管理
umijs05-dva数据管理
一、状态管理概念
react这个框架一般会结合redux来完成数据仓库管理
原生的react开发,react+redux+redux-thunk中间件来完成项目开发
在umi中默认没有自己封装redux状态机。
redux发送异步请求:
redux-thunk redux-saga
有一个框架dva,这个框架也是react封装框架。dva框架主要封装的react-redux-redux-saga。
一般在开发过程中很少会直接使用dva框架。
umijs这个框架把dva这个框架整合起来,umi框架内置了dva。就会将状态管理交给dva来进行管理。
在umi框架中,dva其实就是作为一个插件,可以在项目中引入进来。我们就可以实现状态管理。
dva框架:DvaJS
二、搭建状态机
我们无需下载任何插件,umi框架已经整合了所有的插件和依赖
按照约定的规范来开发程序,就能实现状态管理
(1)在项目src目录下面创建文件夹models
只要是放在models文件夹下面ts文件,默认就会被加载为状态的文件
在models文件夹下面UserModel模块
/**
* 这个userModel专门用于管理user的数据
*/
export default {
// user模块的状态机数据
state:{
count:10
},
// 处理异步请求
effects:{
},
// 存放我们reducer代码
reducers:{
// reducers里面每个函数其实就是以前case
initState(state:any,action:any){
state.count = action.payload
return state
}
}
}
(2)页面上使用状态机的数据
import {useDispatch,useSelector} from "umi"
使用hook函数来获取状态机的数据
const {count} = useSelector(state=>{
console.log(state);
return state.userModel
})
const dispatch = useDispatch()
{{count}}
从状态机获取的数据,默认文件名字就是命名空间名字。必须要指定命名空间
(3)派发请求修改仓库数据
const changeCount = ()=>{
// 派发action到reducer的时候,必须指定命名空间名字
dispatch({type:"userModel/initState",payload:100})
}
需要获取到action对象,这个action对象里面需要提供命名空间,才能找到对应的reducers函数
在页面上ts检测useSelector无法加载模块
// 加上类型约束
type RootState = {userModel:{count:number}}
const {count} = useSelector((state:RootState)=>{
console.log(state);
return state.userModel
})
三、完成异步请求
redux默认没有异步请求处理的模块,我们需要借助于中间件来实现状态管理
redux-thunk:加强dispatch,让我们在派发到reducer之前发送异步请求,得到结果复制reducer
redux-saga:也是用于处理异步请求的中间件,采用与generator编程来实现异步请求
面试题:generator异步请求解决方案。iterator迭代器。
在我们userModel模块中我们可以使用effects来定义异步请求
/**
* 这个userModel专门用于管理user的数据
*/
import { getAllUser } from "../apis/user"
export default {
// user模块的状态机数据
state: {
count: 10,
users:[]
},
// 处理异步请求
effects: {
// 名字无所谓redux-saga
// 这个语法是generator代码
*getAllUserSaga({payload},{call,put}){
// 发送异步请求(这里的payload就是页面传过来的参数)
console.log(payload);
// 默认只能传递一个参数,如果多个参数,封装为对象传递过去
const res = yield call(getAllUser,payload)
console.log(res);
// 继续将结果派发给reducer更新数据put就是在执行dispacth
// 等待结果,put方法底层默认调用disptach派发请求
yield put({type:"initUser",payload:res.data.data})
}
},
// 存放我们reducer代码
reducers: {
initUser(state:any,action:any){
state.users = action.payload
return {
...state
}
}
}
}
页面上调用:
import React from 'react';
import { useDispatch, useSelector } from 'umi';
export default function index() {
// 定义数据类型
type RootState = { userModel: { count: number,users:[] } };
// 引入userModel的数据
const { count,users } = useSelector((state: RootState) => {
return state.userModel;
});
// 引入dispatch
const dispatch = useDispatch();
// 异步获取user的数据
const asGetData = () => {
dispatch({ type: 'userModel/getUserSage',payload:{id:"1"} });
};
return (
);
}
effect这个这个模块里面,我们需要编写generator代码,
里面每个函数前面都需要加上*,
在函数有里面yield关键字来控制执行顺序。等待异步的结果
put方法来进行dispatch调用,更新仓库数据
umijs06-组件内部数据约束
umijs06-组件内部数据约束
useState可以定义组件的内部数据,我们可以使用type和interface来进行约束
一、state内部数据约束
函数数组约束:
import React,{useState} from 'react'
import {IUser,IProduct} from "../../types/user"
export default function List() {
// 定义我们count数据的时候,默认的数据类型为number类型
const [count,setCount] = useState(10)
const [user,setUser] = useState({id:1,name:"xiaowang"})
const [product,setProduct] = useState([
{id:1,name:"小米"}
])
return (
{count}
setCount(100)}>修改
)
}
类组件约束:
import React, { Component } from 'react'
interface IProduct {
id:number,
name:string
}
interface IState {
list:IProduct[]
}
interface IProps {
}
export default class Cart extends Component {
// state数据已经被约束了
state:IState={
list:[
{id:1,name:"小米"}
]
}
render() {
return (
Cart
)
}
}
二、props外部数据约束
函数组件创建一个子组件,这个子组件要接受外部传递进来的参数
import React,{ReactNode} from 'react'
interface IProps {
count:number,
// ReactNode代表React节点类型
children?:ReactNode
}
export default function Header(props:IProps) {
return (
Header
{props.count}
{/*
{props}
*/}
)
}
父组件要传递参数给子组件
import React,{useState} from 'react'
import {IUser,IProduct} from "../../types/user"
import Header from '@/components/Header'
export default function List() {
// 定义我们count数据的时候,默认的数据类型为number类型
const [count,setCount] = useState(10)
return (
)
}
如果子组件那边要求必须传递参数,父组件按照子组件的标准来传递参数。
子组件那边约束Props里面函数类型
import React,{ReactNode} from 'react'
interface IProps {
count:number,
// ReactNode代表React节点类型
user:{
id:number,
name:string
},
changeCount(val:number):void,
children?:ReactNode
}
export default function Header(props:IProps) {
return (
Header
{props.count}
props.changeCount(1000)}>修改count
{/*
{props}
*/}
)
}
类组件要约束外部props
import React, { Component } from 'react'
interface IProps {
count:number,
changeCount(val:number):void
}
interface IState {
}
// 第一个泛型用于约束props对象,第二个泛型state
export default class Footer extends Component {
render() {
const {count,changeCount} = this.props
return (
Fooer
{count}
changeCount(1200)}>修改count
)
}
}
三、路由跳转
路由跳转我们在函数组件中有两种方案
props.history来进行跳转,默认支持query的传递参数
useHistory来进行路由跳转,默认支持state的方式来进行参数传递
const histroy = useHistory()
const gotoPage = ()=>{
// 传递参数不用query
histroy.push({
pathname:"/charts",
state:{
id:1
}
})
// props.history.push({
// pathname:"/charts",
// query:{
// id:1
// }
// })
}
项目开发
项目开发
技术栈
技术栈没有要求:
React、umijs、Vue2、Vue3开发
由个人独立完成
开发时间
明天开始项目设计(今晚开始搭建项目结构)
项目结构搭建完成后,请把项目上传gitee仓库里面
将我设置为管理员 18623187778@sina.cn
提供一个在线文档,gitee仓库地址共享到文档里面
每天会看 大家提交代码进度。
每天规定完成的任务
业务分析
完成养车平台
介绍背景:
在互联网的加持下,我们形成了很多在线养车平台
天猫养车、途虎养车等等。
汽车保养、汽车配件销售、汽车装饰销售、充电业务、上架入住、RBAC权限系统
一共分为两端:
APP端是客户端,用户登录进去后可以购买汽车周边产品,
美容服务:洗车、抛光、打蜡
维修服务:保养、维修
充电桩:寻找最近充电桩,查看充电桩的情况
PC平台端:系统可以管理所有的商家、管理商品、订单、评价等等
商家端:商家需要入住到平台、商家可以上传自己的店铺商品。可以上架下架等等,还可以添加自己服务
目前只需要完成平台端:
业务1:登录
业务2:商家入驻——-平台审核商家—-商铺审核—充电桩审核—门店管理
业务3:用户管理—-角色管理—菜单管理
业务4:商品管理—商品分类管理(扩展)
业务5:服务类型管理
扩展:
有一个插件querystring,你们可以下载yarn add querystring 。他可以默认蒋一个json转化为字符串; 比如:const user = {id:1,name:"xiaowang"} 通过qs.stringify(user)——> "id=1&name=xiaowang"
查看原文
上一篇
typescript javascript 前端 Invalid attempt to spread non-iterable instance.In order to be iterable, n
下一篇
前端 javascript typescript vue3中404页面显示问题Catch all routes (“*“) must now be defined using a param with a custom regexp. 返回顶部 暗黑模式
发表评论