1.网络请求跨域解决方案(不是所有的地址都可以拿到数据)




跨域问题:协议、端口、域名与浏览器不相同时会产生跨越问题(还是不太明白)
解决方法:在config里加入产生跨域的域名,然后删去aioxs.get里地址的域名,然后再启动终端即可解决。
vue.config.js里增加deServer:
javascript
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
//为了解决安装querystring包并且使用但是不兼容问题
configureWebpack: {
resolve: {
fallback: {
// 配置 querystring 的 fallback
"querystring": require.resolve("querystring-es3")
}
}
},
//解决跨域问题
devServer:{
proxy:{
'/api':{
//产生跨域的地址,只需要放入域名(包含了三个信息:协议:http,端口:80,域名:t.weather.sojson.com)
target:'http://t.weather.sojson.com',
changeOrign:true
}
}
}
})
组件删去域名:
html
<template>
<div>
<h3>跨域解决问题</h3>
</div>
</template>
<script>
//引入网络请求
import axios from 'axios';
import api from '@/api/index.js'
export default{
name:"MyComponent",
data(){
return{
}
},
//挂载到生命周期函数里,拿到数据
mounted(){
// api.getChengpin().then(res=>{
// console.log(res.data);//拿到数据
// //path.js里的那个地址现在失效了,网络封装的测试失效
// })
//挂载跨域网络请求?(还是不清楚什么是跨域网络请求?现在这个地址跨域)
//axios.get("http://t.weather.sojson.com/api/weather/city/101030100")产生跨越
//更改config后删去这里前边的域名
//改完发现控制台是404,需要重启服务器,即终端再次进入
axios.get("api/weather/city/101030100")
.then(res=>{
console.log(res.data);
})
}
}
</script>
<style>
</style>

网络请求跨域问题是浏览器出于安全考虑实施的同源策略 限制:当前端 JavaScript(如 fetch/axios) 从一个源(协议+域名+端口)尝试读取另一个不同源 的服务器响应时,若目标服务器未在响应头中明确允许当前源 (如缺少 Access-Control-Allow-Origin),浏览器就会拦截该响应 ,导致前端无法获取数据;解决方法主要有三种------后端配置 CORS 头 、开发时用 devServer 代理 (如 Vue/React 的 proxy)、或通过自己的后端代理转发请求。
API ≠ 网站页面
- 访问
https://www.baidu.com是加载一个给人看的网页(HTML)。 - 调用
https://api.example.com/data是获取给程序用的数据(JSON/XML)。
API(应用程序编程接口)是一套预定义的规则和协议,允许不同软件之间相互通信;在 Web 开发中,它通常指通过 HTTP 请求(如 GET、POST)访问的接口,前端调用后可获取结构化数据(如 JSON),而不是网页 HTML,常用于获取天气、用户信息等服务,其能否跨域访问取决于服务器是否返回 Access-Control-Allow-Origin 等 CORS 头。
访问api不是访问网页或者数据库,是访问服务器根据网络请求提供允许的部分数据。
访问 API 不是直接访问网页(HTML 页面)或数据库,而是向服务器发起特定格式的网络请求(如 HTTP GET/POST),由服务器根据该请求从内部(可能包括数据库、业务逻辑等)处理并返回结构化的数据(通常是 JSON 或 XML),供客户端程序使用。
我把地址改成https://www.baidu.com/,即使添加了代理协议,也不能访问,因为这个是个网站,不是一个提供数据或者json的api接口。
对于公开的 API 网站,很多后端确实设置了 Access-Control-Allow-Origin: *,允许任意前端直接调用;对于未设置 CORS 的 API,在开发阶段可通过本地开发服务器(如 Vue/React 的 proxy)代理请求来绕过浏览器限制,而在生产环境中则需要通过自己的后端服务器中转请求,因为服务器之间的通信不受 CORS 约束。
2.Vue引入路由配置(管理页面的跳转,原来用href,现在推荐用路由来管理页面的跳转等关系)
用vue-router实现单页面应用的页面跳转

创建项目,先不选择有路由创建,之后可以选择。
介绍 | Vue Router (vuejs.org)路由的官方文档
路由的过程:(极其重要!!!就按照这个过程来,五步)


2.1 Vue中引入路由
首先在项目终端中安装路由: cnpm install --save vue-router
然后创建一个独立的路由配置文件:在src下新建一个文件夹,名称router。在router中新建一个文件名叫index.js就是路由的配置文件。 在index.js中进行编写:
javascript
//引入路由的库
import { createRouter,createWebHashHistory } from "vue-router";
//需要两个页面跳转,创建两个页面,创建文件夹views里有两个页面,然后引入进来
import HomeView from "../views/HomeVue.vue"
import About from "@/views/About.vue";
//配置这两个页面的信息,多个页面信息用数组承载,里边是对象形式
const routes=[
{//首页,以什么样的方式去访问首页
path:"/", //访问/表示首页
component:HomeView
},{//about页面,以什么样的方式去访问
path:"/about",
component:About
}
]
//配置信息中需要页面的相关配置
//创建路由对象
const router=createRouter({
//将上面对于页面路径配置放在路由·对象当中
routes,
//配置访问方式,使用createWebHashHistory函数
history:createWebHashHistory()
})
//想让外界可以访问,要导出这个路由
export default router;
//在主入口文件main.js进行引用
在main.js中引入router,并加入vue实例中:
javascript
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
//引入路由
import router from './router'
//原本是createApp(App).mount('#app'),调用use方法,将路由实例注册到vue实例中
//总感觉别的哪个文件也有createApp(App).mount('#app')
createApp(App).use(router).mount('#app')
做两个要相互跳转的页面,在src中新建views,views中新建HomeVue.vue和About.vue:
html
<template>
<h3>首页</h3>
</template>
html
<template>
<h3>关于页面</h3>
</template>
在根组件APP.vue中挂载路由:
html
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 路由的显示入口 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
//数据传递
data(){
return{
}},
components:{
},
methods:{
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
全部做完运行时白屏不显示,在终端中重新安装依赖包npm install vue-router@4,成功显示,可以看到地址不同,分别是/和/about


添加用按钮实现页面跳转:(也必须有 <router-view></router-view>进行路由的显示)
<!-- 用按钮实现页面的跳转 -->
<router-link to="/">首页</router-link>|
<router-link to="/about">关于页面</router-link>
所以APP.vue文件:
html
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 路由的显示入口 -->
<router-view></router-view>
<!-- 用按钮实现页面的跳转 -->
<router-link to="/">首页</router-link>|
<router-link to="/about">关于页面</router-link>
</template>
<script>
export default {
name: 'App',
//数据传递
data(){
return{
}},
components:{
},
methods:{
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
对于路由里的index.js:createWebHashHistory与createWebHistory区别:
const router=createRouter({
//将上面对于页面路径配置放在路由·对象当中
routes,
//配置访问方式,使用createWebHashHistory函数
//createWebHashHistory函数的地址,首页:http://192.168.124.6:8081/#/,about页:http://192.168.124.6:8081/#/about
//原理:A标签的锚点链接
//用createWebHistory函数的地址,首页:http://192.168.124.6:8081/,about页:http://192.168.124.6:8081/about,这种方式默认后台配合做重定向,否则会出现404问题
//原理:H5的postStatues()
history:createWebHashHistory()})
javascript
//引入路由的库
import { createRouter,createWebHashHistory} from "vue-router";
//需要两个页面跳转,创建两个页面,创建文件夹views里有两个页面,然后引入进来
import HomeView from "../views/HomeVue.vue"
import About from "@/views/About.vue";
//配置这两个页面的信息,多个页面信息用数组承载,里边是对象形式
const routes=[
{//首页,以什么样的方式去访问首页
path:"/", //访问/表示首页
component:HomeView
},{//about页面,以什么样的方式去访问
path:"/about",
component:About
}
]
//配置信息中需要页面的相关配置
//创建路由对象
const router=createRouter({
//将上面对于页面路径配置放在路由·对象当中
routes,
//配置访问方式,使用createWebHashHistory函数
//createWebHashHistory函数的地址,首页:http://192.168.124.6:8081/#/,about页:http://192.168.124.6:8081/#/about
//原理:A标签的锚点链接
//用createWebHistory函数的地址,首页:http://192.168.124.6:8081/,about页:http://192.168.124.6:8081/about,这种方式默认后台配合做重定向,否则会出现404问题
//原理:H5的postStatues()
history:createWebHashHistory()
})
//想让外界可以访问,要导出这个路由
export default router;
//在主入口文件main.js进行引用
问题:路由和a标签那种方式的跳转有什么区别?为什么不用那个?
跳转页面的方式,路由和a标签跳转区别:
| 特性 | <a> 标签跳转 |
前端路由(如 Vue Router) |
|---|---|---|
| 是否整页刷新 | ✅ 是(浏览器加载新页面) | ❌ 否(页面局部更新) |
| 谁控制跳转 | 浏览器原生行为 | 前端 JavaScript 控制 |
| URL 变化方式 | 直接跳转新地址 | 通常使用 history.pushState 无刷新改 URL |
| 适用于 SPA(单页应用) | ❌ 不适用 | ✅ 专为 SPA 设计 |
| 体验 | 有白屏、加载慢 | 流畅、无刷新 |
现在大多是单网页架构,通过更新更改地址实现网页的跳转。a标签更适合多网页结构,不能实现局部刷新,每次都要浏览器全部加载页面,加载速度慢。
为什么现代前端项目"不用 <a>"?
- 破坏单页应用(SPA)体验
SPA 的核心优势是"无刷新切换",用<a>会回到传统多页网站模式,失去流畅性。 - 服务器可能没有对应页面
Vue/React 项目通常只在服务器部署一个index.html,其他路由靠前端接管。用<a>跳转到/profile,服务器找不到该文件 → 404。 - 无法享受前端路由的高级功能
- 路由守卫(登录校验)
- 动态路由参数(
/user/123) - 嵌套路由、懒加载等
什么时候可以用 <a>?
- 跳转到外部网站 (如
<a href="https://baidu.com">) - 项目是传统多页应用(MPA),每个页面是独立 HTML
- 需要强制刷新(如退出登录后跳首页)
无刷新切换 (也叫"无页面刷新跳转")是指在 Web 应用中,用户点击链接或操作页面时,URL 会变化,但浏览器不会重新加载整个网页 ,而是仅更新页面的一部分内容,从而实现更流畅、更快的用户体验。
在 Vue、React 等单页应用中,用前端路由(如 router-link)代替 <a> 标签,是为了实现无刷新、高性能、可控的页面切换。而 <a> 会触发整页重新加载,破坏 SPA 体验,且可能导致 404。
| 问题 | 答案 |
|---|---|
| 无刷新切换是否依赖前端? | ✅ 是,完全由前端路由控制 |
| 能否用无刷新切换跳转外部网站(如百度)? | ❌ 不能,必须整页跳转(受安全策略限制) |
2.2 路由传递参数(最后显示结果首页、关于、新闻是并列的,新闻中还有三个导航,点击跳转一个页面但是显示不同内容)
创建项目选择上路由这个选项

创建完成后不需要像上一节一样去创建路由了,因为已经自动写好了。存在router这个文件夹以及他底下的index.js这个文件,包含里边的路由设置

并且也创建了HomeView和AboutView两个文件,并在main.js中创建了router实例并在app.vue上进行显示。不同点:index.js中引入vue页面由放在上边import 'about' from '../ '放到下边


这样是异步加载的方式,如果这个页面没有显示出来,这个代码是不会执行的,这样就节省了内存空间。如果把所有的页面引用都放在顶部,当不管这个页面有有没有加载出来,这些路由都会被执行,页面多的情况下会造成卡顿。所以一般只有首页页面引入放在顶部,其余都是异步加载。
,app.vue中把<router-link>跳转信息放在了导航<nav>中

下面进行学习
增加一个新闻的vue文件,在index.js中增加vue组件对应的路径后,并在app.vue中增添对应的按钮

新闻的vue文件里包含3个跳转链接,分别对应3个页面(这里用一个页面来表示,但是过程是一样的,这个页面必须先在index.js那里有自己对应的路径)
最后新闻的vue如图:

而跳转的页面newsdetails也在路由中进行了注册。运行加载出来后点击这几个导航可以跳转到该页面。
为了点击不同的导航按钮,连接一个页面但是显示不同内容,进行如下操作:

1、首先更改跳转后页面的路由设置,增加参数key

2、然后更改新闻页面,更改路由点击的设置,精确到key的值

3、设计跳转页面显示传递的参数

其中,这里写name是因为前边用的key是name。
这样就实现了新闻页有三个导航栏,跳转一个页面但是挂载显示不同内容了。通过路由传递参数,实现不同导航跳转一个页面但是显示不同内容。
app.vue
html
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>|
<!-- 可以通过地址来访问了,但是想要通过按钮导航还需要挂载到app上 -->
<router-link to="/news">新闻</router-link>
</nav>
<router-view/>
</template>
<style>
</style>
router中index.js
javascript
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{//添加新闻地址,新闻导航是和主页、关于并列的
path: '/news',
name: 'news',
component: () => import( '../views/news.vue')
},
{//新闻下的导航,点击跳转一个页面,显示不同内容
//那么是对应的参数,对应着baidu、wangyi等,在newsdetails对应着传递参数的那个name(因为是可变化的key,所以前边有:)
path: '/newsdetails/:name',
name: 'newsdetails',
component: () => import( '../views/newsdetails.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
new.vue
javascript
<template>
<!-- 新闻页面和首页、关于页面并列,仅显示新闻两个字 -->
<!-- <h3>新闻</h3> -->
<!-- 再挂载三个导航,点击跳转一个页面,但是点击不同,显示内容不同 -->
<!-- 不需要:,直接写对应参数name即可 -->
<router-link to="/newsdetails/baidu">百度新闻</router-link>
<router-link to="/newsdetails/wangyi">网易新闻</router-link>
<router-link to="/newsdetails/toutiao">头条新闻</router-link>
<router-view/>
</template>
newsdetails.vue
html
<template>
<h1>新闻</h1>
<!-- 一个页面显示不同导航对应的内容,显示路由传递的参数 -->
<p>{{$route.params.name}}</p>
</template>
最后结果:(可以看到点击二级导航后跳转,二级导航消失(这就是是和导航嵌套的区别))


2.3 路由嵌套配置(二级导航配置)



首先建立一个项目,勾选上router,在终端中运行。可以看到首页有home和about两个导航。
如果想在about里放入两个子导航,进行以下操作:
在router文件夹的index.js中进行以下操作:在about路径配置中加入children数组,里边放入要关联的两个子导航路径

在about页面添加子导航页面显示路由,还有子导航按钮(不添加按钮通过地址来进行访问,添加按钮可以看到,有home和about按钮,下边还有子导航)


这里加载二级导航后显示空白,如果想要默认显示一个子页面则需要再index.js路由配置中进行重定向

如图,加载上来就是第一个页面了。
3.Vue状态管理(管理组件与组件之间数据的传递)组件与组件间的数据管理

前边用props和$emit实现父子组件之间数据的传递,但是同级之间数据传递按照这个方式很困难,并且如果有10级父子级,从第一级传到最后一级经过8级也很麻烦。采用几种管理的方式处理组件间的信息传递。如图所示,用prps或者自定义事件想把紫色数据传递给其他,红色的很关键,一旦出现问题则无法实现。

计划采用集中式管理方案,增加一层数据管理,所有要处理的组件数据都发送到数据处理层,即使有组件不能使用也不会影响其他组件的信息传递。

利用Vuex实现数据管理的实现方式(与路由的配置方式很相似)
后续在创建项目时可以直接引入vuex,本次创建是手动创建,不引入vuex



现在用Pinia的很多,好像快替代Vuex了。Pinia:Pinia | The intuitive store for Vue.js (vuejs.org)
对于Vuex文档:状态管理 | Vue.js (vuejs.org)
进行基础实现:
1、首先创建项目,在集成终端安装Vuex:cnpm install --save vuex
2、然后在src中创建文件夹store,新建文件index.js用来配置Vuex。index.js里:
引入创建vuex的对象,创建一个vuex对象,对象里包含组件交互需要的所有的数据,然后将这个对象导出。
javascript
import {createStore} from "vuex"
// 创建vuex实例对象
// vuex的作用是帮我们管理组件之间的状态,作为store仓库使用
const store=createStore({
// 所以状态都放在这里(数据)
state:{
counter:0
}
})
//或者这种直接导出的形式
//export default createStore({})
//还得挂载到main中才可以被调用
//导出实例对象,外边可以直接引用
export default store;
可以是export default createStore({})(向外导出就这一个,名字可以自己取,export可以导出多个),也可以是const store=createStore({})再export default store;但是不能是export default store=createStore({})------不满足格式,会报错。
3、然后在主文件main.js中去引入vuex,并且注册vuex方法,表示项目安装vuex功能
javascript
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
// 挂载到main中才会被调用
import store from './store/index'
//创建根组件实例
const app=createApp(App);
// 安装插件
app.use(router)
//使用use方法明确安装vuex功能
app.use(store)
// 安装完所有插件安装应用
app.mount('#app')
注意!!!这里的链式调用顺序不能乱
createApp(App).use(router).mount('#app')分步骤写:
// 1. 创建应用实例 const app = createApp(App) // 2. 安装路由插件 app.use(router) // 3. 把应用挂载到页面 DOM 上 app.mount('#app')
createApp(App).use(router).mount('#app')
App.use(store)------这样写是错误的,要挂到为根组件App创建的实例上

Vue3 的应用实例规则是:必须先完成所有插件(路由、Vuex、UI 组件库等)的安装(app.use()),再执行 mount() 挂载应用。
++现在任何组件都可以读取到这个数据了++。比如在hello组件去读取,添加p标签,{{$store.state.counter}}


如果一个组件读取多次这个数据,可以引入快捷读取方式。在组件中加入:

javascript
<template>
<div class="about">
<!-- <p>counter={{$store.state.counter}}</p> -->
<h1>This is an about page</h1>
<p>{{counter}}</p>
</div>
</template>
<script>
//为了避免重复使用的麻烦,采用简略写法
import {mapState} from "vuex"
export default{
name:"about",
//计算属性,构建响应式数据,读取vuex数据
computed:{
//使用拓展运算符更简单,中括号里是数据,上面可以直接调用counter
...mapState(["counter"])
}
}
</script>
4.Vue状态管理核心(Vuex)对数据进行同步的修改,其他组件可以得到同步的响应

创建项目,选上vuex

1.Getter:对Vuex state中的数据进行过滤,符合条件的进行返回(读取过滤)

getters是对象类型,里边自定义一个函数getCount,里边传递参数是state。若想过滤生效,不能访问state,而得访问getters里自定义的函数getCount
有个问题:getCount中的state是上边的state吗?这个参数名称不是随意的吗?如果换成m可以吗?还可以使用m.counter吗?
state(或m,s等) 是一个参数,由 Vuex 自动传入。- 这个参数的值就是你定义在
state: { counter: 0 }里的那个对象。 - 你可以自由命名这个参数,但访问里面的属性时,必须通过这个参数名来访问,比如
m.counter。
所以这个参数名称可以随便换,但是都表示上边的state,下边使用也必须是xxx.counter
问题:最后页面上显示home|about以及counter常驻,是因为是二级路由吗?不是,根组件的temple中就是常驻的


上面这种方法多次使用时需要多次写$store.getters.getcounter,为了简化可采用下边的形式

store里的index.js
javascript
import { createStore } from 'vuex'
export default createStore({
state:{
counter:3
},
getters:{
getCounter(state){
return state.counter>2 ?state.counter+1 : 0
}
}
})
app.vue
html
<template>
<p>counter={{$store.getters.getCounter}}</p>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</template>
<script>
</script>
<style>
</style>
about.vue
javascript
<template>
<div class="about">
<h1>This is an about page</h1>
<p>hhh={{getCounter}}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name:"about",
computed:{
...mapGetters(["getCounter"])
}
}
</script>
问题:哪里来的mapGetters?computed是干什么的?
1.computed:(计算属性)是用来声明式地定义依赖于其他数据的"派生"数据 。它不是简单的值,而是基于其他响应式数据(如 data、props 或 Vuex 的 state)动态计算出来的值。
- 缓存 (Cache): 只有当它所依赖的数据发生变化时,才会重新计算。如果依赖没变,它会直接返回上一次计算的结果,避免重复计算,性能更好。
- 响应式 (Reactive): 当依赖的数据变化时,计算属性会自动更新,并触发视图重新渲染。
- 只读: 计算属性通常是只读的(虽然可以设置 setter,但一般不推荐)。
所以说放在computed里的值,只有其依赖的数据发生变化才会重新计算、更新,触发渲染,其他时候保持不变。
2.mapGetters 是 Vuex 提供的一个辅助函数 (自带的,不是自己定义的)
它和 mapState, mapMutations, mapActions 一样,都是为了让你在组件中更方便地访问 Vuex store 的状态和方法。
mapGetters 的主要作用是:将 Vuex store 中定义的 getters 映射到当前组件的 computed 属性中。简化调用getters中函数的方法,并且可以在mapGetters中对getters函数重命名和引用多个,简化多个函数的调用。
javascript
computed: {
...mapGetters({
localCounter: 'getCounter' // 把 store.getters.getCounter 映射为组件的 computed.localCounter
})
}
javascript
computed: {
...mapGetters([
'getCounter',
'getSomeOtherValue',
'calculateTotal'
])
}
问题:computed中都是...的这种方式吗?为什么?
... 是 对象展开运算符(Object Spread Operator),它会把一个对象的所有可枚举属性"展开"到另一个对象中。
这种方式不是必须的,但是可以起简化作用,如下两个方式等价:
javascript
// 方式1:手动定义(不用 mapGetters,也不用 ...)
computed: {
getCounter() {
return this.$store.getters.getCounter;
},
anotherValue() {
return this.$store.getters.anotherValue;
}
}
// 方式2:先取出来,再合并(等价于 ...)
const getterComputed = mapGetters(['getCounter']);
computed: {
...getterComputed
}
2.mutation:更改数据

在store的index.js中增加该方法

在vue组件中调用这个方法进行使用事件触发该方法

下边增加这个事件,以字符串的形式调用触发,固定形式

所有组件对于这个值的引用都发生了修改。
修改自加的大小


也可以采用简化的方式

index.js
javascript
import { createStore } from 'vuex'
export default createStore({
state:{
counter:3
},
getters:{
getCounter(m){
console.log(m)
return m.counter>2 ?m.counter+1 : 0
}
},
mutations:{
addCounter(m,n){
console.log(m);
return m.counter+=n
}
}
})
about.vue:
html
<template>
<div class="about">
<h1>This is an about page</h1>
<p>hhh={{getCounter}}</p>
<button @click="clickhandle">加</button>
</div>
</template>
<script>
import { mapGetters,mapMutations } from 'vuex';
export default {
name:"about",
methods:{
...mapMutations(["addCounter"]),
clickhandle(){
this.addCounter(20)
}
},
computed:{
...mapGetters(["getCounter"])
}
}
</script>
结果:点击按钮,数字增加20

3.Action:与mutation类似,但是mutation只能同步操作,Action提交mutation,还可以实现异步操作(例如网络请求时)

为测试异步操作,在项目中安装网络请求axios:cnpm install --save axios
使用公开API测试:https://restcountries.com/v3.1/name/china
在store的index.js中:

在vue组件中用按钮事件提交网络请求

事件中调用Action方法,实现异步网络请求

增加的数据来源于网络请求,属于异步操作,使用Action,同步操作还是用mutation

store里的index.js
javascript
import axios from 'axios';
import { createStore } from 'vuex'
export default createStore({
state:{
counter:3
},
getters:{
getCounter(m){
console.log(m)
return m.counter>2 ?m.counter+1 : 0
}
},
mutations:{
addCounter(m,n){
console.log(m);
return m.counter+=n
}
},
// 为异步操作准备
actions:{
// 自定义函数
asyncAddCounter({commit}){
axios.get("https://hacker-news.firebaseio.com/v0/topstories.json")
.then(res=>{
commit("addCounter",res.data[0])
// console.log(res[0]);
}
)
}
}
})
这个里选择api网站里的数组里第一个数据res.data[0],不能用res[0],这个表示异步请求的整个对象。
调用mutions里定义的函数addCounter,将异步请求数据作为n传入,与m=4进行相加
app.vue里:
html
<template>
<div class="about">
<h1>This is an about page</h1>
<p>hhh={{getCounter}}</p>
<button @click="clickhandle">加</button>
<button @click="clickAsynchandle">异步增加</button>
</div>
</template>
<script>
import { mapGetters,mapMutations,mapActions } from 'vuex';
export default {
name:"about",
methods:{
...mapMutations(["addCounter"]),
...mapActions(["asyncAddCounter"]),
clickhandle(){
this.addCounter(20)
},
clickAsynchandle(){
this.asyncAddCounter()
}
},
computed:{
...mapGetters(["getCounter"])
}
}
</script>

问题:什么是同步操作?什么是异步操作?按钮点击是同步的?为什么?不得等按钮触发的函数执行完再进行下一行程序吗?
| 问题 | 答案 |
|---|---|
| 同步操作 | 逐行执行,阻塞主线程,必须等当前行完成 |
| 异步操作 | 立即继续执行下一行,结果通过回调处理,不阻塞页面 |
| 按钮点击是同步的吗? | 事件处理函数被同步调用,但函数内部可以包含异步操作 |
| 是否等函数执行完? | 纯同步函数会等 ;包含 await 的函数会在 await 处暂停,不阻塞页面 |
- 永远不要写纯同步的耗时操作(如大循环),会卡死页面;
- 网络请求、定时器、文件读取等必须用异步 (
async/await或 Promise); - Vuex 的 Action 就是为异步操作设计的,Mutation 必须是同步的。
**问题:**asyncAddCounter({commit}){}为什么里边要加{commit}?不加可以吗?res里为什么用commit()?
解释:当定义action时,vue会自动传入一个context对象,context对象包含多个方法,action想要调用之前的方法,需要写明
{
commit, // 用于触发 mutation
dispatch, // 用于触发其他 action
state, // 当前模块的 state
getters, // 当前模块的 getters
rootState // 根 state(在模块中使用)
}
例如上面要用到mutation里定义的addCounter函数实现对state值的改变,两种方法,第一种:
asyncAddCounter(context) {
context.commit("addCounter", ...)
}
第二种:
asyncAddCounter({ commit }) { // 解构:从 context 中取出 commit
commit("addCounter", ...)
}
利用commit实现对于mutation中定义函数的调用
5.Vue3新特性

用setup将相同的逻辑功能放在一处,原来是根据程序的属性放置,现在是根据实现的功能放置。


setup中声明的变量外部想访问都需要return出去
问题:组合式API有什么好处?为什么可以代替data?为什么要从vue中引用ref?ref是干什么的?
| 问题 | 答案 |
|---|---|
| 组合式 API 有什么好处? | 逻辑内聚、复用方便、类型推导好,解决了选项式 API 的分散性和复用难题。 |
为什么可以代替 data? |
因为 ref 和 reactive 就是用来定义响应式数据的,功能与 data 相同,但更灵活。 |
为什么要从 Vue 引入 ref? |
ref 是 Vue 提供的工具函数,用于创建响应式数据,不是原生 JS 功能。 |
ref 是干什么的? |
创建一个响应式引用对象,其 .value 属性可变,变化时会触发视图更新。 |
| Vue2 Options-based API | Vue Composition API |
|---|---|
| beforeCreate | setup() |
| created | setup() |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
| errorCaptured | onErrorCaptured |
1.ref和reactive代替data,定义响应式数据


用ref和reactive定义响应式数据,ref可以定义字符串等类型,reactive可以定义对象。
想要将对象里的数组每个元素都显示出来

2.methods中的定义方法放在setup()中(事件定义放在setup)

若是事件放在setup中,则不再用methods,在setup定义函数事件,并将函数放在return中,运行外部访问

用ref和reactive定义的数据需要.value来修改

3.setup()中使用props(组件传递数据)和context(当前对象)

在根组件App.vue中定义一个变量存入字符串,想将根组件数据传到其他组件中使用并在页面中显示

在使用这个数据的vue组件中用props接受并用组合式API setup:先定义props和传输数据类型,再将props作为参数传入setup,setup中直接使用props传递的数据

若想在页面上显示,则在setup中定义变量接收传递的数据,并放在return中,p标签中加入这个变量



上边组件传递变量信息的方法用setup相较于原来用data的方法好像更复杂
setup中没有this关键字
实例本身用context表示,增加setup中参数

若想改变数据,也不需要this,直接用 数据.value 修改即可
4.Setup中使用生命周期函数(由原来的8个变为6个,并且函数名称前添加on)

之前mounted函数在一个组件中只能放一个mounted(){},现在放在setup中可以放多个同样的生命周期函数,不同业务可以放在不同函数里,使业务更清晰

5.嵌套组件的数据传递Provide/Inject

原来顺序:a-b-c,现在可实现a-c
注意:只能是父组件向子组件的方向传递,方向不能相反
根组件app.vue中:提供参数message2

父组件home.vue中,提供参数message1:

组件helloworld.vue中,接收这两个参数分别是a-c,b-c,a-c的过程不需要经过b


6.Vue3加载element-plus(UI组件库,包含多种现成的功能)
Element Plus 是一个 基于 Vue 3 的现代化 UI 组件库,它提供了大量开箱即用的高质量 UI 组件(如按钮、表单、表格、弹窗等),帮助你快速构建美观、一致的企业级中后台应用。
Element - 网站快速成型工具 (vue2版本)
https://element-plus.org/zh-CN (Vue3版本)
1.安装element-plus库:
npm install element-plus --save
或者 yarn add element-plus
2.引入element-plus
2.1 完整引入,但是项目文件会很大(在main.js中引入)
在main.js中对方法进行添加:
javascript
//引入element-plus和其配套的css文件
import ElementPlus from "element-plus"
import 'element-plus/dist/index.css'
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
这样就可以引用element-plus提供的代码
2.2 按需引入,只引入需要的组件
| 选项式 API (Options API) | 组合式 API (Composition API) |
|---------------------------|---------------------------------------|------------------------|
| 代码组织方式 | 按功能类型分组(data、methods、computed...) | 按逻辑功能分组(把相关逻辑写在一起) |
| 适用场景 | 小型项目、快速原型 | 中大型项目、逻辑复用、TypeScript |
比如一个定时器功能,在选项式api可能分成data、methods、computed等几个部分完成;但是在组合式api则会把定时器功能放一起,计算功能放一起,而不是碎片化处理
github里awesome可以去学习
element-plus按需导入

如果全部安装了,那么按需安装就是删掉main.js里的

并且在项目中安装 cnpm install -D unplugin-vue-components unplugin-auto-import
然后修改vue.config.js配置文件

javascript
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
}
})
最后,直接使用:

但是试验发现直接运行会卡在3%,是因为包的版本过高,下载制定版本的包解决
cnpm install unplugin-vue-components@0.25.2
cnpm install unplugin-auto-import@0.16.1


vue3加载element-plus的字体图标
1.首先和上边一样,项目要先加载UI库:cnpm install element-plus --save
2.然后按照上边一样按需加载:
cnpm install unplugin-vue-components@0.25.2
cnpm install unplugin-auto-import@0.16.1
3.然后将vue.config.js中内容改成
javascript
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
}
})
4.想使用饿了么UI库里的字体图标,首先要先在项目中下载字体图标:npm install @element-plus/icons-vue


5.在src下新建plugins文件夹,在文件夹里创建icons.js文件,里边粘贴如下代码:
javascript
import * as components from "@element-plus/icons-vue";
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};
6.在main.js中添加图标字体库的工具,这是和前边引入其他组件的不同之处:

7.在element-ui中找到想要的字体图标点击复制粘贴加入

8.运行项目可以看到加入了想要的字体图标

9.查看官方文档,想要改变图标的大小和颜色:


为什么属性值是数字时用v-bind,而属性是字符串时不需要?
:size="50"是 动态绑定 ------ 它告诉 Vue:"这个属性的值是一个 JavaScript 表达式,需要动态解析"。color="red"是 静态绑定 ------ 它只是把字符串"red"直接作为属性值传给组件。
| 场景 | 是否用 v-bind |
说明 |
|---|---|---|
| 属性值是变量/表达式 | ✅ 必须用 | 如 :size="iconSize"、:disabled="isDisabled" |
| 属性值是固定字符串/数字 | ❌ 不用 | 如 color="red"、title="点击我" |
| 绑定布尔值(如 disabled, readonly) | ✅ 推荐用 | 如 :disabled="isDisabled" |
| 绑定对象/数组 | ✅ 必须用 | 如 :style="{ color: 'red' }" |
问自己:这个值会不会变?是不是从 data / computed / props 来的?
- 如果是硬编码的常量(比如
"red","100px"),可以直接写。 - 如果是变量、计算结果、函数调用、响应式数据 → 一定要加
:或v-bind:。