续 vue <一>
组件化的核心思想:
最终整个程序会变成组件树
父子组件之间的通信:
父组件传递给子组件:通过 props 属性
子组件传递给父组件:通过 $emit 触发事件
父组件传递给子组件:通过 props 属性
Props
Props 有两种常见的用法
比如指定传入的 attribute 的类型 type
比如指定传入的 attribute 是否是必传的
比如指定没有传入时,attribute 的默认值
Prop 的大小写命名(camelCase vs kebab-case)
父组件
<template>
<div>
<ShowInfo name="lili" age="33" height="1.88"></ShowInfo>
<ShowInfo name="xiaoming" age="33" height="1.88"></ShowInfo>
<ShowInfo name="lihaia" :age="33" height="1.88"></ShowInfo>
</div>
</template>
<script>
import ShowInfo from "./ShowInfo.vue";
export default {
components: {
ShowInfo
}
};
</script>
<style scoped></style>
<template>
<div>
<!-- {{ }}:能够访问data 或者 props 中的数据 -->
<h2>姓名:{{ name }}</h2>
<h2>身高:{{ height }}</h2>
<h2>年龄:{{ age }}</h2>
</div>
</template>
<script>
export default {
// 接收父组件传递过来的数据
// 1. 数组语法
props: ["name", "age", "height"],
// 2. 对象语法
props: {
name: {
type: String,
default:"lili",
},
height:{
type:Number,
default:2
},
age: {
type: Number,
default: 44
// 默认值和必传选一个
},
friend: {
type: Object,
default: () => ({
name: "james",
}),
},
// 也可以这么写
hobbies: {
type: Object,
default() {
return {
name: "james",
};
},
},
hobbies: {
type: Array,
default: () => ["篮球", "数学"],
},
}
};
</script>
<style scoped></style>
inheritAttrs:false
v-bind="$attrs.class"
一般都是子组件中触发了某个事件,需要在父组件中进行响应
或者子组件有一些内容想要传递给父组件
步骤
<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
<button @click="btnClick(15)">+15</button>
<button @click="btnClick(20)">+20</button>
</div>
</template>
<script>
export default {
//告诉别人自己发出去了这个事件
//数组语法
emits:["add"],
methods: {
btnClick(count) {
console.log("btnClick", count);
// 子组件发出去一个自定义事件
// 第一个参数是自定义事件的名称
// 第二个参数是传递的参数
this.$emit('add',count)
}
}
};
</script>
<style scoped>
.add{
display: flex;
}
button{
margin: 20px 10px;
}</style>
<template>
<div class="app">
<h1>当前计数:{{ counter }}</h1>
<Add @add="addClick"></Add>
<Sub @sub="subClick"></Sub>
</div>
</template>
<script>
import Add from "./Add.vue";
import Sub from "./Sub.vue";
export default {
data() {
return {
counter:0
}
},
components: {
Add,
Sub,
},
methods: {
addClick(count) {
this.counter+=count
},
subClick(count) {
this.counter+=count
}
}
};
</script>
<style scoped></style>
自定义事件的参数和验证:声明自己发出去了事件
<template>
<div class="app">
<!-- tab-control -->
<TabControl :titles="['ddd', '1ddd', 'eee']" @tab-item-click="tabItemClick(index)"></TabControl>
<!-- 展示内容 -->
<h1>{{ pageContents[currentIndex] }}</h1>
</div>
</template>
<script>
import TabControl from "./TabControl.vue";
export default {
components: {
TabControl,
},
data() {
return {
pageContents: ["衣服列表", "鞋子列表", "裤子列表"],
currentIndex: 0,
};
},
methods: {
tabItemClick(index) {
this.currentIndex = index;
},
},
};
</script>
<style scoped></style>
<template>
<div class="tab-control">
<template v-for="(item, index) in titles" :key="item">
<div :class="{active:currentIndex===index}" @click="itemClick(index)" class="item"><span>{{ item }}</span></div>
</template>
</div>
</template>
<script>
export default {
emits:['tabItemClick'],
props: {
titles: {
type: Array,
default: () => [],
},
},
data() {
return {
currentIndex: 0,
};
},
methods: {
itemClick(index) {
this.currentIndex = index;
this.$emit('tabItemClick',index)
},
},
};
</script>
<style scoped>
.tab-control {
display: flex;
height: 44px;
line-height: 44px;
text-align: center;
}
.item {
flex: 1;
}
.active{
color: brown;
font-weight: 900;
}
.active span{
color: brown;
}
</style>
在开发中,会经常封装一个个可复用的组件:
插槽:让使用者决定里面使用什么,展示什么内容
定义插槽 slot :
<slot><div>哈哈哈哈</div></slot>
使用 slot
Vue 中将 元素作为承载分发内容的出口
在封装组件中,使用特殊的元素就可以为封装组件开启一个插槽
该插槽插入什么内容取决于父组件如何使用
<!-- 1. 内容是一个按钮 -->
<ShowMessage title="zu件" message="chacao">
<button>我是按钮元素!!!</button>
</ShowMessage>
<!-- 2. 内容是一个链接 -->
<ShowMessage>
<a href="http://baidu.com">我是超链接!!!</a>
</ShowMessage>
<!-- 2. 内容是一张图片 -->
<ShowMessage>
<img src="http://" alt="fg">
</ShowMessage>
<ShowMessage></ShowMessage>
默认内容
<div class="nav-bar">
<div class="left"><slot name="left">left</slot></div>
<div class="center"><slot name="center">center</slot></div>
<div class="right"><slot name="right">right</slot></div>
</div>
<NavBar>
<!-- 需要用template进行包裹 -->
<template v-slot:left>
<button>hh</button>
</template>
<template v-slot:center>
<button>hh</button>
</template>
<template v-slot:right>
<button>hh</button>
</template>
</NavBar>
动态插槽名
插槽的值不希望写死,动态的
<NavBar>
<template v-slot:[position]>
<span>哈哈哈哈</span>
</template>
</NavBar>
<div class="tab-control">
<template v-for="(item, index) in titles" :key="item">
<div :class="{ active: currentIndex === index }" @click="itemClick(index)" class="item">
<!-- 把item传给slot -->
<slot :item="item" :name="jja"
><span>{{ item }}</span></slot
>
</div>
</template>
</div>
<TabControl :titles="['ddd', '1ddd', 'eee']" @tab-item-click="tabItemClick">
<template v-slot:default="props">
<button>{{ props.item }}</button>
</template>
</TabControl>
provide/inject 用于非父子之间共享数据
比如有一些深度嵌套的组件,子组件想要获取父组件的部分内
容
在这种情况下,如果仍然将props沿着组件链逐级传递下
去,就会非常的麻烦
对于这种情况下,可以使用 Provide 和 Inject :
提供者
应用于深度嵌套的组件,子组件需要使用父组件的一部分内容
使用
在父组件中添加属性provide{}
在子组件中添加属性inject:[]
生命周期:创建–》挂载–》更新–》卸载
组件的生命周期函数告诉我们目前组件正在哪一个过程
生命周期函数
生命周期的流程
beforecreate
created(发送网络请求、事件监听、this.$watch)
beforeMount
mounted
beforeUpdate
update
beforeunmount
unmounted(回收操作:取消事件监听)
export default {
// 演示生命周期函数
// 1. 组件被创建之前
beforeCreate() {
console.log(" beforeCreate");
},
// 2. 组件被创建完成
created() {
console.log("created");
console.log("组件被创建完成");
console.log("1. 发送网络请求,请求数据");
console.log("2. 监听eventbus事件");
console.log("3. 监听watch数据");
},
// 3. 组件template准备被挂载
beforeMount() {
console.log("beforemount");
},
// 4. 组件template被挂载:虚拟dom-》真实dom
mounted() {
console.log("mounted");
console.log("获取dom");
console.log("使用dom");
},
// 5. 数据发生改变
// 5.1 准备更新dom
beforeUpdate() {
console.log("beforeUpdate");
},
// 5.2更新dom
update() {
console.log("update");
},
// 6. 准备卸载VNode->DOM元素
// 6.1卸载之前
beforeUnmount() {
console.log("beforeUnmount");
},
// 6.2DOM元素被卸载完成
unmounted() {
console.log("卸载组件unmount");
}
}
想直接获取到元素对象或者子组件实例
要获取元素
<template>
<div class="app">
<h1>title:happiness is the most important thing!</h1>
<h2 ref="message">{{ message }}</h2>
<button ref="btn" @click="changeMessage">change message</button>
</div>
<Banner ref="banner"></Banner>
</template>
<script>
import Banner from "./Banner.vue";
export default {
components: {
Banner,
},
data() {
return {
message: "哈哈哈哈",
};
},
methods: {
changeMessage() {
this.message = "hehehheehehehheh";
// 1.获取元素
console.log(this.$refs.message);
console.log(this.$refs.btn);
// 2. 获取组件实例
console.log(this.$refs.banner);
// 3. 在父组件中可以直接调用子组件的方法
// this.$refs.banner.bannerClick()
// 4. 获取子组件里面的元素:开发中推荐一个组件只有一个根
console.log(this.$refs.banner.$el);
// 5. 组件实例中有两个属性(少用)
this.$parent//获取父组件
this.$root//获取根组件
},
},
};
</script>
<style scoped></style>
<template>
<div class="home">
<h1>home</h1>
name:{{ name }}
<button @click="homeBtnClick()">btn</button>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default:'hahh'
}
},
methods: {
homeBtnClick() {
this.$emit('homeClick','home')
}
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<div class="app">
<h2>app</h2>
<template v-for="(item,index) in tabs" :key="item">
<button :class="{active:currentTab === item}" @click="itemClick(item)">{{ item }}</button>
</template>
<Component @homeBtnClick="handleClick" name='lili' :is="currentTab"></Component>
</div>
</template>
<script>
import About from "./views/About.vue";
import Category from "./views/Category.vue";
import Home from './views/Home.vue';
export default {
data() {
return {
tabs: ['home', 'about', 'category'],
currentTab:'home'
}
},
methods: {
itemClick(item) {
this.currentTab=item
},
handleClick(payload) {
console.log(payload);
}
},
components: {
About,
Category,
Home,
},
};
</script>
<style lang="less" scoped></style>
会将当前的组件缓存,保存里面的数据,不会将其销毁
include 属性:只缓存这两个属性,属性需要在组件定义时添加name属性
exclude 属性:排除
max属性:最多缓存几个
对于 keep-alive 组件,监听有没有进行切换
<template>
<div class="home">
<h2>home</h2>
</div>
</template>
<script>
export default {
name: 'home',
activated() {
console.log("进入活跃状态!");
},
deactivated() {
console.log("离开活跃状态~");
}
}
</script>
<style lang="less" scoped>
</style>
<script>
import { defineAsyncComponent } from "vue";
const AsyncAbout = defineAsyncComponent(()=>import('./views/About.vue'))
export default {
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home",
};
},
components: {
About:AsyncAbout,
},
};
</script>
<template>
<div>counter:{{ modelValue }}</div>
<button @click="changeCounter">changeCounter</button>
</template>
<script>
export default {
props: {
modelValue: {
type: Number,
default:8
}
},
emits: ['update:modelValue'],
methods: {
changeCounter() {
this.$emit("update:modelValue",10000)
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app">
<!-- 1. input v-model -->
<!-- <input type="text" v-model="message" /> -->
<!-- 等价于 -->
<!-- <input :value="message" @input="message = $event.target.value" /> -->
<!-- 2. 组件的 model -->
<!-- <Counter v-model="appCounter"></Counter> -->
<!-- 等价于 -->
<Counter :modelValue="appCounter" @update:modelValue="appCounter = $event"></Counter>
</div>
</template>
<script>
import Counter from "./Counter.vue";
export default {
data() {
return {
message: "hello",
appCounter:100
};
},
components: {
Counter,
},
};
</script>
<style scoped></style>
组件和组件间可能会有相同的代码逻辑,希望对相同的代码逻辑进行抽取
创建文件夹 mixins
export default {
data() {
return {
message: "hello world",
};
},
};
组件中使用
export default {
mixins: [messageMixin]
}
都返回 data 对象,会进行合并,属性名冲突保留组件自身数据
生命周期会合并
手动混入:在子组件中添加 mixins:[messageMixin]
全局混入:app.mixin({方法})
vue3 中几乎不用
<template>
<div class="home">
<h2>home:{{ message }}</h2>
<button @click="changeMessage">changeMessage</button>
<h2>account:{{ account.username }}</h2>
<h2>account:{{ account.password }}</h2>
<button @click="changeAccount">changeAccount</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
// 1. 定义普通的数据: 可以正常使用
// 缺点:数据不是响应式
const message = "hello lili";
// 2. 定义响应式数据
// 2.1 reactive函数:定义复杂类型的数据
const account = reactive({
username: "lili",
password: 343434,
});
const changeMessage = () => {
message = "lili nihao";
};
function changeAccount() {
account.username='konbe'
}
return {
message,
account,
changeMessage,
changeAccount
};
},
};
</script>
<style lang="less" scoped></style>
// 2.2 ref函数 :定义简单类型的数据(也可以定义复杂类型的数据)
// 返回的是 ref 对象
const counter = ref(0)
function increment() {
counter.value++
}
reactive
<form action="">
账号:<input type="text" v-model="account.username" />
密码:<input type="password" v-model="account.password" /></form>
ref
单向数据流
react 的使用是非常灵活的,但有一个重要的原则:任何一个组件都应该像纯函数一样,不能修改传入的 props
引入 readonly,会返回原始对象的只读代理(依然是一个 Proxy,这是一个 proxy 的 set 方法被劫持),强制函数规定子组件不能进行修改
参数
setup 中子组件传递给父组件数据,符合单项数据流
<template>
<div>showinfo:{{ info }}</div>
<button @click="showInfoBtn">showinfo</button>
</template>
<script>
export default {
props: {
info: {
type: Object,
default: () => ({}),
},
},
emits: ["showInfoBtn"],
setup(props, context) {
function showInfoBtn() {
context.emit("changeInfo", "koebb");
}
return {
showInfoBtn,
};
},
};
</script>
<style scoped></style>
<template>
<div>hah</div>
<ShowInfo :info="info" @changeInfo="changeName"></ShowInfo>
</template>
<script>
import ShowInfo from './ShowInfo.vue';
import { reactive } from 'vue';
export default {
components: {
ShowInfo
},
setup() {
// 本地定义多个数据,需要传递给子组件
const info = reactive({
name: "lili",
age:22
})
function changeName(payload) {
info.name=payload
}
return {
info,changeName
}
}
}
</script>
<style scoped>
</style>
const roInfo = readonly(info)
<template>
<div>hah</div>
<h1>{{ name }}</h1>
<h1>{{ age }}</h1>
<h2>{{ height }}</h2>
<h3>{{ info }}</h3>
</template>
<script>
import { reactive, toRefs, toRef } from "vue";
export default {
setup() {
const info = reactive({
name: "why",
age: 22,
height: 4.44,
});
const { name, age } = toRefs(info);
const { height } = toRef(info, 'height');
console.log(height);
return {
info,
name,
age,
height,
};
},
};
</script>
<style scoped></style>
当某些属性是依赖其他状态时,可以使用计算属性来处理
直接在 setup 函数里面使用 computed 方法
<template>
<div>hah</div>
{{ fullName }}
<button @click="setFullName">setFullName</button>
</template>
<script>
import { reactive } from "vue";
import { computed } from "vue";
export default {
setup() {
const names = reactive({
firstName: "kobe",
lastName: "ddd",
});
// 一般只使用getter
// const fullName = computed(() =>{
// return names.firstName+ " "+ names.lastName
// })
// 如果 getter 和 setter都使用:传入一个对象
const fullName = computed({
set: function (newValue) {
const tempNames = newValue.split(" ");
names.firstName = tempNames[0];
names.lastName = tempNames[1];
},
get: function () {
return names.firstName + " " + names.lastName;
},
});
function setFullName() {
fullName.value = "lili hha";
}
return {
fullName,
setFullName,
};
},
};
</script>
<style scoped></style>
<template>
<div>
<h2 ref="titleRef">woshibiaoti</h2>
<button ref="btnRef">woshianniu</button>
<ShowInfo ref="showInfoRef"></ShowInfo>
</div>
</template>
<script>
import { onMounted, ref } from 'vue';
import ShowInfo from '../03_setup其他函数/ShowInfo.vue';
export default {
components: {
ShowInfo
},
setup() {
const titleRef = ref()
const btnRef = ref()
const showInfoRef = ref()
onMounted(() => {
console.log(titleRef.value);
console.log(btnRef.value);
// .value 才能拿到实例
console.log(showInfoRef.value);
showInfoRef.value.showInfoFoo()
})
return {
titleRef,btnRef,showInfoRef
}
}
}
</script>
<style scoped>
</style>
<template>
<div>hah</div>
</template>
<script>
import { onMounted } from 'vue';
export default {
setup() {
// 是一个函数的调用,传入一个回调函数
onMounted(() => {
console.log("onMounted");
})
}
}
</script>
<style scoped>
</style>
<template>
<div>hah</div>
<ShowInfo></ShowInfo>
</template>
<script>
import { provide } from 'vue';
import ShowInfo from './ShowInfo.vue'
export default {
components:{ShowInfo},
setup() {
// 响应式数据
const name = ref('lili')
provide("name", name)
provide("age",99)
}
}
</script>
<style scoped>
</style>
<template>
<div>hah</div>
<h1>{{ name }}</h1>
</template>
<script>
import { inject } from 'vue';
export default {
setup() {
const name = inject("name")
return {
name
}
}
}
</script>
<style scoped>
</style>
<template>
<div>hah</div>
<h2>{{ message }}</h2>
<button @click="message = 'hahhh'">btnchange</button>
<button @click="info.name = 'konbe'">btnchange</button>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
setup() {
// 1. 定义数据
const message = ref("hello world");
const info = reactive({
name: "lili",
age: 18,
});
// 2. 侦听数据的变化
watch(message, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
// 同一个引用,同一个东西value
// 默认对reactive对象有进行深度监听
watch(
info,
(newValue, oldValue) => {
console.log(newValue, oldValue);
console.log(newValue === oldValue);
},
{
immediate: true,
}
);
// 3. 监听reactive 数据变化后,获取普通对象
watch(
() => ({ ...info }),
(newValue, oldValue) => {
console.log(newValue, oldValue);
}, {
immediate: true,
deep:true
}
);
return {
message,
info,
};
},
};
</script>
<style scoped></style>
<template>
<div>hah</div>
<h2>{{ counter }}</h2>
</template>
<script>
import { ref, watchEffect } from "vue";
export default {
setup() {
const counter = ref(0);
const age = ref(33);
// 传入的函数会被直接执行
// 会自动收集依赖
const stopWatch = watchEffect(() => {
console.log(counter.value, age.value);
// 希望停止监听
if (counter.value >= 10) {
stopWatch();
}
});
return {
counter,
age,
};
},
};
</script>
<style scoped></style>
直接传入回调函数,函数默认会直接被执行,不需要在前面绑定
在执行的过程中,会自动收集依赖(依赖哪些响应式数据)
没更新一次,会执行一次该函数
与 watch 区别
watch必须指定数据源,watcheffect自动指定依赖项
watch 监听到改变的时候可以拿到改变前后的 value
watcheffect 默认直接执行一次,watch 需要设置 immediate 属性才能立即执行
import {ref} from'vue'
export default function useCounter() {
const counter = ref(10)
const increment=(()=>{
counter.value++
})
const decrement=(()=>{
counter.value--
})
return {
increment,decrement,counter
}
}
<template>
<div>Homecounter:{{counter }}</div>
<button @click="increment">add</button>
<button @click="decrement">sub</button>
</template>
<script>
import { ref } from 'vue';
import useCounter from '../hooks/useCounter'
export default {
setup() {
return {...useCounter()}
}
}
</script>
<style scoped>
</style>
import { ref,watch } from "vue"
export default function changeTitle(titleValue) {
// document.title = title
//定义ref的引用数据
const title = ref(titleValue)
//监听title的改变
watch(title,(newValue)=>{
document.title = newValue
},{
immediate:true
})
//返回一个title,对应组件中修改value,在setup中调用一次
return title
}
在 script 里面加上 setup
导入组件就可以直接使用,不需要注册
顶层编写的代码直接暴露给 template
定义 **props:**语法糖提供了一个定义在作用域中的函数 defineProps
a. const props=defineProps({})
定义 emits:defineEmits
a. const emits=defineEmits([“showInfo”])
暴露属性:defineExpose
好处
模拟网络请求
const highScore = ref({})
highScore.value = res.data
组件化开发
定义 props、动态传值
json 数据的引入
样式:自己算宽度
computed 属性的应用,数组变成字符串==》join
后端路由:
早期的网站开发整个 HTML 页面是由服务器来渲染的
一个页面有自己对应的网址, 也就是 URL,后端维护映射关系
URL 会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个 Controller进行处理
Controller 进行各种处理, 最终生成 HTML 或者数据, 返回给前端.
当页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端
这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于 SEO 的优化
后端路由的缺点
前端渲染的理解
每次请求涉及到的静态资源都会从静态资源服务器获取,这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染
需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件
同时可以看到,和之前的后端路由不同,这时后端只是负责提供 API 了
前后端分离阶段
URL 的 hash也就是锚点**(#**), 本质上是改变 window.location 的 href 属性
可以通过直接赋值 location.hash 来改变 href, 但是页面不发生刷新
hash 的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个 #,显得不像一个真实的路径
目前前端流行的三大框架, 都有自己的路由实现:
Vue Router 是 Vue.js 的官方路由
vue-router 是基于路由和组件的
URL
, 将路径和组件映射起来安装 Vue Router:npm install vue-router
步骤
使用
import { createRouter, createWebHashHistory,createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const router = createRouter({
// 指定采用的模式:hash
history: createWebHashHistory(),
//history:createWebHistory(),
// 映射关系
routes: [
{path:"/",redirect:"/home"},
{path: "/home", component: Home },
{path:"/about",component:About},
{path:"/about",component:()=>import("../Views/Home.vue")}
]
})
export default router
<template>
<div class="app">
<h1>app content</h1>
<div class="nav">
<router-link to="/home" replace active-class="active">首页</router-link>//replace不会记录历史,不能返回上一步
<router-link to="/about">关于</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script setup>
</script>
<style scoped>
/* 自定义属性名并修改 */
.active{color:red}
/* 使用系统默认,不需要添加*/
.router-link-active{}
</style>
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
const Home = ()=>import(/*webpackChunkName:'home'*/"../views/Home.vue")
{path:"/about",component:()=>import("../Views/Home.vue")}
{ path: "/user/:id", component: () => import("../views/User.vue") }
需要将给定匹配模式的路由映射到同一个组件
用户界面拿到值
在模板中: <h2>user{{ $route.params.id }}</h2>
在代码中:
options api:this.$route.params.id
composition api :
import { useRoute } from 'vue-router';
const route = useRoute()
console.log(route.params.id);
每次都需要对应更新
import { useRoute, onBeforeRouteUpdate } from "vue-router";
// onBeforeRouteUpdate(() => {
// const route = useRoute();
// console.log(route.params.id);
// });
//第一次进入
const route = useRoute();
console.log(route.params.id);
onBeforeRouteUpdate((to, from) => {
console.log("from", from.params.id);
console.log("to", to.params.id);
});
对于哪些没有匹配到的路由,通常会匹配到固定的某个页面
比如NotFound的错误页面中,可编写一个动态路由用于匹配所有的页面
{ path: "/:pathMatch(.*)", component: () => import("../views/NotFound.vue") }
可以通过 $route.params.pathMatch
获取到传入的参数: <h2>not found:{{ $route.params.pathMatch }}</h2>
对路径进行解析:path: “/:pathMatch(.*)*”
<template>
<div class="app">
<h1>app content</h1>
<div class="nav">
<router-link to="/home" replace active-class="active">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link to="/user/999">用户</router-link>
<router-link to="/user/559">用户</router-link>
</div>
<router-view></router-view>
</div>
<!-- 其他元素跳转 -->
<button @click="btnClick">按钮</button>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
const btnClick1 = () => {
// 跳转到首页
router.push("./home");
};
const btnClick = () => {
// 跳转到首页
router.push({
// name: "./home",//没有重定向,不推荐
path: "/home",
// 对象形式还可以加参数,对应添加了字段
query: {
name: "lili",
age: 88,
},
});
};
</script>
import { useRouter } from "vue-router";
const router = useRouter();
const btnClick = () => {
router.back(); //返回
// router.forward(); //向前
// router.go(19); //向前多少步
};
{
path: "/home",
component: Home,
name: "home",
children: [
{
path: "/home",
redirect: "/home/recommend",
},
{
path: "recommend",
component: () => import("../views/HomeRecommend.vue"),
},
],
},
// 动态路由
let isAdmin = true;
if (isAdmin) {
// 动态添加一级路由
router.addRoute({
path: "/admin",
component: () => import("../views/Admin.vue"),
});
// 动态添加二级路由
// 添加 vip 页面
router.addRoute("home", {
path: "vip",
component: () => import("../views/HomeVip.vue"),
});
}
// 路由导航守卫
// beforeEach:进行任何的路由跳转之前,传入的beforeEach中的函数都会被回调
router.beforeEach((to, from) => {
console.log(to, from);
if (to.path !== "/login") {
return "/login";
}
});
逻辑==》js 代码
路由导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航
全局的前置守卫beforeEach是在导航触发时会被回调的:
它有两个参数
它有返回值
可选的第三个参数:next(不推荐使用)
需求
router.beforeEach((to, from) => {
const token = localStorage.getItem('token')
if (to.path === '/order' && !token) {
return '/login'
}
});
router.beforeEach((to, from) => {
const token = localStorage.getItem('token')
if (to.path === '/order' && !token) {
return '/login'
}
});
<template>
<div class="home">
<h2>home</h2>
<button @click="logoutLogin">退出登陆</button>
<h1>query:{{ $route.query }}</h1>
<router-view></router-view>
</div>
</template>
<script setup>
function logoutLogin() {
localStorage.removeItem('token')
}
</script>
<style lang="less" scoped>
</style>
在 Vue 的模板语法中有各种各样的指令:v-show、v-for、v-model 等等,除了使用这些指令之外,Vue 也允许来 自定义自己的指令
注意:
自定义指令分为两种
案例:当某个元素挂载完成后可以自定获取焦点
import { onMounted, ref } from 'vue';
export default function useInput() {
const inputRef = ref()
onMounted(() => {
inputRef.value?.focus()
})
return {inputRef}
}
实现方式二:自定义一个 v-focus 的局部指令
<script>
export default {
directives: {
focus: {
// 生命周期函数(自定义指令)
mounted(el) {
// console.log("v-focus 运用的元素被挂载了", el);
el?.focus()
},
},
},
};
</script>
// 方式二:自定义指令(局部指令)
const vFocus = {
// 生命周期函数(自定义指令)
mounted(el) {
// console.log("v-focus 运用的元素被挂载了", el);
el?.focus();
},
};
实现方式三:自定义一个 v-focus 的全局指令
export default function directiveFocus(app) {
app.directive("focus", {
mounted(el) {
// console.log("v-focus 运用的元素被挂载了", el);
el?.focus();
},
});
}
import directiveFocus from './focus'
export default function useDirectives(app) {
directiveFocus(app)
}
import { createApp } from "vue";
import App from "./01_自定义指令/App.vue";
import useDirectives from "./01_自定义指令/directives/index";
const app = createApp(App);
useDirectives(app);
app.mount("#app");
<template>
<div class="app">
<button @click="counter++">+1</button>
<button @click="showTitle = !showTitle">切换</button>
<h2 v-if="showTitle" v-lili>当前计数:{{ counter }}</h2>
</div>
</template>
<script setup>
import { ref } from "vue";
const counter = ref(0);
const showTitle = ref(true);
const vLili = {
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeUnmount() {
console.log("beforeUnmount");
},
umounted() {
console.log("umounted");
},
};
</script>
<style scoped></style>
<input type="text">
<!-- v-指令名称:参数(argument).修饰符="value" -->
<Counter v-model:title.lazy="message"></Counter>
export default function directiveUnit(app) {
app.directive("unit", {
mounted(el, bindings) {
const defaultText = el.textContent;
let unit = bindings.value;
if (!unit) {
unit = "$";
}
el.textContent = unit + defaultText;
},
});
}
<template>
<div class="app">
<h2 v-lili>hhhhhh</h2>
<!-- v-指令名称:参数(argument).修饰符="value" -->
<h2 v-lili:name.abc.cba="message">hhhhhh</h2>
<!-- 价格拼接单位符号 -->
<h2 v-unit>{{ 111}}</h2>
</div>
</template>
<script setup>
import { ref } from "vue";
const message = '你好呀李思达'
const vLili = {
mounted(el,bindings) {
console.log(el,bindings);
el.textContent = bindings.value
}
};
</script>
<style scoped></style>
import dayjs from "dayjs";
export default function directiveFtime(app) {
app.directive("ftime", {
mounted(el, bindings) {
// 1. 获取时间,并且转化成毫秒
let timestamp = Number(el.textContent);;
if (timestamp.length === 10) {
timestamp *= 1000;
}
// 2. 获取传入的参数
let value = bindings.value;
if (!value) {
value = "YYYY-MM-DD HH:mm:ss";
}
// 3. 对时间进行格式化
const formatTime = dayjs(timestamp).format(value);
el.textContent = formatTime;
},
});
}
<template>
<div class="app">
<h1 v-ftime="'YYYY-MM-DD'">{{ timestamp}}</h1>
</div>
</template>
<script setup>
const timestamp = 1934458935
</script>
<style scoped>
</style>
<template>
<div class="app">
<div class="hello">
<h1 class="content">
<!-- <teleport to="body">
<hello-world />
</teleport> -->
<teleport to="#abc">
<hello-world />
</teleport>
</h1>
</div>
<div class="content">
<teleport to="#abc">
<h2>hhhhhh</h2>
</teleport>
</div>
</div>
</template>
<script setup>
import HelloWorld from "./HelloWorld.vue";
</script>
<style scoped></style>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<style>
#abc{
background-color: antiquewhite;
}
</style>
</head>
<body>
<div id="app"></div>
<div id="abc"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
<template>
<div class="app">
<Suspense>
<template #default>
<async-home />
</template>
<template #fallback>
<h2>loading</h2>
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue"));
</script>
<style scoped></style>
// app.use(router)
// app.use(store)
// app.use(pinia)
import { createApp } from "vue";
import App from "./01_自定义指令/App.vue";
// import App from './02_内置组件补充/App.vue'
// import App from "./03_安装插件/App.vue";
import directives from "./01_自定义指令/directives/index";
// const app = createApp(App);
// directives(app)
// app.mount("#app");
createApp(App).use(directives).mount("#app");
// 安装插件
// 方式一:传入对象的情况
// app.use(router) 拿到对象去执行 install 函数
// app.use(store)
// app.use(pinia)
app.use({
install: function (app) {
console.log("传入对象的 install 被执行", app);
// app.directive()
// app.component()
},
});
// function use(obj) {
// obj.install(app)
// }
// use({
// install:function(){}
// })
// 方式二:传入函数的情况
app.use(function (app) {
console.log("传入函数的 install 被执行", app);
});
import directiveFocus from "./focus";
import directiveUnit from "./unit";
import directiveFtime from "./ftime";
// export default function useDirectives(app) {
// directiveFocus(app)
// directiveUnit(app)
// directiveFtime(app)
// }
export default function directives(app) {
directiveFocus(app);
directiveUnit(app);
directiveFtime(app);
}
npm i vue-router
接受三个参数
createVNode("div",{class:"abc"},[createVNode("h2",null,[]),createVNode("p",{},"我是内容哈哈哈哈")])
注意事项
h 函数可以在两个地方使用
options api
<script>
import { h } from "vue";
export default {
render() {
return h("div", { class: "app" }, [h("h2", { class: "title" }, "我是标题"), h("p", { class: "content" }, "我是内容")]);
},
};
</script>
<style scoped></style>
<script>
import { h } from "vue";
import Home from './Home.vue'
export default {
data() {
return {
counter:0
}
},
render() {
return h("div", { class: "app" }, [
h("h2",null,`当前计数:${this.counter}`),
h("button",{onClick:this.increment},"+1"),
h("button", { onClick: this.decrement }, "-1"),
h(Home)
]);
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
};
</script>
<style scoped></style>
<template>
<render />
</template>
<!-- <script>
import { h, ref } from "vue";
import Home from './Home.vue'
export default {
setup() {
const counter = ref(0)
const increment = () => {
counter.value++
}
const decrement = () => {
counter.value--
}
return () => {
h("div", { class: "app" }, [
h("h2",null,`当前计数:${counter.value}`),
h("button",{onClick:increment},"+1"),
h("button", { onClick:decrement }, "-1"),
h(Home)
])
}
}
};
</script> -->
<script setup>
import { h, ref } from "vue";
import Home from "./Home.vue";
const counter = ref(0);
const increment = () => {
counter.value++;
};
const decrement = () => {
counter.value--;
};
const render = () => {
h("div", { class: "app" }, [h("h2", null, `当前计数:${counter.value}`), h("button", { onClick: increment }, "+1"), h("button", { onClick: decrement }, "-1"), h(Home)]);
};
</script>
<style scoped></style>
<script lang="jsx">
import About from './About.vue'
export default {
data() {
return {
counter: 0,
};
},
methods: {
increment() {
this.counter++;
},
decrement() {
this.counter--;
},
},
render() {
return (
<div class='app'>
<h2>counter:{this.counter}</h2>
<button onClick={(e) => this.decrement()}>-1</button>
<button onClick={(e) => this.increment()}>+1</button>
<About/>
</div>
);
},
};
</script>
<style lang="less" scoped></style>
<!-- <script lang="jsx">
import { ref } from "vue";
import About from './About.vue'
export default {
setup() {
const counter = ref(0);
const increment = () => {
counter.value++;
};
const decrement = () => {
counter.value--;
};
return () => (
<div class='app'>
<h2>counter:{counter.value}</h2>
<button onClick={decrement}>-1</button>
<button onClick={increment}>+1</button>
<About />
</div>
);
},
};
</script> -->
<template>
<jsx />
</template>
<script setup lang="jsx">
import { ref } from "vue";
import About from "./About.vue";
const counter = ref(0);
const increment = () => {
counter.value++;
};
const decrement = () => {
counter.value--;
};
const jsx = () => (
<div class='app'>
<h2>counter:{counter.value}</h2>
<button onClick={decrement}>-1</button>
<button onClick={increment}>+1</button>
<About />
</div>
);
</script>
<style lang="less" scoped></style>
npm install @vue/babel-plugin-jsx -D
npm install @vitejs/plugin-vue-jsx -D
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import jsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
jsx()
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})