下载安装node.js
创建项目目录,在里面npm init vite@latest,可以直接选框架语言快速生成,也可以选完框架后选择自定义,按提示安装相关vue组件,最后进去npm install安装依赖(根据package.json安装),npm run dev运行环境,vscode安装volar
目录中的 index.html 是项目的入口;package.json 是管理项目依赖和配置的文件;public 目录放置静态资源,比如 logo 等图片;vite.config.js 就是和 Vite 相关所有工程化的配置;src 就是工作的重点,我们大部分的代码都会在 src 目录下管理和书写,后面我们也会在 src 目录下细化项目规范。
访问提示里给的本地路径,可以看到vite的界面
Vue 负责核心,Vuex 负责管理数据,vue-router 负责管理路由
npm install vuex@next
npm install vue-router@next
如果在npm i的时候使用了--save,则该包会被安装到所有的环境
如果--save-dev,则只保存在dev环境
package.json里面,各个版本号的前面,^代表大版本号不动,后两位找当前最新,~代表前两位不动,最后一位最新
vue3基于proxy实现了响应机制
语法
风格
把vue.app删光光后,里面就剩下这两块东西
一块放js,一块放html
另外需要注意的是,默认情况下app.vue是程序的入口,也就是这里的东西会出现在所有的程序中,后面开发时,除非你有这方面的需求,如固定的侧边栏导航栏,不然不要在这里写页面代码(除了路由)
<template>
这里放html
</template>
<script>
这里放js
</script>
vue有两种风格,分别是选项式(vue2)和组合式(vue3)
选项式
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
组合式,以下笔记都将由组合式编写(为什么网上教程全是选项式的啊,怒了
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
组合式的风格可以把一组相关的数据和方法放在一起,以便更好地管理
function useTodos() {
let title = ref("");
let todos = ref([{ title: "学习Vue", done: false }]);
function addTodo() {
todos.value.push({
title: title.value,
done: false,
});
title.value = "";
}
function clear() {
todos.value = todos.value.filter((v) => !v.done);
}
let active = computed(() => {
return todos.value.filter((v) => !v.done).length;
});
let all = computed(() => todos.value.length);
let allDone = computed({
get: function () {
return active.value === 0;
},
set: function (value) {
todos.value.forEach((todo) => {
todo.done = value;
});
},
});
return { title, todos, addTodo, clear, active, all, allDone };
}
这段代码定义了一个名为 useTodos 的函数,它返回一个对象,该对象包含了一些属性和方法,用于管理一个 todo 列表。
let title = ref("");: 定义了一个响应式数据 title,初始值为空字符串。
let todos = ref([{ title: "学习Vue", done: false }]);: 定义了一个响应式数据 todos,初始值是一个包含一个 todo 对象的数组,该对象有一个 title 属性和一个 done 属性。
function addTodo() { ... }: 定义了一个名为 addTodo 的方法,用于向 todos 数组中添加一个新的 todo 对象。该方法将新的 todo 对象 push 到 todos.value 中,并将 title.value 设置为空字符串。
function clear() { ... }: 定义了一个名为 clear 的方法,用于清除已完成的 todo 对象。该方法使用 filter 方法从 todos.value 数组中筛选出未完成的 todo 对象,然后将结果赋值给 todos.value。
let active = computed(() => { ... }): 定义了一个计算属性 active,它返回未完成的 todo 对象的数量。计算属性使用 todos.value 数组进行计算,并返回结果。
let all = computed(() => todos.value.length);: 定义了一个计算属性 all,它返回 todos.value 数组的长度。
let allDone = computed({ ... }): 定义了一个计算属性 allDone,它既可以读取也可以设置。当读取 allDone 时,它会判断 active.value 是否等于 0,并返回判断结果。当设置 allDone 时,它会遍历 todos.value 数组,并将每个 todo 对象的 done 属性设置为传入的值。
return { title, todos, addTodo, clear, active, all, allDone };: 返回一个包含 title、todos、addTodo、clear、active、all 和 allDone 属性的对象。这些属性和方法可以在组件中使用。
比如这样
<script>
import { useTodos } from './useTodos';
const { title, todos, addTodo, clear, active, all, allDone } = useTodos();
</script>
<template>
<div>
<h1>{{ title }}</h1>
<ul>
<li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
</ul>
<button @click="addTodo">Add Todo</button>
<button @click="clear">Clear Todos</button>
</div>
</template>
基础
值/属性绑定
文本插值,会根据变量的值动态更新,使用双大括号加载,被加载的值会被当做普通文本输出而不是html处理
<span>Message: {{ msg }}</span>
双大括号的写法不能用于html的属性,所以提供了另外一种写法
<div v-bind:id="dynamicId"></div>
或者简写成
<div :id="dynamicId"></div>
如果想一次性绑多个,可以这样写
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
//不指定具体的属性
<div v-bind="objectOfAttrs"></div>
//动态的属性名
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
当属性为真值或者true时,这个属性是显性的,如果属性值为false,那么这个属性将不被启用
js表达式
vue支持完整的js表达式,{{}}或者所有的v-指令中都能被识别
但是vue只支持能够返回值的语句,类似赋值语句或者判断语句都无法生效
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
ref,reactive
响应式的创建变量、对象
注意是ref,不是Ref,ref用于创建一个响应式变量,而Ref是ts中的ref调用后返回的类型
<script>
import {reactive,computed,toRefs} from 'vue'
interface DataProps {
count: number;
double: number;
increase: () => void;
}
const data: DataProps = reactive({
count: 0,
increase: () => {data.count++},
double: computed(() => data.count * 2)
})
//const refdata = toRefs(data)
</script>
<p>{{ data.count }}</p>
<p>{{ data.double }}</p>
<button @click="data.increase">clickhere</button>
//如果是用torefs转的响应式,increase要写成refdata.count.increase
示例:搜索框
<div class="content">
<input type="text" placeholder="搜索" ref="searchWord">
<span class="iconfont icon-fangdajing" @click="search(this.$refs.searchWord.value)"></span>
</div>
要注意的是,ref在函数中引用赋值时,需要指定访问xxx.value,但是从template传递过来时(包括从函数参数传过来时),可以直接访问
示例:翻页
<template>
<div class="project_main">
<div class="search_box"></div>
<div class="project_table_box">
<el-table :data="projectlist" style="width: 100%" :row-height="rowHeight">
<el-table-column fixed prop="project_name" label="项目名" width="150" />
<el-table-column prop="project_rank" label="重要等级" width="120" />
<el-table-column prop="project_owner_id" label="负责人" width="120" />
<el-table-column prop="project_create_time" label="创建时间" width="320" />
<el-table-column prop="project_last_mod" label="最后修改时间" width="600" />
</el-table>
</div>
<div class="pagination">
<div class="demo-pagination-block">
<div class="demonstration">每页显示数</div>
<el-pagination
v-model:current-page="page"
v-model:page-size="page_size"
:page-sizes="[10, 20, 30, 40, 50]"
:small="small"
:disabled="disabled"
:background="background"
layout="sizes, prev, pager, next"
:total="100"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { projectrequest } from '../network/projectrequest';
const projectlist = ref([]);
const page = ref(1);
const page_size = ref(10);
const rowHeight = 40;
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
onMounted(() => {
projectinfo(page.value, page_size.value)
});
function projectinfo(currentpage,currentpage_size){
projectrequest(currentpage, currentpage_size).then((res) => {
projectlist.value = res.data.data;
console.log(projectlist.value)
console.log(currentpage,currentpage_size)
});
}
function handleSizeChange(pagesize_now){
page_size.value = pagesize_now;
page.value = 1;
projectinfo(page.value, page_size.value)
};
function handleCurrentChange(page_now){
page.value = page_now
projectinfo(page.value, page_size.value)
}
</script>
计算属性
利用现有定义的值来计算出一个新的值,好处是计算过一次后就会缓存
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
v-if/show
v-i会根据值的真假来判断是否插入/移除该节点
<p v-if="seen">Now you see me</p>
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
示例
<template>
<div><button @click="onclickup" id="show">click here</button></div>
<div v-if!="flag">U.N is her?</div>
<div v-else>WOW!</div>
</template>
<script setup>
import { ref } from 'vue';
const flag = ref(false)
function onclickup() {
if (flag.value === false){
flag.value = true
}
else if(flag.value === true){
flag.value = false
}
}
</script>
v-show和v-if功能类似,都是控制节点是否展示,但是show是在样式添加css--display:none,隐藏后dom仍然存在,if则是增加删除对应的dom
v-on
监听dom事件,用于对事件的反应,可以用于函数的调用
<a v-on:click="counter += 1"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething">
<!--带参-->
<button @click="say('hello')">Say hello</button>
function say(message) {
alert(message)
}
v-for
遍历取值,绑定key属性可以让vue根据该属性所对应的值来绑定节点的身份,以方便其进行排序,如果没有可绑的可以用index
<template>
<--值only-->
<li v-for="item in agelist">{{ item.message }}</li>
<--值和索引-->
<li v-for="(item,index) in agelist">{{index}} : {{ item.message }}</li>
<--用第二个位置代表属性,用第三个位置代表索引-->
<li v-for="(value,key,index) in character" :key="key|index">{{ index }}:{{ key }}:{{ value }}</li>
<--范围内取值,从1开始-->
<span v-for="n in 10">{{ n }}</span>
</template>
<script setup>
import { ref } from 'vue';
const flag = ref(false)
const agelist = ref([{message:17},{message:20}])
const character = ref({
yukari : 17,
marisa : 20
})
</script>
<!-- 17
20
0 : 17
1 : 20
0:yukari:17
1:marisa:20
2:erin:9999
12345678910 -->
如果要同时使用v-for和v-if,不要放在同一级,if优先级比for更高,会影响代码功能
正确示范
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
v-model
表单绑定
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
//复选框的值放入列表,列表中的值为选项的value
<template>
<div>choice list:{{ checklist }}</div>
<form>
<input type="checkbox" v-model="checklist" id="zhangsan" value="zhangsan">
<label for="zhangsan">zhangsan</label>
<input type="checkbox" v-model="checklist" id="lisi" value="lisi">
<label for="lisi">lisi</label>
<input type="checkbox" v-model="checklist" id="wangwu" value="wangwu">
<label for="wangwu">wangwu</label>
</form>
</template>
<script>
const checklist = ref([])
</script>
//自定义值选择器
<select v-model="selected">
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
<div>Selected: {{ selected }}</div>
const options = ref([
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
])
修饰符
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
//自动转数字
<input v-model.number="age" />
//去除两端空格
<input v-model.trim="msg" />
watch
监听某个值的变化并对其进行操作
例:监听count的变化
<script setup>
import { ref, watch } from 'vue';
const count = ref(0)
watch(count,(newValue, oldValue)=>{
if(newValue >=3){
console.log("too much click",newValue)
}
}
)
function clickup(){
count.value++
}
</script>
<template>
<button @click="clickup">clickhere:{{ count }}</button>
</template>
如果有多个变化的值,可以从newvalue中取对应的值,值名可在控制台看到
v-slot
插槽绑定
假如某模板如下
<template>
<form class="validate-form-container">
<slot name="default"></slot>
<div class="submit-area" @click.prevent="submitForm">
<slot name="submit">//当没有绑定时会显示默认的提交按钮
<button type="submit" class="btn btn-primary">提交</button>
</slot>
</div>
</form>
</template>
现对其使用
<template>
<div class="login-page mx-auto p-3 w-330">
<h5 class="my-4 text-center">登录到xx</h5>
<validate-form @form-submit="onFormSubmit">
<div class="mb-3">
<label class="form-label">邮箱地址</label>
<validate-input //上面模板的代码块
:rules="emailRules" v-model="emailVal"
placeholder="请输入邮箱地址"
type="text"
ref="inputRef"
/>
</div>
<div class="mb-3">
<label class="form-label">密码</label>
<validate-input
type="password"
placeholder="请输入密码"
:rules="passwordRules"
v-model="passwordVal"
/>
</div>
<template #submit>//v-slot的简写,指定name="submit"
<button type="submit" class="btn btn-primary btn-block btn-large">登录</button>
</template>
</validate-form>
</div>
</template>
响应式
下面使用组合式表达
想创建一个变量,用ref来进行声明,其值通过var.value输出
当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。<script setup>
SFC可以避免我们手动在setup()中输出的繁琐
<template>
<div>count:<button @click="onclickup">{{ count }}</button></div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(111)
function onclickup() {
count.value++
}
</script>
直接输出count和count.value的区别
RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 111, _value: 111}dep: undefined__v_isRef: true__v_isShallow: false_rawValue: 111_value: 111value: (...)[[Prototype]]: Object
App.vue:9 111
在其他组件模板中使用ref的方式,无setup的情况下
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}
引用模板
<template>
<HelloWorld />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
如果需要父组件给子组件传递一些值,需要用到props
props传过来的值,不能在setup作为变量使用,但是可以在template里面使用
<template>
<p>title:{{ title }}</p>
<p>id:{{ paperid }}</p>
</template>
<script setup>
defineProps(['paperid','title'])
</script>
<template>
<HelloWorld v-for="paper in paperlist" :paperid="paper.id" :title="paper.title"/>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue';
const paperlist = ref([
{id: 1, title: 'hello on board'},
{id: 2, title: 'guide for new blood'},
{id: 3, title: '100 way to learn vue'}
])
</script>
更全能的写法
const props = defineProps({
isOpen: Boolean,
count: {
type: Number, //类型
default: 0 //默认值
},
title: {
type: String,
required: true //是否必须
}
});
如果想要使用复杂的类型,比如接口数组,得用PropType包裹
export interface Columntype{
id: number;
title: string;
avatar: string;
abstract: string;
}
const props = defineProps({
columnlist: {
type: Array as PropType<Columntype[]>,
required: true
}
})
子调用父的方法,可以用emit
https://zhuanlan.zhihu.com/p/581646269?utm_id=0
emit有点像订阅发布的意思,子组件在自己身上注册了一个方法,然后告诉父组件我这里要调用一个名为xxx的方法,父组件真实定义xxx后,再把xxx放入子组件注册这个方法的地方真实调用。也就是子组件负责注册,父组件负责定义和调用
<template>
<HelloWorld v-for="paper in paperlist" :paperid="paper.id" :title="paper.title" @printpaper="printpaper(paper.title)"/>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue';
function printpaper(papername) {
console.log("already printed paper ",papername)
}
</script>
<template>
<p>title:{{ title }}</p>
<p>id:{{ paperid }}</p>
<button @click="$emit('printpaper')">Print</button>
</template>
<script setup>
defineProps(['paperid','title'])
defineEmits(['printpaper'])
</script>
页面切换,通过点击按钮来切换对应加载的组件,使用component
<template>
<button v-for="(_,tab) in tabs" @click="currentpage = tab" :key="tab">{{ tab }}</button>
<component :is="tabs[currentpage]"></component>
</template>
<script setup>
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
import shop from './components/shop.vue'
const currentpage = ref()
</script>
创建一个应用
在默认文件里有个main.js
createApp的参数为根组件,在这里根组件为App
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
监听
监听变量的值,当发生变化时执行操作
如果想要同时监控多个值,把第一个参数换成数组
import { watch } from 'vue'
watch(data,()=>{
document.title = 'data updated'
})
如果想监控一个对象里面的值,需要把那个值函数化
const data: DataProps = reactive({
count: 0,
increase: () => {data.count++},
double: computed(() => data.count * 2)
})
watch(()=>data.count,()=>{ //不能直接data.count
document.title = 'data updated'
})
插槽
在子组件上提供一个插槽,以便于父组件传入的东西能够替代插槽里面的默认设置
https://zhuanlan.zhihu.com/p/529152853
子组件FancyButton
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
父组件中使用
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
传送
传送可以让你把你的html挂载到其他的dom节点下
某个模组
<template>
<Teleport to="#modal">//指定要传送的节点的id,被包裹的部分会给传送走
<div id="centermodal" v-if="isOpen">
<h2><slot>Modal window</slot></h2>
<button @click="$emit('onModalclose')">close</button>
</div>
</Teleport>
</template>
index.html
<!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>
</head>
<body>
<div id="app"></div>
<div id="modal"></div>//会被挂载在这里
<script type="module" src="/src/main.ts"></script>
</body>
</html>
加载后按f12查看,发现窗口在modal这个div下,和app是平级,成功实现了传送
可以用来避免过深的嵌套
Promise、Suspense
异步执行
示例:两秒后打印42
<template>
<h1>{{ result }}</h1>
<span></span>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const result = ref<number | null>(null);
new Promise<number>((resolve) => {
setTimeout(() => {
resolve(42);
}, 2000);
}).then((value) => {
result.value = value;
});
</script>
app.vue
<Suspense>
<template #default>//加载出来后再显示这块
<Asyncshow />
</template>
<template #fallback> //加载失败时显示
<h1>Loading...</h1>
</template>
</Suspense>
provide,inject
在根组件中一次申明,就可以在任意组件中取用
app.vue
const location = ref('HZ')
const changelocation = (input: string) => {
location.value = input
}
provide('location',location)
<button @click="changelocation('Tokyo')">changeyourlocation</button>
//点击按钮就能修改
xxx.vue
import {inject} from 'vue'
const location = inject('location')
获取节点
vue3移除了this指针,但是依旧有方法可以获得节点
<template>
<div class="demo-dropdown-wrap" ref="dropdownRef">
</template>
<script setup lang="ts">
const dropdownRef = ref<null | HTMLElement>(null) //起名一定要和上面一样
const handleMenuClick = (e: MouseEvent) => {
console.log('click', e);
if(dropdownRef.value){
if(dropdownRef.value.contains(e.target as HTMLElement)){
console.log(dropdownRef.value)
}
}
};
</script>
$attrs
好像是阻止vue把子节点的属性合并到父节点上面去的,暂时没用到就先不写了
路由
https://router.vuejs.org/zh/guide
在vue中使用来显示对应的路由界面
基本路由
npm install vue-router@4 --save
当然也可以在创建项目的时候用手脚架快速搭建
在src/router下创建index.js
默认文件
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
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('../views/AboutView.vue')
}
]
})
export default router
在views下创建个shop,添加到路由里面,访问路径/shop,即可看到对应的页面
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import shop from '../views/shop.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
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('../views/AboutView.vue')
},
{
path: '/shop',
name: 'shop',
component: shop//指定对应的组件名,如果没放在同级目录或者views下要像上面import,上面展示的是动态写法
},
]
})
export default router
动态路由
动态路径,可以动态的识别参数,我这里把User.vue放在了/src/news/
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import shop from '../views/shop.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/User/:username/home/:id',
name: 'User',
component: () => import('../news/User.vue')
}
]
})
export default router
User.vue
<template>
<h1>User</h1>
<p>welcome {{ $route.params.username }},your id is {{ $route.params.id }}</p>
</template>
//访问http://localhost:5173/User/alice/home/123
//显示User
//welcome alice,your id is 123
跳转
如果想要通过链接的形式跳转,使用RouterLink
<RouterLink to="/shop">Shop</RouterLink>
如果是要用变量做跳转,记得直接绑定
<RouterLink :to="'/column/' + item.id" @click="console.log(item.id)">
当你想使用函数做跳转,你就需要push
// const goToHomePage = () => {
// routerhome.push('/login');
// };
重定向
重定向
{
path: '/buy',
redirect: '/shop'
}
嵌套路由
嵌套路由
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
],
},
]
嵌套示例
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import shop from '../views/shop.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
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('../views/AboutView.vue')
},
{
path: '/shop',
name: 'shop',
component: shop
},
{
path: '/User/:username/home/:id',
name: 'User',
component: () => import('../news/User.vue'),
children: [
{
path: '',
component: () => import('../news/Userhome.vue')
},
{
path: 'profile',
component: () => import('../news/Userprofile.vue')
},
],
},
{
path: '/buy',
redirect: '/shop'
}
]
})
export default router
import { RouterView } from 'vue-router';
<template>
<h1>User</h1>
<p>welcome {{ $route.params.username }},your id is {{ $route.params.id }}</p>
<RouterView></RouterView> //必须有这行来显示子组件
</template>
Userhome.vue
<template>
<p>here is home</p>
</template>
Userprofile.vue
<template>
<p>here is profile</p>
</template>
懒加载
懒加载可以让你访问到对应路由的时候再加载对应的资源,以减少资源开销和加载时间
// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
props传参
URL /search?q=vue 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
在对应组件使用$route.query.q来获取值
另外一种params的方式在上面动态路由里面展示过了
回退
退回上n页
router.go(n)
push
import { useRouter } from 'vue-router';
const routerhome = useRouter()
const goToHomePage = () => {
routerhome.push('/');
};
守卫
声明式api里面有这样三个路由守卫
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
换到setup的组合式里面,由于setup中路由已经创建完毕,所以只有leave和update两个可用
如果你想使用enter,可以参考这篇,其实原理就是除了setup以外另写个script,在里面用export default
https://blog.richex.cn/vue3-how-to-use-beforerouteenter-in-script-setup-syntactic-sugar.html
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
当然你也可以用用看路由独享的守卫,不过这个得写在router里面
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
示例:检测用户是否登录,否则通通逐一发送login(老牛皮藓网址了
import { globalFlagsStore } from '@/stores/store'
router.beforeEach((to, from, next) => {
const { user } = globalFlagsStore().flags;
// 如果用户未登录且访问的不是登录页面,则导航至登录页面
if (!user.isLoging && to.path !== '/login') {
next('/login');
} else {
next();
}
});
meta
可以在路由上面带元信息,用来配合守卫之类的功能
import ColumnListVue from '@/hooks/ColumnList.vue'
import ColumnDetailVue from '@/views/ColumnDetail.vue'
import HomeVue from '@/views/Home.vue'
import LoginVue from '@/views/Login.vue'
import PostpageVue from '@/views/Postpage.vue'
import { createRouter, createWebHistory } from 'vue-router'
import { globalFlagsStore } from '@/stores/store'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeVue
},
{
path: '/login',
name: 'login',
component: LoginVue,
meta: {requirenologin: true}
},
{
path: '/column/',
name: 'column',
component: ColumnListVue
},
{
path: '/column/:id',
name: 'columndetail',
component: ColumnDetailVue
},
{
path: '/create/',
name: 'createpost',
component: PostpageVue,
meta: {requirelogin: true},
}
]
})
router.beforeEach((to, from, next) => {
const { user } = globalFlagsStore().flags;
//登录的不给访问login,没登陆的不给访问create
if (!user.isLoging && to.meta.requirelogin) {
next('/login');
}else if (to.meta.requirenologin && user.isLoging){
next('/');
}else {
next()
}
});
export default router
promise
const test = new Promise((resolve,rejects) =>{
setTimeout(() => {
const data = { name: "John", age: 30 };
// 如果成功获取到数据,调用 resolve 并传递数据
resolve(data);
// 如果获取数据失败,调用 reject 并传递错误信息
rejects("Error fetching data");
}, 2000);
}).then((data)=>{
if(true){
console.log(data)
}
}).catch((error) => {
console.error(error)
})
async await
async表明一个函数是个异步函数,在async内部可以使用await,await表面这个调用是个promise调用,如果调用成功会返回调用结果,如果失败则会返回报错
async function getData() {
try {
const response = await fetch('https://api.example.com/data'); // 等待fetch请求的Promise对象完成
const data = await response.json(); // 等待解析响应的Promise对象完成
console.log(data);
} catch (error) {
console.log('Error:', error);
}
}
getData();
pinia
状态管理工具,对标vuex,更轻量
vite自定义安装的时候可以直接勾选,如果快速安装则需要另外再装下
npm install pinia
状态是啥,说白了就是当前一些值,比如说我当前在查询3369这个单号的工单,然后我需要切换到另外一个组件继续处理,我希望再切换过去的时候依旧能保持工单号,这样可以减少再操作的麻烦,并且这种情况可能会出现多次,于是我就需要一个状态保持工具
pinia中的store是全局的,不跟随组件的加载卸载改变,并且每个组件都可以读或者写,它有三个概念,state、getter 和 action,相当于组件中的 data、 computed 和 function
(选项式真的太丑了,我宁愿写组合式)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
如果你选的是自定义安装,那么自带的示例长这样,放在stores目录下(不过好像没被使用
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0) //代表变量
const doubleCount = computed(() => count.value * 2) //代表计算属性
function increment() { //代表函数
count.value++
}
return { count, doubleCount, increment }
})
让我们写个vue调用一下他
<template>
<h3>hello,here is shop</h3>
<p>here is your point:{{ store.count }}</p>
<button @click="store.increment">click</button>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>
钩子
使用不同的钩子函数可以在程序的不同生命周期来调用功能
vue3中beforeDestory变成beforeUnmount,destoryed变成unmounted,beforecreate和created合成了setup
另外新增两个调试用的
renderTracked -> onRenderTracked,状态跟踪,vue3新引入的钩子函数,只有在开发环境有用,用于跟踪所有响应式变量和方法,一旦页面有update,就会跟踪他们并返回一个event对象
renderTriggered -> onRenderTriggered,状态触发,同样是vue3新引入的钩子函数,只有在开发环境有效,与onRenderTracked的效果类似,但不会跟踪所有的响应式变量方法,只会定点追踪发生改变的数据,同样返回一个event对象
使用示例
import {onMounted, onUpdated,onRenderTriggered} from 'vue'
onMounted(()=> {
console.log('mounted')
})
onUpdated(()=>{
console.log('updated')
})
onRenderTriggered(()=>{
console.log(event)
})
监听鼠标坐标
const x = ref(0)
const y = ref(0)
function updateMouse(e: MouseEvent){
x.value = e.pageX
y.value = e.pageY
}
onMounted(()=>{
document.addEventListener('click',updateMouse)
})
onUnmounted(()=>{
document.removeEventListener('click',updateMouse)
})
打包
npm run build
npm run preview
vite自带rollup打包,不用另外去折腾webpack啥的
rollup带tree shaking,也就是摇树机制,会自动把没用到的代码删除掉
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --", //打包语句,@会被替换成build-only
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false"
},
第三方
axios
npm install --save axios
npm install --save querystring
基于promise
(这里不知道为啥全局引用失败,先单个引用先)
如果安装了报但是还是报找不到这个模块,那么看下这个处理方法
https://blog.csdn.net/qq_22841387/article/details/123433223?spm=1001.2014.3001.5501
基础
get
axios({
method: "get",
url: "xxxx"
}).then(res => {
console.log(res.data);
})
简写
axios.get("xxxx")
.then(res =>{
console.log(res.data);
})
post
axios({
method:"post",
url:"xxx",
data:qs.stringify({
param1:"xxx",
param2:"xxxx",
verification_code:"xxxxx"
})
}).then(res =>{
console.log(res.data);
})
简写
axios.post("xxx", qs.stringify({
param: "xxx",
......
}))
.then(res => {
console.log(res.data);
})
跨域问题
在vue/vite.config.js中加上
devServer: {
proxy: {
'/api': {
target: '<url>',
changeOrigin: true
}
}
}
ts例子
结合TS使用
import { ref } from 'vue'
import axios from 'axios'
function useURLLoader (url: string){
const result = ref(null)
const loading = ref(true)
const loaded = ref(false)
const error = ref(null)
axios.get(url).then((rawData) => {
loading.value = false
loaded.value = true
result.value = rawData.data
}).catch(e => {
error.value = e
loading.value = false
})
return {
result,
loading,
loaded,
error
}
}
export default useURLLoader
import useURLLoader from './hooks/useURLLoader'
const {result,loading,loaded} = useURLLoader("https://mock.apifox.cn/m1/3372030-0-default/pet/1")
<h1 v-if="loading">Loading</h1>
<p v-if="loaded">reslut:{{ result }}</p>
如果想要使用的调用都在某个网站上的不同路径下,可以在main直接设置根目录,然后调用api的时候只要写相对目录就行了
axios.defaults.baseURL = 'https://根目录'
axios.get('/users')
拦截器
说白了就是把包拦下来改点东西再继续传,分成请求和响应两种
请求拦截器
axios.interceptors.request.use(
(config) => {
// 在发送请求之前,对请求进行处理
// 比如添加请求头,验证身份信息等
config.headers.Authorization = 'Bearer token';
return config;//必须返回,不能只拦不放
},
(error) => {
// 请求错误时的处理
return Promise.reject(error);
}
);
在 Axios 中,请求拦截器的回调函数中的 config 参数是一个包含请求配置的对象。这个对象具有以下常用属性:
url:请求的 URL 地址。
method:请求的 HTTP 方法,例如 GET、POST。
baseURL:基础 URL 地址,会被添加到 url 前面。
headers:请求的头部信息,是一个对象,可以设置请求头的内容,例如设置认证信息。
params:请求的 URL 参数,也是一个对象。这个对象会被自动转换为 URL 查询字符串的形式,并添加到 URL 的末尾。
data:请求的主体数据,通常用于 POST、PUT、PATCH 等请求方法。
timeout:请求的超时时间,单位是毫秒。
transformRequest:请求数据的转换函数,可以用来转换请求数据的格式。
transformResponse:响应数据的转换函数,可以用来转换响应数据的格式。
paramsSerializer:URL 参数的序列化函数,可以自定义参数的序列化方式。
responseType:响应的数据类型,例如 ‘json’、‘text’ 等。
withCredentials:是否允许携带跨域请求的凭证。
auth:用于 HTTP 基础认证的用户名和密码。
onUploadProgress:上传进度的回调函数。
onDownloadProgress:下载进度的回调函数。
响应拦截器
axios.interceptors.response.use(
(response) => {
// 在接收到响应之前,对响应进行处理
// 比如解析数据,统一处理错误等
const data = response.data;
if (data.code !== 200) {
// 处理错误
console.log('请求出错');
}
return response;
},
(error) => {
// 响应错误时的处理
return Promise.reject(error);
}
);
封装、并发
很多时候肯定要用的网址不止一个,单纯封一个默认的网址有点不够用,可以用create来创建独立的axios示例
axios.all可以处理并发请求,他的参数为一个包含多个请求的数组
axios本身也是个promise函数,也可以用then catch来处理结果
import axios from 'axios'
const userRequest = axios.create({
baseURL:"xx"
}
)
const goodsRequest = axios.create({
baseURL: "xxxx"
})
axios.all([
userRequest.get(),
goodsRequest.post({})
])
.then(axios.spread((userResponse, goodsResponse) => {
console.log('User Response:', userResponse);
console.log('Goods Response:', goodsResponse);
}))
.catch(error => {
console.log('Error:', error);
});
环境隔离
不同环境需要封装的地址是不同的,可以通过配置环境文件,来做到在启动的时候就进行自动读取对应环境下的配置,以实现环境隔离封装
https://cn.vitejs.dev/guide/env-and-mode.html
示例:
在项目根目录下创建.env.development
VITE_MODE_NAME=development
VITE_APP_BASE_URL=http://127.0.0.1:8000/
配置vite.config.ts
export default defineConfig({
plugins: [
vue(),
vueJsx(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
envDir: './'//按照实际的路径来
})
封装使用
import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios';
function request(config) {
const instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_URL,
timeout: 5000
});
return instance(config);
}
quillEditor
富文本编辑器
https://blog.csdn.net/weixin_42232622/article/details/126317622
https://www.kancloud.cn/liuwave/quill/1434140
npm install @vueup/vue-quill@alpha --save
无缝滚动vue3-seamless-scroll
https://doc.wssio.com/opensource/vue3-seamless-scroll/
webpack
npm install webpack-dev-server;
UI
antd vue
npm install ant-design-vue@next --save
https://2x.antdv.com/docs/vue/getting-started-cn
import Antd from 'ant-design-vue';
app.use(Antd)
element-plus
npm install element-plus --save
默认源下不了可以用国内源--registry=https://registry.npmmirror.com
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
iconfont
一个前端图表素材库
https://www.iconfont.cn/
选择图表放入购物车,结算到项目,选择下载到本地,解压后放到本地目录下
打开里面的html文件,根据提示引用对应的组件,也可直接在main引用css