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.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 因为 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:
相关推荐
sunfove1 天前
照度 (E) 与亮度 (L) 的关系
学习
HL_风神1 天前
设计原则之单一职责原则
c++·学习·设计模式·单一职责原则
CS创新实验室1 天前
正态分布的深入学习:从数学发现到自然法则的演变
学习·数据挖掘·数据分析·统计学·正态分布
半夏知半秋1 天前
rust学习-Option与Result
开发语言·笔记·后端·学习·rust
王夏奇1 天前
Python库学习-标准库
学习
wenxin-1 天前
NS3学习-Packet数据包结构
网络·学习·ns3·ns3内核
Century_Dragon1 天前
特斯拉Model3智能网联汽车自动驾驶虚拟教学实训软件
学习
wdfk_prog1 天前
[Linux]学习笔记系列 -- [fs]read_write
linux·笔记·学习
风送雨1 天前
Go 语言进阶学习:第 2 周 —— 接口、反射与错误处理进阶
开发语言·学习·golang