項(xiàng)目環(huán)境搭建
這里使用vite 構(gòu)建工具來構(gòu)建項(xiàng)目
使用命令 npm init vite@latest 創(chuàng)建項(xiàng)目
填寫項(xiàng)目名稱
選擇框架,這里選擇vue
這里我選擇ts,進(jìn)入項(xiàng)目目錄
這是創(chuàng)建完成的項(xiàng)目目錄
使用 npm install 命令安裝依賴豫缨,安裝完成后目錄:
使用命令 npm run dev 啟動(dòng)項(xiàng)目
按住ctrl點(diǎn)擊那個(gè)網(wǎng)址打開頁面
到這里項(xiàng)目就創(chuàng)建完成了独令,下面開始學(xué)習(xí)vue3的語法
一.學(xué)習(xí)setup語法糖模式
1.1如何創(chuàng)建一個(gè)響應(yīng)式數(shù)據(jù)
<template>
<div>
<div>{{ aa }}</div>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const aa = ref("小王");
</script>
<style scoped>
</style>
效果
也可以使用vue2的寫法,這里不做展示以后都是語法糖模式
二.基礎(chǔ)指令(和vue 2基本不變好芭,這里只做展示 燃箭,詳情請(qǐng)學(xué)習(xí)vue2)
v-for循環(huán)渲染元素
v-html設(shè)置元素的innerHtml,該指令會(huì)導(dǎo)致元素內(nèi)容的模板內(nèi)容失效
v-on注冊(cè)事件簡寫為@
v-bind:綁定動(dòng)態(tài)屬性簡寫為::
v-show 控制元素的可見度
v-if v-else-if v-else 控制元素的生成
v-model 雙向數(shù)據(jù)綁定 常用于表單元素v-on和v-bind結(jié)合版
<script setup lang="ts">
import {ref} from 'vue'
let a:number =1;//最簡單的插值語法
let aa=ref(a);
const event="click";
const b:string ="我是一段文字";
const c=ref("小王");
const f:string ="<h1>ssss</h1>";
const aaa = ()=>{
aa.value++;
// c.value="小李"+a;
console.log(a);
}
</script>
<template>
<!-- vue 3 支持vue2的寫法 -->
<div>
<div >{{ aa }}</div>
<input type="text" :value="a"/>
<div>{{ c }}</div>
<!-- <input type="text" v-model="c"/> -->
<!-- <div v-text="b"></div>
<div v-html="f"></div>
<div v-if="a===1">true</div>
<div v-if="a===2">false</div> -->
<!-- v-if 操作dom v-show, 操作css v-show性能要高一點(diǎn) -->
<div v-show="a===1">顯示</div>
<div><button @click="aaa">盡情的點(diǎn)擊我吧</button></div>
<!-- <div><button @[event]="aaa">動(dòng)態(tài)事件</button></div> -->
</div>
</template>
<style scoped>
</style>
3.計(jì)算屬性computed(兩種寫法)
<template>
<div>
<div>名字:{{ name }}</div>
<button @click="change">修改</button>
<form>
<div>{{ form.name }}{{ form.age }}</div>
<input type="text" v-model="form.name"/>
<input type="text" v-model="form.age"/>
<button @click.prevent="submitForm">提交</button>
</form>
<div>
<div>{{ firstname }}</div>
<div>{{ lastname }}</div>
<div>{{ fullname }}</div>
<button @click="changename">改變名字</button>
</div>
</div>
</template>
//1.選項(xiàng)式寫法舍败,支持一個(gè)對(duì)象傳入get和set函數(shù)自定義操作
let firstname=ref("張");
let lastname=ref("三");
let fullname=computed<string>({
get(){
return firstname.value+lastname.value
},
set(value){
const [firstName,lastName]=value.split(" ");
firstname.value=firstName;
lastname.value=lastName;
}
})
const changename=()=>{
fullname.value="李 四";
}
//2.函數(shù)式寫法(不能修改fullname值)
let fullname=computed(()=>firstname.value+lastname.value)
const changename=()=>{
firstname.value="李";
lastname.value="四";//只能通過這種方式來改變值不能直接修改名字
}
4.ref和reacvite
數(shù)據(jù)類型不同
ref:將一個(gè)普通的 JavaScript 值包裝成響應(yīng)式的引用類型招狸。可以理解為 ref 是對(duì)普通值的包裝瓤湘。
reactive:將一個(gè)普通的 JavaScript 對(duì)象(或數(shù)組)轉(zhuǎn)換為響應(yīng)式代理對(duì)象瓢颅。可以理解為 reactive 是對(duì)對(duì)象(或數(shù)組)的包裝弛说。
訪問方式不同
ref:使用 .value 屬性來訪問和修改值挽懦。
reactive:可以直接訪問和修改對(duì)象或數(shù)組的屬性或元素,而無需使用 .value木人。
更新觸發(fā)方式不同
ref:通過 ref() 或 .value 的賦值來觸發(fā)更新信柿。
reactive:通過直接修改對(duì)象或數(shù)組的屬性或元素來觸發(fā)更新。
import { ref, reactive } from 'vue';
// ref示例
const count = ref(0);
console.log(count.value); // 訪問值
count.value += 1; // 修改值
// reactive示例
const state = reactive({
name: 'Alice',
age: 25,
});
console.log(state.name); // 訪問屬性
state.age += 1; // 修改屬性
補(bǔ)充:ref 深層次的響應(yīng)醒第,shallowRef 淺層次的響應(yīng)渔嚷,triggerRef,customref
兩個(gè)不能同時(shí)使用稠曼,會(huì)影響
ref支持所有類型reactive 只能傳遞引用數(shù)據(jù)類型
ref取值賦值都需要.value形病,reactive不需要
綜合練習(xí)
完成案例:
<template>
<div>
<input type="text" placeholder="請(qǐng)輸入要篩選的值" :value="keyword" @keyup.enter="aa($event)"/>
<ul v-for="(item,index) in seraData" :key="item.name">
<li >
<span>名字:{{ item.name }}</span>
<span>價(jià)格:{{ item.price }}數(shù)量:</span>
<button @click="item.count>1?item.count--:item.count">-</button>
<span>{{ item.count }}</span>
<button @click="item.count<99?item.count++:item.count">+</button>
<span>總價(jià):{{ item.price*item.count }}</span>
<button @click="removeItem(index)">刪除</button>
</li>
</ul>
<span>總價(jià){{ total }}</span>
</div>
</template>
<script setup lang="ts">
import { reactive,computed,ref } from "vue";
interface Data{
name: string,
price: number,
count: number
}
let keyword=ref<string>("");
let data=reactive<Data[]>([
{name: 'apple', price: 20, count: 3},
{name: 'banana', price: 10, count: 5},
{name: 'orange', price: 30, count: 2},
{name: 'grape', price: 40, count: 1},
{name: 'kiwi', price: 50, count: 4}
]);
let total=computed(()=>data.reduce((prev:number,next:Data)=>{
return prev+next.price*next.count
},0))
const removeItem=(index:number)=>{
data.splice(index,1);
}
const seraData=computed(()=>data.filter(item=>item.name.includes(keyword.value)))
const aa=(e:any)=>{
keyword.value=e.target.value;
}
</script>
<style scoped>
</style>
五.組件
5.1組件生命周期
在Vue 3中,生命周期鉤子被重命名并且分成了不同的階段霞幅,以更好地描述它們的用途漠吻。這些新的生命周期鉤子包括:
setup(): 這是一個(gè)新的入口點(diǎn)桶略,在beforeCreate和created之前調(diào)用艰管。
onBeforeMount/onMounted:組件掛載前/后的生命周期鉤子。
onBeforeUpdate/onUpdated:組件更新前/后的生命周期鉤子幻枉。
onBeforeUnmount/onUnmounted:組件卸載前/后的生命周期鉤子扔傅。
onActivated/onDeactivated:組件被 keep-alive 緩存和恢復(fù)時(shí)調(diào)用耍共。
onErrorCaptured:當(dāng)捕獲一個(gè)來自子孫組件的錯(cuò)誤時(shí)被調(diào)用。
<template>
<div ref="div">
{{ name }}
<h3>我是組件</h3>
<button @click="change">名字</button>
</div>
</template>
<script setup lang="ts">
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,} from "vue";
//beforeCreate 和 created set語法糖模式是沒有這兩個(gè)生命周期的猎塞,setup 去代替
const name=ref("xiaowang");
const div=ref<HTMLDivElement>()
console.log("setup");
const change=()=>{
name.value="xiaoli";
}
//創(chuàng)建鉤子
onBeforeMount(() => {
//創(chuàng)建之前是讀不到dom的
console.log("創(chuàng)建之前",div.value);
});
onMounted(() => {
//創(chuàng)建完成才可以
console.log("創(chuàng)建完成",div.value);
});
//更新
onBeforeUpdate(() => {
//獲取的是更新之前的dom
console.log("更新之前",div.value?.innerText);
});
onUpdated(() => {
console.log("更新完成",div.value?.innerText);
});
//銷毀
onBeforeUnmount(() => {
console.log("銷毀之前");
});
onUnmounted(() => {
console.log("銷毀");
});
</script>
<style scoped>
</style>
5.2組件插槽
<template>
<div>
<!-- <SlotSub>我是默認(rèn)插槽</SlotSub> -->
<SlotSub>
<template #[sname]>
<h2>Header</h2>
</template>
<template #footer="data">
<div>{{data}}</div>
</template>
</SlotSub>
</div>
</template>
<script setup>
import SlotSub from './SlotSub.vue';
import {ref} from 'vue';
let sname=ref("header")
</script>
<style scoped></style>
<template>
<div>
<!-- 默認(rèn)插槽 -->
<slot></slot>
<!-- 具名插槽 -->
<slot name="header">Header content</slot>
<div v-for="item in data " :key="item.id" >
<slot name="footer" :data="item"></slot>
</div>
</div>
</template>
<script setup>
import {reactive} from 'vue';
const data=reactive([{
id:1,
name:'Alice',
age:20
},
{
id:2,
name:'Bob',
age:22
}],
);
</script>
<style scoped>
</style>
5.3 父子組件傳參
<template>
<div>
<span>{{ title }}</span>
<span>{{ arr }}</span>
<button @click="send">Emit</button>
</div>
</template>
<script setup lang="ts">
// import {ref,} from "vue";
//父組件向子組件傳值的兩種方式
//1.不使用ts的方式
// defineProps({
// title: {
// type: string,
// required: true,
// },
// })
//2.使用ts的方式
defineProps<{title:string,
arr:any[],
}>();
//3.使用ts 特有的方式給默認(rèn)值withDefaults(defineProps,{默認(rèn)值})
// withDefaults(defineProps<{title:string,
// arr:any[]
// }>(),{title:"ni",arr:()=>["xiaomi"]})
//子組件給父組件傳值
const eimt=defineEmits(['on-click',])
const send=()=>{
eimt("on-click","12121")
}
//子組件還可以通過defineExpose來暴露一些屬性和方法給父組件使用
defineExpose({
myProp: "myValue",
myMethod: () => {
console.log("myMethod");
},
})
</script>
<style scoped>
</style>
//=====parent<template>
<div>
<ParentSubParam :title="a" :arr="[1,2,3]" @on-click="dianji" ref="waterFall"/>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
import ParentSubParam from './components/ParentSubParam.vue';
let a=ref("小李1");
const dianji=(name:string)=>{
console.log(name);
}
const waterFall=ref<InstanceType<typeof ParentSubParam>>();
console.log(waterFall.value?.myProp);
</script>
<style scoped>
provide/inject 依賴注入方式實(shí)現(xiàn)父子組件傳參
</style>
5.4動(dòng)態(tài)組件
<template>
<div class="box">
<div class="buttom" @click="curractive=0" :class="{active:curractive===0}">按鈕a</div>
<div class="buttom" @click="curractive=1" :class="{active:curractive===1}">按鈕a</div>
<div class="buttom" @click="curractive=2" :class="{active:curractive===2}">按鈕a</div>
</div>
<!-- 創(chuàng)建動(dòng)態(tài)組件 -->
<component :is="components[curractive]"></component>
</template>
<script setup>
//markRaw標(biāo)記一個(gè)對(duì)象使其永遠(yuǎn)不會(huì)成為響應(yīng)式對(duì)象
import {ref,reactive,markRaw} from "vue"
import ComputedDemo from "./ComputedDemo.vue";
import ComponentsLife from "./ComponentsLife.vue";
import ComputedStart from "./ComputedStart.vue";
const curractive=ref(0);
const components = reactive({
0: markRaw(ComputedDemo),
1: markRaw(ComponentsLife),
2: markRaw(ComputedStart)});
</script>
<style scoped>
.buttom{
width: 100px;
height: 30px;
line-height: 30px;
text-align: center;
border: 1px solid #ccc;
margin: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.buttom:hover{
background-color: #ccc;
}
.box{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
}
.active{
background-color: rgb(166, 162, 162);
}
</style>
六.vue-router
6.1.安裝router運(yùn)行命令 npm install vue-router -s
6.2使用路由實(shí)現(xiàn)頁面跳轉(zhuǎn)
//導(dǎo)入router createRouter, createWebHashHistory, RouteRecordRaw
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routers: Array<RouteRecordRaw> = [{
path: '/',
name: 'home',
redirect: '/login'//默認(rèn)跳轉(zhuǎn)路徑
},
{
path: '/regist',
name: 'regist',
component: () => import('../components/regist.vue')
},
{
path: '/login',
name: 'login',
component: () => import('../components/login.vue')
},
{
path: '/detail/:id/:name/:price',
name: 'detail',
component: () => import('../components/Detials.vue')
},
{
path: '/UrlParamDemo',
name: 'UrlParamDemo',
component: () => import('../components/UrlParamDemo.vue'),
// meta: {//路由元信息试读,里面可以放一些信息可以通過to.meta.title獲取
// title: '路由傳參demo頁面'
// }
},
]
const router = createRouter({
history: createWebHistory(),//路由的模式
routes: routers
})
export default router
<template>
<div>
<h1>router</h1>
<!-- 1.通過path方式進(jìn)行跳轉(zhuǎn) 通過加replace屬性使得跳轉(zhuǎn)沒有歷史記錄-->
<!-- <router-link replace to="/">登錄</router-link>
<router-link to="/regist">注冊(cè)</router-link> -->
<!--2. 通過那么方式進(jìn)行路由跳轉(zhuǎn) -->
<!-- <router-link :to="{name:'home'}">登錄</router-link>
<router-link :to="{name:'regist'}">注冊(cè)</router-link> -->
<!-- 3.通過 a標(biāo)簽的方式進(jìn)行頁面跳轉(zhuǎn),這種方式會(huì)刷新頁面,不推薦使用 -->
<!-- <a href="/login">登錄</a>
<a href="/regist">注冊(cè)</a> -->
<!-- 4.通過js 的方式進(jìn)行路由跳轉(zhuǎn) -->
<button @click="toLogin">登錄</button>
<button @click="toRegist">注冊(cè)</button>
<button @click="prev">prev</button>
<button @click="back">back</button>
<!-- 路由匹配到的組件 會(huì)被渲染在這里 這是一個(gè)內(nèi)置組件 -->
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {useRouter} from 'vue-router'
const router = useRouter()
const toLogin = () => {
// router.replace('/login')通過這樣的方式進(jìn)行路由跳轉(zhuǎn)不會(huì)留下歷史記錄
router.push({name:'login'})
}
const toRegist = () => {
// router.push('/regist')字符串方式
// router.push({path:'/regist'})duing對(duì)象方式
// router.push({name:'regist'}) name方式
router.push({name:'regist'})
}
const prev = () => {
//前進(jìn) router.go(1) 數(shù)字代表跳轉(zhuǎn)的次數(shù)
router.go(-1)
}
const back = () => {
//后退 router.go(-1) 或者router.back(1)
router.go(1)
}
</script>
<style scoped>
</style>
6.3 路由傳參
取值為useRoute荠耽,設(shè)置值為useRouter
<template>
<div class="container">
<!-- 商品列表 {
"name": "腳踩老壇酸菜",
"price": 30,
"count": 2
}-->
<div class="item" v-for="(item,index) in goods" :key="index">
<span class="name">{{item.name}}</span>
<span class="price">{{item.price}}</span>
<span class="count">{{item.count}}</span>
<span class="total">{{item.price * item.count}}</span>
<button @click="detail(item)">詳情</button>
</div>
</div>
</template>
<script setup lang="ts">
import{useRouter} from 'vue-router'
import {ref} from 'vue'
import {data} from './list.json'
const route = useRouter()
type Items = {
id:string,
name:string,
price:number,
count:number
}
const goods = ref(data)
const detail = (item:Items) => {
// route.push({// 1.通過路由跳轉(zhuǎn)攜帶參數(shù)
// path:'/detail',
// query:item
// })
route.push({// 2.通過路由跳轉(zhuǎn)攜帶參數(shù)
name:'detail',
params:item
})
}
</script>
<style scoped>
</style>
<template>
<div>
<h1>我是詳情頁</h1>
<div>
<span>名稱:</span>
<span>{{data.name}}</span><br>
<span>價(jià)格:</span>
<span>{{data.price}}</span>
</div>
<router-link :to="{name:'UrlParamDemo'}">返回</router-link>
</div>
</template>
<script setup lang="ts">
import {useRoute} from 'vue-router'
const route = useRoute()
// const data = route.query
// const data = route.params 該方式已經(jīng)取消使用了
//如果參數(shù)較少可以使用動(dòng)態(tài)路由的方式來傳參
const data = route.params
</script>
<style scoped>
</style>
6.4路由守衛(wèi)(前置守衛(wèi)鹏往,后置守衛(wèi))
import { createApp,createVNode,render } from 'vue'
import App from './App.vue'
import router from './router/index'
import loadingBar from './components/loadingBar.vue'
const vNode=createVNode(loadingBar)//創(chuàng)建一個(gè)虛擬節(jié)點(diǎn)
render(vNode,document.body)//渲染到真是dom
const app=createApp(App)
app.use(router)
app.mount('#app')
const passPath=['/login','/regist','/']
router.beforeEach((to,from,next)=>{//路由前置守衛(wèi)
vNode.component?.exposed?.startLoading()//開始加載
if(passPath.includes(to.path) || localStorage.getItem('token')){//如果路徑在passPath中或者已經(jīng)登錄過了,則放行
next()
}else{
next('/')//否則跳轉(zhuǎn)到登錄頁面
}
})
router.afterEach((to,from)=>{//路由后置守衛(wèi),應(yīng)用場景l(fā)oding bar
// console.log(to,from)
vNode.component?.exposed?.endLoading()//結(jié)束加載
})
通過路由守衛(wèi)實(shí)現(xiàn)一個(gè)頁面加載進(jìn)度條
<template>
<div class="wraps">
<div class="bar" ref="bar">
</div>
</div>
</template>
<script setup lang="ts">
import { ref, } from 'vue'
let bar = ref<HTMLElement>()
let speed = ref<number>(0);
let timer = ref<number>(0);
const startLoading = () => {
let ba = bar.value as HTMLElement
setTimeout(() => {
speed.value = 1
timer.value = window.requestAnimationFrame(function fn() {
if (speed.value < 100) {
ba.style.width = speed.value + '%';
speed.value += 1;
timer.value = window.requestAnimationFrame(fn)
} else {
speed.value = 0
window.cancelAnimationFrame(timer.value)
}
});
}, 1000)
}
const endLoading = () => {
let ba = bar.value as HTMLElement
setTimeout(() => {
window.requestAnimationFrame(() => {
speed.value = 100
ba.style.width = speed.value + '%';
})
}, 2000)
}
defineExpose({
startLoading,
endLoading
})
</script>
<style scoped lang="less">
.wraps {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 2px;
// background-color: #409eff;
z-index: 999;
.bar {
height: 2px;
background-color: #409eff;
width: 1%;
}
}
</style>
6.5路由滾動(dòng)行為
當(dāng)切換新路由時(shí)如果想要滾動(dòng)到頂部或者保持原先的位置,就像重新加載頁面那樣router 可以提供一個(gè) scrollBehavior方法定義切換路由時(shí)如何滾動(dòng)
6.6動(dòng)態(tài)路由
可以通過動(dòng)態(tài)路由的方式來實(shí)現(xiàn)權(quán)限控制
七.pinia 數(shù)據(jù)倉庫
1.初始化vite-cli
npm init vite@latest
2.根據(jù)提示創(chuàng)建vue+ts的項(xiàng)目
3.npm install 安裝項(xiàng)目需要的依賴
4.啟動(dòng)項(xiàng)目npm run dev
5.安裝pinia
npm install pinia -s
6.導(dǎo)入并使用
import { createPinia } from 'pinia';
const store = createPinia();
let app=createApp(App)
app.use(store)
app.mount('#app')
7.pinia修改值的五種方式
1.直接修改
2.通過test.patch((state)=>{
//工廠函數(shù)的方式去修改值伊履,函數(shù)里可以做一些邏輯處理
})//推薦使用這種方式
4.test.$state={}//有缺陷必須全部修改韩容,一般不使用這種方式
5.借助actions修改
解構(gòu)出來的數(shù)據(jù)是不具有響應(yīng)式的這一點(diǎn)和reactive是一樣的
可以通過storeToRefs包裹解決這一點(diǎn)
import { defineStore } from "pinia";
//導(dǎo)入命名枚舉
import { Names } from "./storeNameSpace";
export const userTestStore=defineStore(Names.Test,{
state:()=>{
return {
name:'你好',
current:1,
}
},
//類似于computed 可以用來修飾一些值
getters:{
},
//actions類似于methods 用來寫一些方法,同步異步都可以
actions:{
},
});
<template>
<div>
{{ test.current }}----{{ test.name }}
</div>
</template>
<script setup lang="ts">
import { userTestStore } from './store/demo';
const test=userTestStore();
</script>
<style scoped>
</style>
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia';
const store = createPinia();
let app=createApp(App)
app.use(store)
app.mount('#app')