生活札记
Vue3学习笔记 - 基础(二)
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
文档:https://cn.vuejs.org/guide/introduction.html
教程:https://www.bilibili.com/video/BV1QA4y1d7xf
1、Vue-Router:基于路由和组件,将路径和组件组合成一张映射表
官网:https://router.vuejs.org/zh/introduction.html
安装:npm install vue-router@4
代码实例:
1)、main.js:
import { createApp } from 'vue'
import App from './App.vue'
// import './assets/main.css'
// createApp(App).mount('#app')
// 导入路由:默认自动加载index.js
import router from "./router/";
// 创建实例
const app = createApp(App)
// 使用路由
app.use(router)
// 挂载
app.mount('#app')
2)、index.js:
// 导入vue-router
import { createRouter,createWebHashHistory,createWebHistory } from "vue-router"
// 1. 定义路由组件
// import Home from '../views/Home.vue'
// import About from '../views/About.vue'
// import User from '../views/User.vue'
// import NoFound from '../views/NoFound.vue'
// import News from '../views/News.vue'
// import Parent from '../views/Parent.vue'
// import SonOne from '../views/SonOne.vue'
// import SonTwo from '../views/SonTwo.vue'
// import Page from '../views/Page.vue'
// import ShopMain from '../views/ShopMain.vue'
// import ShopHeader from '../views/ShopHeader.vue'
// import ShopFooter from '../views/ShopFooter.vue'
// 路由懒加载
const Home = () => import('../views/Home.vue')
const User = () => import('../views/User.vue')
const NoFound = () => import('../views/NoFound.vue')
const News = () => import('../views/News.vue')
const Parent = () => import('../views/Parent.vue')
const SonOne = () => import('../views/SonOne.vue')
const SonTwo = () => import('../views/SonTwo.vue')
const Page = () => import('../views/Page.vue')
const ShopMain = () => import('../views/ShopMain.vue')
const ShopHeader = () => import('../views/ShopHeader.vue')
const ShopFooter = () => import('../views/ShopFooter.vue')
// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
const routes = [
// 重定向
{
path: '/',
// redirect: "/home" // 重定向
redirect: {name: 'home'} // 命名重定向
// redirect:(to) => { // 方法重定向
// console.log(to)
// return {name: 'home'}
// }
},
{
name:"home",
path: '/home',
component: Home
},
{
path: '/about',
// component: About,
component: () => import("../views/About.vue"), // 懒加载
// 单个路由守卫
beforeEnter: (to, from, next) => {
// console.log(to)
// console.log(from)
if (1 == 1){
next() // 通行证,有这个才会往下执行
}
}
},
{
name:"user",
path:'/user/:id',
component: User,
props:true // 路由组件传参,通过url如:/abc/123 获取 参数 123
},
{
// 正则:+:一个以上、*:任意、?:一个或者没有
// path:'/news/:id(\\d+)',
path:'/news/:id*',
// path:'/news/:id(\\d?)',
component: News
},
// 嵌套路由
{
path:'/parent',
component: Parent,
children:[
{
path:"sonone",
component:SonOne
},
{
path:"sontwo",
component:SonTwo
}
]
},
// 别名
{
name:"page",
alias:"/page1", // 别名
// alias:["/page2","/page3"], // 别名
path:'/page',
component: Page
},
// 多级视图路由,一个路由里有多个组件
{
name:'shop',
path:"/shop",
components:{
default: ShopMain, // 多级默认组件
ShopHeader, // 名称与router-view 的 name 属性一致
ShopFooter
},
props:{default:true,ShopHeader:true,ShopFooter:false} // 路由组件传参
},
// 404
{
path:'/:pathMatch(.*)',
// path:'/:path(.*)',
component: NoFound
}
]
// 3. 创建路由实例并传递 `routes` 配置
const router = createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
// history: createWebHashHistory(), // Hash 模式,内部传递的实际 URL 之前使用了一个哈希字符(#)
history: createWebHistory(), // HTML5 模式,如果没有适当的服务器配置,用户在浏览器中直接访问,就会得到一个 404 错误
routes, // `routes: routes` 的缩写
})
// 全局路由守卫,全局路由中间件
router.beforeEach((to, from, next) => {
// console.log(to)
// console.log(from)
next() // 通行证
})
// 渲染导出router
export default router
3)、App.vue:
<script>
// 每个组件都是独立的实例
// 申明式渲染
export default {
data(){
return {
title:"Hello App!"
}
}
}
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Home</router-link>
<br>
<router-link to="/about">About</router-link>
<br>
<router-link to="/user/1">用户</router-link>
<br>
<router-link :to="{name:'user',params:{id: 2 }}">用户1</router-link>
<br>
<router-link to="/news/1">News</router-link>
<br>
<router-link to="/parent">Parent</router-link>
<br>
<router-link to="/page">Page</router-link>
<br>
<router-link to="/shop">Shop</router-link>
<br>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view name="ShopHeader"></router-view>
<router-view></router-view>
<router-view name="ShopFooter"></router-view>
</div>
</template>
4)、About.vue:
<template>
<div>
<p>{{ title }}</p>
</div>
</template>
<script>
export default {
data() {
return {
title: "About"
}
},
// 进入路由组件守卫
beforeRouteEnter: (to, from, next) => { // 拿不到 this 实例里数据,只能通过 next() 方法获取
console.log('enter')
next((vm) => { // 通行
console.log(vm.title)
})
},
// 更新组件守卫
beforeRouteUpdate: (to, from, next) => {
console.log('update')
next() // 通行
},
// 离开路由组件守卫
beforeRouteLeave: (to, from, next) => {
console.log('leave')
next() // 通行
}
}
</script>
5)、Page.vue:
<template>
<div>
<p>{{ title }}</p>
<p><button type="button" @click="goHome">跳转到Home</button></p>
</div>
</template>
<script>
export default {
data() {
return {
title: "Page"
}
},
methods:{
goHome(){
// 跳转
// this.$router.push("/") // this.$router 是获取所有的路由,this.$route 是获取当前路由
// this.$router.push("/home")
// this.$router.push({path:"/"})
// this.$router.push({name:"home", replace:true})
// 带参数(params)跳转,pathinfo模式
// this.$router.push({ name: "user", params: {id:123}})
// 带参数(query)问号模式
// this.$router.push({ name: "home", query: { id: 123 } })
// 前进、后退
// this.$router.go(-1)
// this.$router.back()
// this.$router.forward()
}
}
}
</script>
6)、User.vue:
<template>
<div>
<p>用户</p>
</div>
</template>
<!--<script>
export default {
data(){
return {
title: "用户",
}
},
mounted() {
// console.log(this.$route.params.id)
console.log(this.id)
},
// 路由组件传参
props:{
id:{
type:String
}
}
}
</script>-->
<script setup>
import { useRoute } from "vue-router"
// console.log(useRoute().params.id)
// 属性
const props = defineProps({
id:{
type: String
}
})
console.log(props.id)
</script>
2、状态管理:数据集中在某个文件管理,提供统一数据;fetch() 、axios() 获取数据;vite 通过 proxy 实现解决跨域
axios官网:http://www.axios-js.com/
axios安装:npm install axios
1)、index.js:
// 状态集中管理:数据集中管理
// provide/inject跨级通讯
import {ref, reactive} from 'vue'
// 配置公共数据
const store = {
state:reactive({
msg:"Store",
bannerList:[]
}),
updateStateMsg:function(val){
this.state.msg = val
},
changeBanner:function(val){
this.state.bannerList = val
}
}
// 导出
export default store
2)、App.vue:
<script>
// 每个组件都是独立的实例
import store from './store'
import StoreNew from './views/Store.vue'
// 申明式渲染
export default {
data(){
return {
title:"Hello App!"
}
},
// 提供出去
provide:{
store
},
components:{
StoreNew
}
}
</script>
<template>
<div>
<h1>{{ title }}</h1>
<StoreNew></StoreNew>
</div>
</template>
3)、store.vue:
<template>
<div>
<p>{{ title }}</p>
<p>Store:{{store.state.msg}}</p>
<p><button type="button" @click="changeStateMsg">改变stateMsg</button></p>
<p>
<ul>
<li v-for="(item, index) in store.state.bannerList" :key="index">
<a :href="item.url" target="_blank"><img :src="item.imgurl" :alt="item.name"></a>
</li>
</ul>
</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
title: "Store"
}
},
inject:['store'], // 接受数据
methods: {
changeStateMsg(){
this.store.updateStateMsg("Store New")
}
},
created() {
// fetch():原生js的http请求数据,返回promise对象
// fetch("http://127.0.0.1:5173/data.json").then((res) => {
// console.log(res)
// return res.json()
// }).then((res) => {
// this.store.changeBanner(res.data.list)
// })
// axios:是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
// 官网:http://www.axios-js.com/
// axios.get("http://127.0.0.1:5173/data.json").then((res) => {
// console.log(res.data.data)
// this.store.changeBanner(res.data.data.list)
// }).catch((err) => {
// console.log(err)
// });
// 跨域请求:
axios.get("/path/api/mmdb/movie/v3/list/hot.json?ct=%E6%B5%B7%E6%B2%A7&ci=964&channelId=4").then((res) => {
console.log(res.data.data)
this.store.changeBanner(res.data.data.list)
}).catch((err) => {
console.log(err)
});
},
}
</script>
4)、配置文件vite.config.js:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
// vite的proxy代理服务,实现跨域
proxy:{
"/path":{
target:"https://i.maoyan.com", // 替换服务器的地址
changeOrigin:true, // 开启代理,允许跨域
rewrite: path => path.replace(/^\/path/,""), // 设置path重写
}
}
}
})
3、vue-cli:https://cli.vuejs.org/zh/guide/
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,Vue CLI 现已处于维护模式,现在官方推荐使用 create-vue 来创建基于 Vite 的新项目。
查看版本cli:vue --version
安装cli:npm install -g @vue/cli
升级cli:npm update -g @vue/cli
创建项目:vue create hello-world
启动项目:npm run serve
4、Vuex:https://vuex.vuejs.org/zh/index.html
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
安装:npm install vuex@next --save
代码实例:
1)、main.js:
import { createApp } from 'vue'
import App from './App.vue'
// 导入vuex
import store from "./vuex/"
// 创建实例
const app = createApp(App)
// 使用store,可以使用$store
app.use(store)
// 挂载
app.mount('#app')
2)、App.vue:
<script>
// 每个组件都是独立的实例
import VuexNew from './views/Vuex.vue'
// 申明式渲染
export default {
data(){
return {
title:"Hello App!"
}
},
components:{
VuexNew
}
}
</script>
<template>
<div>
<h1>{{ title }}</h1>
<VuexNew></VuexNew>
<!-- <Test></Test> -->
</div>
</template>
3)、vuex/index.js:vuex仓库文件,存放公共的数据,以便其他组件调用
// 导入vuex
import { createStore } from "vuex";
import axios from 'axios'
import teststore from "./teststore";
// 创建store实例
const store = createStore({
// 单一状态树,存储基本数据,state状态是与其他子模块独立的,没有进行合并
state(){
return {
msg:"Vuex",
count:0,
bannerList:[]
}
},
// getters 可以认为是 store 的计算属性
getters:{
reverseMsg(state){
return state.msg.split("").reverse().join("")
},
msgLenth(state, getters){ // state表示当前实例的state对象,getters表示当前的getters对象
return getters.reverseMsg.length
}
},
// mutations 改变状态,store.commit() 方法实现 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,实现状态的监听
mutations:{
increment(state, val){ // state 返回以上的state返回的对象,val参数可以是对象方式
state.count += val
},
changeBanner(state, val){
state.bannerList = val
}
},
// actions 行为,提交的是 mutation,而不是直接变更状态,例如获取后端接口数据
actions:{
getData(context, val){ // context 是store的对象
console.log(val)
// 跨域请求:
axios.get("/path/api/mmdb/movie/v3/list/hot.json?ct=%E6%B5%B7%E6%B2%A7&ci=964&channelId=4").then((res) => {
console.log(res.data.data.hot)
// commit 调用 mutations 执行改变state
context.commit("changeBanner", res.data.data.hot)
}).catch((err) => {
console.log(err)
});
}
},
// 为了避免臃肿,store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割,其中 state 不合并,mutation、action、getter 都会与根store的合并在一起且不能重名
modules:{
//名称:模块
// a:teststore,
// b:test
teststore
}
})
// 导出
export default store
3)、vuex/teststore/index.js:子仓库模块文件,与根store的结构是一致的
import axios from 'axios'
// 这个是对象,必须要通过 createStore 创建,子模块不需要创建,只需要在根store的modules模块加载,createStore 由根store来创建
const teststore = {
// namespaced: true, // 命名空间
// 单一状态树,存储基本数据,通过 $store.state.模块名称.属性
state(){
return {
username:"Test Store",
age:100
}
},
// getters 可以认为是 store 的计算属性
getters:{
reverseUsername(state){
return state.username.split("").reverse().join("")
},
usernameLenth(state, getters, rootState){ // state表示当前实例的state对象,getters表示当前的getters对象, rootState 根store的对象
return getters.reverseUsername.length
}
},
// mutations 改变状态,store.commit() 方法实现 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,实现状态的监听
mutations:{
incrementAge(state, val){ // state 返回以上的state返回的对象,val参数可以是对象方式
state.age += val
}
},
// actions 行为,提交的是 mutation,而不是直接变更状态,例如获取后端接口数据
actions:{
getData2(context, val){ // context 是store的对象
console.log(val)
// 跨域请求:
axios.get("/path/api/mmdb/movie/v3/list/hot.json?ct=%E6%B5%B7%E6%B2%A7&ci=964&channelId=4").then((res) => {
console.log(res.data.data.hot)
// commit 调用 mutations 执行改变state
context.commit("changeBanner", res.data.data.hot)
}).catch((err) => {
console.log(err)
});
}
},
// 为了避免臃肿,store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割,其中 state 不合并,mutation、action、getter 都会与根store的合并在一起
modules:{
}
}
// 导出
export default teststore
5)、Vuex.vue:组件
<template>
<div>
<p>{{title}}</p>
<p>Count:{{ $store.state.count }}</p>
<p><button type="button" @click="changeCount">改变count++</button></p>
<p>Getters计算属性:{{ $store.getters.reverseMsg }}</p>
<p>Getters计算属性长度:{{ $store.getters.msgLenth }}</p>
<h2>Actions返回接口数据:</h2>
<p>
<ul>
<li v-for="(item, index) in $store.state.bannerList" :key="index">
{{ index }}
<a :href="item.videourl" target="_blank">{{item.videoName}}</a>
<br>
<img :src="item.img" :alt="item.videoName" width="100">
</li>
</ul>
</p>
<p>子模块store Username:{{ $store.state.teststore.username }}</p>
<p>子模块store Age:{{ $store.state.teststore.age }}</p>
<p><button type="button" @click="changeAge">改变子模块store count++</button></p>
<p>子模块storeGetters计算属性:{{ $store.getters.reverseUsername }}</p>
<p>子模块storeGetters计算属性长度:{{ $store.getters.usernameLenth }}</p>
<!-- 开启命名空间 -->
<p>子模块storeGetters计算属性长度:{{ $store.getters["teststore/usernameLenth"] }}</p>
<!-- 辅助函数 -->
<p>辅助函数username:{{ username }}</p>
<p>辅助函数age:{{ age }}</p>
<p>辅助函数reverseUsername:{{ reverseUsername }}</p>
<p>辅助函数usernameLenth:{{ usernameLenth }}</p>
</div>
</template>
<script>
import {mapState,mapGetters,mapActions,mapMutations} from "vuex"
export default {
data(){
return {
title:"Vuex"
}
},
created() {
// console.log(this.$store.state.count)
},
mounted() {
// 组件转派发事件到store的actions中
this.$store.dispatch("getData","aaa")
// this.getData("aaa") // 解构出来之后直接调用方法
// this.getData2('bbb')
},
computed: {
// ...mapState("teststore", {username: "username", age: state => state.age }), // 命名空间解构state状态
// ...mapGetters("teststore", ["reverseUsername", "usernameLenth"]) // 命名空间解构getters
...mapState(["msg"]), // 解构state状态
...mapState({ username: state => state.teststore.username, age: state => state.teststore.age }), // 解构子模块state状态
...mapGetters(["reverseUsername", "usernameLenth"]) // 解构getters
},
methods: {
changeCount(){
// this.$store.state.count++ // 直接修改不建议
// this.$store.commit("increment", 5); // 通过store.commit来提交给对应的函数
this.increment(5); // 解构之后直接执行函数
// this.$store.commit({ // 对象方式必须保证参数也是对象方式
// type:"increment",
// val:5
// })
},
changeAge() {
// this.$store.state.count++ // 直接修改不建议
this.$store.commit("incrementAge", 5); // 通过store.commit来提交给对应的函数
// this.$store.commit("teststore/incrementAge", 5); // 开启namespace模式,通过store.commit来提交给对应的函数
// this.$store.commit({ // 对象方式必须保证参数也是对象方式
// type:"increment",
// val:5
// })
},
// ...mapActions("teststore", { getData2: actions => actions.getData2 }), // 命名空间解构actions
// ...mapMutations("teststore", { incrementAge: "incrementAge" }) // 命名空间解构mutations
...mapActions({ getData: "getData", getData2: "getData2" }), // 解构actions
...mapMutations({ incrementAge: "incrementAge", increment:"increment" }) // 解构mutations
}
}
</script>
5、其他:
1)、v-for 与 v-if 不建议在一起使用,v-for 优先级高于 v-if,如果将两者放在一起,会执行v-for循环列表,v-if 去判断造成性能浪费,建议用计算属性解决
<template>
<div>
<ul>
<li v-for="item,index in listArr" :key="index">
{{item.name}} - {{item.name}}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list:[
{
id:1,
name:"PHP",
isShow:true,
},
{
id: 1,
name: "GO",
isShow: true,
},
{
id: 1,
name: "JAVA",
isShow: false,
}
]
}
},
computed: {
listArr(){
return this.list.filter(item => {
return item.isShow
})
}
}
}
</script>
2)、自定义指令:创建自己想要的指令,使用必须以v-为前缀,参考:https://www.jb51.net/article/257259.htm
<template>
<div>
<!-- 自定义指令 -->
<p><input type="text" name="aaa">非自定义指令</p>
<p><input type="text" name="bbb" v-focus>自定义指令</p>
</div>
</template>
<script>
export default {
data() {
return {
}
},
directives: { // 自定义指令
focus: { // v-focus
// el:指令绑定到的元素。这可以用于直接操作 DOM。
// binding:一个对象,包含以下 property:
// value:传递给指令的值。例如在 v- my - directive=“1 + 1” 中,值是 2。
// oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
// arg:传递给指令的参数(如果有的话)。例如在 v- my - directive: foo 中,参数是 “foo”。
// modifiers:一个包含修饰符的对象(如果有的话)。例如在 v- my - directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
// instance:使用该指令的组件实例。
// dir:指令的定义对象。
// vnode:代表绑定元素的底层 VNode。
// prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
mounted: (el, binding, vnode, prevNode) => el.focus()
}
}
}
</script>
<script setup>
// setup 模式指令 v-focus
// 注:在<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令,vFocus 即可以在模板中以 v-focus 的形式使用
const vFocus = {
mounted: (el) => el.focus()
}
</script>
// 全局的自定义指令
app.directive('focus', {
mounted: (el) => el.focus()
})
3)、$nextTick:回调函数延迟,在下一次dom更新之后调用
<template>
<div>
<!-- $nextTick -->
<p id="msg" ref="msg">消息1:{{ msg }}</p>
<p id="msg2">消息2:{{ msg2 }}</p>
<p><button type="button" @click="changeMsg">改变消息</button></p>
</div>
</template>
<script>
export default {
data() {
return {
msg:"哈哈哈",
msg2:"啦啦啦"
}
},
methods: {
changeMsg(){
// $nextTick:回调函数延迟,在下一次dom更新之后调用
// vue 是异步渲染框架,数据更新之后,dom不会立刻渲染
this.msg = "晚安啦,啊啊啊"
// 解决方式1:有点迟钝
// setTimeout(() => {
// this.msg2 = this.$refs.msg.innerHTML
// },200)
// 解决方式2:$nextTick会在dom渲染之后触发,用来获取最新的dom节点
// 使用场景1:在生命周期函数created中进行dom操作,一定还要放到$nextTick函数中执行
// 使用场景2:在数据变化后要执行某个操作,而这个操作需要变化后的dom时,这个操作需要放到$nextTick中
this.$nextTick(() => {
this.msg2 = this.$refs.msg.innerHTML
})
}
}
}
</script>
文明上网理性发言!
已完结!!!