项目环境搭建
这里使用vite 构建工具来构建项目
使用命令 npm init vite@latest 创建项目

填写项目名称

选择框架,这里选择vue

这里我选择ts,进入项目目录
这是创建完成的项目目录

使用 npm install 命令安装依赖,安装完成后目录:

使用命令 npm run dev 启动项目

按住ctrl点击那个网址打开页面

到这里项目就创建完成了,下面开始学习vue3的语法
一.学习setup语法糖模式
1.1如何创建一个响应式数据
<template>
<div>
<div>{{ aa }}</div>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const aa = ref("小王");
</script>
<style scoped>
</style>
效果

也可以使用vue2的写法,这里不做展示以后都是语法糖模式
二.基础指令(和vue 2基本不变,这里只做展示 ,详情请学习vue2)
v-for循环渲染元素
v-html设置元素的innerHtml,该指令会导致元素内容的模板内容失效
v-on注册事件简写为@
v-bind:绑定动态属性简写为::
v-show 控制元素的可见度
v-if v-else-if v-else 控制元素的生成
v-model 双向数据绑定 常用于表单元素v-on和v-bind结合版
<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性能要高一点 -->
<div v-show="a===1">显示</div>
<div><button @click="aaa">尽情的点击我吧</button></div>
<!-- <div><button @[event]="aaa">动态事件</button></div> -->
</div>
</template>
<style scoped>
</style>
3.计算属性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.选项式写法,支持一个对象传入get和set函数自定义操作
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.函数式写法(不能修改fullname值)
let fullname=computed(()=>firstname.value+lastname.value)
const changename=()=>{
firstname.value="李";
lastname.value="四";//只能通过这种方式来改变值不能直接修改名字
}
4.ref和reacvite
数据类型不同
ref:将一个普通的 JavaScript 值包装成响应式的引用类型。可以理解为 ref 是对普通值的包装。
reactive:将一个普通的 JavaScript 对象(或数组)转换为响应式代理对象。可以理解为 reactive 是对对象(或数组)的包装。
访问方式不同
ref:使用 .value 属性来访问和修改值。
reactive:可以直接访问和修改对象或数组的属性或元素,而无需使用 .value。
更新触发方式不同
ref:通过 ref() 或 .value 的赋值来触发更新。
reactive:通过直接修改对象或数组的属性或元素来触发更新。
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; // 修改属性
补充:ref 深层次的响应,shallowRef 浅层次的响应,triggerRef,customref
两个不能同时使用,会影响
ref支持所有类型reactive 只能传递引用数据类型
ref取值赋值都需要.value,reactive不需要
综合练习
完成案例:

<template>
<div>
<input type="text" placeholder="请输入要筛选的值" :value="keyword" @keyup.enter="aa($event)"/>
<ul v-for="(item,index) in seraData" :key="item.name">
<li >
<span>名字:{{ item.name }}</span>
<span>价格:{{ item.price }}数量:</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>总价:{{ item.price*item.count }}</span>
<button @click="removeItem(index)">删除</button>
</li>
</ul>
<span>总价{{ 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(): 这是一个新的入口点,在beforeCreate和created之前调用。
onBeforeMount/onMounted:组件挂载前/后的生命周期钩子。
onBeforeUpdate/onUpdated:组件更新前/后的生命周期钩子。
onBeforeUnmount/onUnmounted:组件卸载前/后的生命周期钩子。
onActivated/onDeactivated:组件被 keep-alive 缓存和恢复时调用。
onErrorCaptured:当捕获一个来自子孙组件的错误时被调用。
<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语法糖模式是没有这两个生命周期的,setup 去代替
const name=ref("xiaowang");
const div=ref<HTMLDivElement>()
console.log("setup");
const change=()=>{
name.value="xiaoli";
}
//创建钩子
onBeforeMount(() => {
//创建之前是读不到dom的
console.log("创建之前",div.value);
});
onMounted(() => {
//创建完成才可以
console.log("创建完成",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>我是默认插槽</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>
<!-- 默认插槽 -->
<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 特有的方式给默认值withDefaults(defineProps,{默认值})
// 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 依赖注入方式实现父子组件传参
</style>
5.4动态组件
<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>
<!-- 创建动态组件 -->
<component :is="components[curractive]"></component>
</template>
<script setup>
//markRaw标记一个对象使其永远不会成为响应式对象
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运行命令 npm install vue-router -s
6.2使用路由实现页面跳转
//导入router createRouter, createWebHashHistory, RouteRecordRaw
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routers: Array<RouteRecordRaw> = [{
path: '/',
name: 'home',
redirect: '/login'//默认跳转路径
},
{
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方式进行跳转 通过加replace属性使得跳转没有历史记录-->
<!-- <router-link replace to="/">登录</router-link>
<router-link to="/regist">注册</router-link> -->
<!--2. 通过那么方式进行路由跳转 -->
<!-- <router-link :to="{name:'home'}">登录</router-link>
<router-link :to="{name:'regist'}">注册</router-link> -->
<!-- 3.通过 a标签的方式进行页面跳转,这种方式会刷新页面,不推荐使用 -->
<!-- <a href="/login">登录</a>
<a href="/regist">注册</a> -->
<!-- 4.通过js 的方式进行路由跳转 -->
<button @click="toLogin">登录</button>
<button @click="toRegist">注册</button>
<button @click="prev">prev</button>
<button @click="back">back</button>
<!-- 路由匹配到的组件 会被渲染在这里 这是一个内置组件 -->
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {useRouter} from 'vue-router'
const router = useRouter()
const toLogin = () => {
// router.replace('/login')通过这样的方式进行路由跳转不会留下历史记录
router.push({name:'login'})
}
const toRegist = () => {
// router.push('/regist')字符串方式
// router.push({path:'/regist'})duing对象方式
// router.push({name:'regist'}) name方式
router.push({name:'regist'})
}
const prev = () => {
//前进 router.go(1) 数字代表跳转的次数
router.go(-1)
}
const back = () => {
//后退 router.go(-1) 或者router.back(1)
router.go(1)
}
</script>
<style scoped>
</style>
6.3 路由传参
取值为useRoute,设置值为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.通过路由跳转携带参数
// path:'/detail',
// query:item
// })
route.push({// 2.通过路由跳转携带参数
name:'detail',
params:item
})
}
</script>
<style scoped>
</style>
<template>
<div>
<h1>我是详情页</h1>
<div>
<span>名称:</span>
<span>{{data.name}}</span><br>
<span>价格:</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 该方式已经取消使用了
//如果参数较少可以使用动态路由的方式来传参
const data = route.params
</script>
<style scoped>
</style>
6.4路由守卫(前置守卫,后置守卫)
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)//创建一个虚拟节点
render(vNode,document.body)//渲染到真是dom
const app=createApp(App)
app.use(router)
app.mount('#app')
const passPath=['/login','/regist','/']
router.beforeEach((to,from,next)=>{//路由前置守卫
vNode.component?.exposed?.startLoading()//开始加载
if(passPath.includes(to.path) || localStorage.getItem('token')){//如果路径在passPath中或者已经登录过了,则放行
next()
}else{
next('/')//否则跳转到登录页面
}
})
router.afterEach((to,from)=>{//路由后置守卫,应用场景loding bar
// console.log(to,from)
vNode.component?.exposed?.endLoading()//结束加载
})
通过路由守卫实现一个页面加载进度条
<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路由滚动行为
当切换新路由时如果想要滚动到顶部或者保持原先的位置,就像重新加载页面那样router 可以提供一个 scrollBehavior方法定义切换路由时如何滚动
6.6动态路由
可以通过动态路由的方式来实现权限控制
七.pinia 数据仓库
1.初始化vite-cli
npm init vite@latest
2.根据提示创建vue+ts的项目
3.npm install 安装项目需要的依赖
4.启动项目npm run dev
5.安装pinia
npm install pinia -s
6.导入并使用
import { createPinia } from 'pinia';
const store = createPinia();
let app=createApp(App)
app.use(store)
app.mount('#app')
7.pinia修改值的五种方式
1.直接修改
2.通过test.patch((state)=>{
//工厂函数的方式去修改值,函数里可以做一些逻辑处理
})//推荐使用这种方式
4.test.$state={}//有缺陷必须全部修改,一般不使用这种方式
5.借助actions修改
解构出来的数据是不具有响应式的这一点和reactive是一样的
可以通过storeToRefs包裹解决这一点
import { defineStore } from "pinia";
//导入命名枚举
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')
网友评论