12.2 vue学习02

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>"?

  1. 破坏单页应用(SPA)体验
    SPA 的核心优势是"无刷新切换",用 <a> 会回到传统多页网站模式,失去流畅性。
  2. 服务器可能没有对应页面
    Vue/React 项目通常只在服务器部署一个 index.html,其他路由靠前端接管。用 <a> 跳转到 /profile,服务器找不到该文件 → 404。
  3. 无法享受前端路由的高级功能
    • 路由守卫(登录校验)
    • 动态路由参数(/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:(计算属性)是用来声明式地定义依赖于其他数据的"派生"数据 。它不是简单的值,而是基于其他响应式数据(如 dataprops 或 Vuex 的 state动态计算出来的值

  • 缓存 (Cache): 只有当它所依赖的数据发生变化时,才会重新计算。如果依赖没变,它会直接返回上一次计算的结果,避免重复计算,性能更好。
  • 响应式 (Reactive): 当依赖的数据变化时,计算属性会自动更新,并触发视图重新渲染。
  • 只读: 计算属性通常是只读的(虽然可以设置 setter,但一般不推荐)。

所以说放在computed里的值,只有其依赖的数据发生变化才会重新计算、更新,触发渲染,其他时候保持不变。

2.mapGettersVuex 提供的一个辅助函数 (自带的,不是自己定义的)

它和 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.data0,不能用res0,这个表示异步请求的整个对象。

调用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 因为 refreactive 就是用来定义响应式数据的,功能与 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:
相关推荐
zr想努力16 分钟前
GAS学习(UE5自用)
学习·ue5
她说..22 分钟前
MySQL JSON 处理学习文档
学习·mysql·json
资讯综合42 分钟前
一年级数学机构怎么选?与学习阶段相匹配的线上思维课程与线下机构差异分析
学习
M78佐菲1 小时前
51单片机学习笔记(2)
linux·笔记·嵌入式硬件·学习·51单片机
子不语1801 小时前
LabVIEW和PLC通过OPC通信
学习·自动化·plc
萧行之1 小时前
Observable Plot 源码深度解析——4 核心抽象逐项学习
前端·学习·数据可视化
浔溺1 小时前
al+大数据每日学习笔记37
大数据·笔记·学习
TheBestRucy2 小时前
PyTorch 基本使用学习笔记
pytorch·笔记·学习
ouynagda2 小时前
51 单片机 LED 流水灯 + 数码管动态扫描学习笔记(STC89C52)
笔记·单片机·学习