Appearance
Vue 笔记
基础示例
html
<!-- Vue -->
<div id="box">
<ul>
<li>{{book}}</li>
</ul>
</div>
<script type="text/javascript">
var app=new Vue({
el:'#box',
data:{book:'这是书名'},
methods:{函数名:function(){}},
computed:{函数名:function(){}},//计算属性是基于它们的响应式依赖进行缓存的,这就意味着只要值还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。
watch:{data名:function([newVal],[oldVal]){}}
});
//freeze会冻结变量,值不可修改
var obj = {foo: 'bar'}
Object.freeze(obj)
app.$data === data
app.$el===document.getElementById('#box')
</script>生命周期
js
//生命周期钩子(vue3)
beforeCreate://此时data,methods还没初始化
created: //实例创建后调用,data,methods可以使用------重要
beforeMount://模板编辑完成,el尚未挂载
mounted: //el挂载到实例上调用,完全创建------重要
beforeUpdate//数据更新时调用,此时数据是旧的
updated://数据更新时调用,此时数据是新的
beforeUnmount: //实例销毁之前调用
unmounted:过滤器与指令
js
//私有过滤器
filter:{
msgFormat:function(){}
}
// 全局过滤器:
Vue.filter('msgFormat',function(data,[arg],[arg2]){
var dt=new Date(data);
return});
//私有自定义指令
directives:{
'color':{
bind:function(el){
el.style.color='red'
}
}
}
//全局自定义指令
Vue.directive('focus',{
bind:function(el,[binding]){// 指令绑定到元素时(未插入DOM)执行,只执行一次
el.style.color='red'
//el.style.color=binding.value
//el.style.color=binding.expression
},
inserted:function(el){//元素插入到DOM中的时候执行,只执行一次
el.focus()
},
updated:function(el){//当VNode更新的时候执行,可能会触发多次
}
})
Vue.directive('focus',function(el){}) //简写,bind+update列表操作与搜索
js
//数组操作
push() //新增
splice(index,1)//删除
this.list=[] //清空
v-show="list.length!=0" //隐藏
//根据关键词显示数组,for(item in search(keywords))
search:function(keywords){
var newList=[];
for(var i=0;i<this.lists.length;i++){ //欲搜索的数组
if(this.lists[i].name.indexOf(keywords)!==-1){ //在数组中搜索keywords
newList.push(this.lists[i]) //装填进新数组
}
}
this.lists=newList
}模板指令速记
txt
//指令
//在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写
v-text//输出文本内容
v-html //输出原始html内容
v-if //条件成立是显示该行,可配合<template>标签
v-else-if//条件成立是显示该行
v-else//否则显示该行
v-show //不会对元素进行销毁
v-pre //跳过这个元素和子元素的渲染,显示原始{{}}
v-for="(item,[index]) in books" //遍历数组
v-for="(value,[key],[index]) in books" //遍历对象
v-for="count in 10"//迭代数字,从1开始
//特殊情况下需要绑定:key,以便互斥
v-on:click //事件监听,缩写为@
//修饰符
// .stop阻止冒泡
// .prevent 阻止默认事件(跳转)
// .capture 实现捕获触发事件
// .self 点击当前元素时才触发
// .once 只触发一次
v-bind:href //绑定属性,缩写为:
v-cloak//无表达式,编译结束时移除,配合[v-cloak]{display:none}使用,即在网速加载慢时隐藏即将渲染的内容
v-once //无表达式,一次性渲染
v-model//表单指令
//单选框:选中时,:value激活,v-model=value时,选中相应value的表单;value可绑定为:value=""
//多选框:v-model="ture"时选中,v-model="false"时未选中,v-model=['value']时,选中相应value的表单;value可绑定为:true-value="" :false-value=""
//文本框:v-model为文本框内容过渡与动画
html
<!-- vue过渡 -->
v-enter-active:v-enter-from~v-enter-to
v-leave-active:v-leave-from~v-leave-to
<style>
.v-enter-from,.v-leave-to{/* 进入过渡的开始状态,离开过渡的开始状态 */
opacity:0
}
.v-enter-active,.v-leave-active{ /* 进入的阶段、离开的阶段 */ani
transition: all .3s ease
}
/* css动画 */
.v-enter-active {
animation: bounce-in .5s;
}
.v-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
</style>
mode属性:
in-out(++a):新元素先进行过渡,完成后当前元素过渡离开
out-in(a++):当前元素先进行过渡,完成之后新元素过渡进入
appear属性:
首次渲染过渡效果
<transition name="" :duration="{enter:200,leave:400} mode="out-in"><!-- 可定义name -->
<div key=""></div><!-- 多个元素过渡用key实现互斥 -->
</transition>
<transition-group tag="ul"><!-- 列表过渡,默认渲染为span -->
<li v-for="" :key="data.id"></li>
</transition>
<!-- JavaScript 钩子 -->
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"></transition>
<script type="text/javascript">
methods: {
beforeEnter: function (el) {},
enter: function (el, done) {done()}, // 当与 CSS 结合使用时,回调函数 done 是可选的
afterEnter: function (el) {},
enterCancelled: function (el) { },
beforeLeave: function (el) {},
leave: function (el, done) {done()},
afterLeave: function (el) {},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {}
}
</script>
<!-- 类名控制第三方动画 -->
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
> </transition>
<!-- 过渡持续时间 -->
<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
<!-- 多个元素过渡 -->
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>组件与通信
html
<!-- 组件 -->
<body>
<xxf :message="wxx"></xxf>
</body>
<!-- 必须要在实例创建前注册,template的DOM结构必须被元素包含,否则无法进行渲染-->
<!-- 1.全局注册-->
<template id="tmp1">这是通过template元素在外部定义的组件结构</template>
<script type="text/X-templates" id="tmp1"></script><!-- X-templates写法 -->
<script>
// (分开写)
var com=Vue.extend({
template:'#tmp1'
})
Vue.component('myCom1',com)//初值传递,父组件使用-分隔,子组件驼峰书写
// (合并写)
Vue.component('xxf',{
props:['message'], //属性名,要使属性名后的数值绑定,使用v-bind,也可以作为
template:'<div>{{message}}</div>',//有且只有一个根元素,里面的值优先选取父元素data,其次子组件data
data:function(){
return{message:'组件中的data数据'};
}
});
//2.局部注册
var app = new Vue({
el: '#app',
components:{
myCom1:{
template:'<div>{{message}}</div>'
}
}
});
</script>
<!-- v-bind.sync可进行双向绑定 -->
<!-- 组件的三种通信 -->
<div id="app">
{{smessage}}
<my-component @xxf="show" :wxx="fmessage"></my-component> <!-- 这是父组件 -->
</div>
<script type="text/javascript">
// var bus=new Vue();//定义第三方Vue实例
Vue.component('my-component',{ //这是子组件
props:['wxx'],//写成对象可以进行数据验证
template:'<button @click="handleEvent">{{wxx}}</button>',
data:function(){
return{
counter:'子组件的内容'
}
},
methods:{
handleEvent:function(){
// bus.$emit('on-message','来自第三方的内容');
this.$emit('xxf',this.counter);//子组件向父组件传值,xxf为父组件自定义事件名称,this.couter为子组件数据,父组件可通过e接收参数
}(子组件触发事件回调传给父组件)
}
});
var app=new Vue({ //这是父组件
el:'#app',
data:{
fmessage:'父组件的内容',
smessage:''
},
methods:{
show:function(msg){
this.smessage=msg;//父组件接收值
}
},
// mounted:function(){
// var that=this;
// bus.$on('on-message',function(msg){//第三方传值
// that.message=msg;
// })
// }
})
</script>ref 与组件实例
js
//ref获取dom元素
<ref="">
this.$refs.ref名.属性名
$parent访问父组件实例对象
$root访问根组件实例对象前端路由
html
<div id="app">
<router-view name="left"></router-view>
</div>
<router-link to="/login" tag="span" replace></router-link> <!-- <a href="#/login"></a>--> <!-- replace不会留下history记录 -->
<router-link to="/register"></router-link><!-- <a href="#/register"></a> -->
<router-link active-class="active"></router-link>动态路由的匹配
| 匹配模式 | 匹配路径 | $route.params |
|---|---|---|
| /user/:username | /user/why | {username:'why'} |
| /user/:username/id/:id | /user/why/id/123 | {username:'why',id:'123'} |
js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path:'/',
redirect:'/login',//默认重定向为login
meta: {title: '后台主页'}
},
{
path:'/login',
component:xxxx,
mounted(){
console.log(this.$route.query)//query传参,不需要修改path参数,
}
}
},
{
path:'/register/:id',
component:()=>import('xxxx'),//异步路由
mounted(){
console.log(this.$route.params)//params传参,包含了动态片段和全匹配片段
}
}
{
path:'/account',
component:xxxx,
//子路由
children:[
{
path:'login',
component:xxxx,
}
]
}
{
path:'/',
components:{'left':leftbox,'right':rightbox},
}//路由视图,需要router-view加name
],
const router = createRouter({
history: createWebHistory(),
routes
})
// const router = new VueRouter({
// mode:'history', //如果不配置mode,就会使用#设置路径
// routes,
// // linkActiveClass: 'class-active'
// })
export default router
//在组件中使用vue-router
import { useRoute, useRouter } from 'vue-router'
// 必须先声明调用
const route = useRoute()
//route.name, route.path, route.params, route.query
const router = useRouter()
//router.push()、router.replace()、router.go()、router.back()路由守卫
js
const router = createRouter()
// to:将进入的路由Route对象
// from:将离开路由Route对象
// 返回值的作用
// 1.false:取消当前导航
// 2.undefined或不返回:进行默认导航
// 3.字符串:一个路由路径
// 4.对象:如{path:'/login',query:{}}
router.beforeEach(async (to, from) => {
if (!isAuthenticated && to.name !== 'Login') {
// 将用户重定向到登录页面
return { name: 'Login' }
}
})全局后置钩子
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身: 它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用
js
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})路由守卫Bug解决方案
js
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}路由内置组件的插槽
router-link插槽:href,route,navigate,isActive,isExactActive
html
<router-link v-slot="props">
<span>{{ props.href }}</span>
<span>{{props.isActive}}</span>
</router-link>router-view插槽可以使用<transition>和<keep-alive>组件包裹 这个对象包含以下属性: Component:当前渲染的组件 route:当前激活的路由信息对象,它包含了诸如 params, query, meta 等信息。
html
<router-view v-slot="props">
<keep-alive>
<component :is="props.Component"></component>
</keep-alive>
</router-view>
<router-view v-slot="{ Component }">
<component :is="Component">
<p>In Vue Router 3, I render inside the route component</p>
</component>
</router-view>插槽
html
<!-- 插槽 -->
<!-- 单个插槽 -->
<script>
Vue.component('login',{
template:'<slot></slot>'
})
</script>
<!-- 具名插槽 -->
<login>
<div slot="a"></div>
<div slot="b"></div>
</login>
<script>
Vue.component('login',{
template:'\
<slot name="a"></slot>\
<slot name="b"></slot>'
})
</script>
<template v-slot:header>可简写为<template #header>
<!-- 作用域插槽 -->
<login>
<template scope="props">
<p>{{props.msg}}</p>
</template>
</login>
<script>
Vue.component('login',{
template:'\
<div>\
<slot msg="子组件内容"></slot>\
</div>'
})
</script>单文件组件提示
txt
<!-- 单vue组件 -->
v-for需要配合"v-bind:key"
JS部分用export default {}
data必须是function(){return:{}}
需要引入模块
img:src渲染时要加require()
引入css时路径为/
引入js时路径为./Vuex
js
//1.store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
//data
state: {
vuex_message: []
},
//methods
mutations: {
// payload为用户传递的值,可以是单一值或者对象
ADD_MESSAGE(state, message){
state.vuex_message.push(message)
}
},
//bus(支持异步promise)
actions:{
addmessage({ commit },message){
commit('ADD_MESSAGE',message)
}
},
//computed
getters:{
getmessage: state => state.vuex_message
}
})
export default store
//2.main.js:
import store from '@/store';
new Vue({
store,//在此添加
})
export default stores
//3.vue:
export default{
computed:{
count(){
return this.$store.state.count;//访问state数据
},
listcount(){
return this.$store.getters.getStateCount;//调用getters方法
}
},
methods:{
handleIncrement(){
this.$store.dispatch('handleIncrement',10);//提交信息
},
}
}
//map快速引入
import {mapState,mapGetters}
computed: {
...mapState(['vuex_token']) //取值(相当于this.$store.state.vuex_token)
...mapGetters([ 'vuex_token'])//过滤
},自定义组件 v-model
js
//自定义组件v-model:
// 标准写法
<input v-model="name">
// 等价于
<input :value="name" @input="name = $event.target.value">
watch: {
//监听用户的值
value(v) {
this.name = v
}
},
methods:{
change(){
this.$emit('input',this.name)
}
},Vue 3 组合式 API
js
// ------------------------------------Vue3----------------------------:
const vm =Vue.createApp({
data(){
return{
book:''
}
}
}).mount('#app')
组合API
setup 执行时机是在 beforeCreate 之前执行
const {reactive,watchEffect,ref}=Vue
setup函数不能访问data,methods,computed
setup函数接受两个可选参数:1.props,2.context
<script>
setup(){
//响应式状态
const state=Vue.reactive({count:0})
function increment(){
state.count++
}
return{
state,increment
}
}
</script>
const book=reactive({
author:'',
title:''
})
// 创建响应式对象:
一:reactive()
二:ref(初始值) 通过value获取值
reactive和ref区别:
Ref 用来创建基础类型的响应式数据,模板默认调用value显示数据。方法中修改需要修改value的值才能修改
Reactive 用来创建引用类型的响应式数据,
//监听:
watchEffect 会立即执行传入的函数,并自动追踪该函数中访问的所有响应式数据。当这些响应式数据发生变化时,watchEffect 会重新运行该函数
watchEffect(()=>)不需要分离监听的数据源和副作用回调
watchEffect(() => {
console.log(`count is now: ${count.value}`);
});
//watch 用于监听特定的响应式数据源
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`);
});
watch([source1, source2], ([new1, new2], [old1, old2]) => {
// 处理逻辑
});
// 计算属性:
const a=computed(()=>)
// 定义一个带有 get 和 set 方法的计算属性 fullName
const fullName = computed({
// get: () => {
// return `${firstName.value} ${lastName.value}`;
// },
// set: (newValue) => {
// const [newFirstName, newLastName] = newValue.split(' ');
// firstName.value = newFirstName;
// lastName.value = newLastName;
// }
})
toRefs 函数用于将一个响应式对象的所有属性都转换为 ref,其中每个属性都是一个 ref。这样做的好处是你可以解构这个新对象,并且每个解构出来的 ref 仍然保持其响应性
coust title=toRefs(book)
用于将一个响应式对象的某个属性转换为一个 ref。这样做的好处是你可以单独传递或返回这个 ref,而不需要传递整个响应式对象。
coust title=toRef(book,'title')
语法糖:<script setup></script>
// 生命周期:
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeUnmount(Vue 2 中为 beforeDestroy)
unmounted(Vue 2 中为 destroyed)
Composition API 生命周期钩子
Vue 3 引入了 Composition API,它提供了一种基于函数的组合方式来重用逻辑,同时引入了新的生命周期钩子,这些钩子是通过 setup() 函数访问的:
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount(等同于 Vue 2 的 beforeDestroy)
onUnmounted(等同于 Vue 2 的 destroyed)
onActivated
onDeactivated
onErrorCaptured
onRenderTracked
onRenderTriggered
在Vue3中,setup函数为什么没有beforeCreate和created生命周期钩子? (B)
A. beforeCreate和created在Vue3中被完全移除,不再需要
B. setup函数的执行时机和功能已经覆盖了beforeCreate和created的作用
<script>
import { nextTick } from 'vue'
nextTick(() => {
// ...
})
import { useStore } from 'vuex'
import { key } from '../store/index'
//vuex
// 必须先声明调用
const store = useStore(key)
// 获取Vuex的state
store.state.xxx
// 触发mutations的方法
store.commit('fnName')
// 触发actions的方法
store.dispatch('fnName')
// 获取Getters
store.getters.xxx
</script>
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
CSS变量
<style vars="{ border, color }" scoped>
h1 {
color: var(--color);
border: var(--border);
}
</style>
<style module>
//可通过$style来获取css类
//"$style.red"
//在vue3中使用v-bind
<style>
.red{
color:v-bind(color1);//vue3.2后可使用
}
<script>
//非父子组件通信(provide和inject)
vue3写法:
import {provide} from 'vue'
provide ("name",name)
provide("age",age)
inport {inject} from 'vue'
inject("age")
//全局事件总线
import mitt from 'mitt'
const emitter=mitt()
emitter.emit('why',{name:"why"})//发送
emitter.on('why',(info)=>{
console.log(info)//监听事件
})
</script>
//异步组件
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
mixin(混入):
如果data属性或methods对象冲突,会使用组件自身数据
生命周期函数都会被调用
全局混入:
let app=createApp(App)
app.mixin({
created(){
}
})
extends(继承):它实际上是在创建一个新组件时继承了另一个组件的所有选项
// 暴露给父组件的属性和方法,在 <script setup> 语法糖中,defineExpose 可以直接使用,而不需要显式导入
defineExpose({
message,
logMessage: () => {
console.log(message.value);
}
});
// 引用子组件
const childRef = ref(null);
// 在挂载后访问子组件的属性和方法
<Child ref="childRef" />
onMounted(() => {
if (childRef.value) {
console.log(childRef.value.message); // 输出: Hello from Child Component
}
});
//render函数的使用
//选项式写法
render(){
return h("h4",{class:"title"},"render组件")
}
//组合式写法
import {h} from 'vue'
// 定义 render 函数
const render = () => h(
'div',
{ class: 'user-card' },
[
h('h2', {}, props.user.name),
h('p', {}, `年龄: ${props.user.age}`),
h('p', {}, `地址: ${props.user.address.street}, ${props.user.address.city}, ${props.user.address.country}`),
h('p', {}, ageMessage.value)
]
);
//使用 JSX 的 `render` 函数示例:(需要@vue/babel-plugin-jsx)
import { defineComponent } from 'vue';
export default defineComponent({
render() {
return <div class="container">Hello, World!</div>;
}
});
//defineModel 是 Vue 3.4+ 引入的宏,用于简化 v-model 的双向绑定实现
<script setup lang="ts">// 自动生成 props 和 emit
const model = defineModel<string>()
</script>
<template>
<input v-model="model" />
</template>
//defineOptions 用于在 <script setup> 中直接定义组件选项,无需再使用额外的 <script> 标签
<script setup lang="ts">// 定义组件名称、继承选项等
defineOptions({
name: 'MyComponent',
inheritAttrs: false,
customOptions: {}
})
</script>在 Vue 3 中,withDefaults 是一个用于 defineProps 和 defineEmits 的辅助函数,主要用于为组件的 props 设置默认值。这个功能是 Vue 3 的一个新增特性,帮助简化组件的 Props 配置和默认值设置。 withDefaults 的基本用法 withDefaults 函数允许你在 defineProps 中定义 props 的默认值,特别是在使用 TypeScript 或者想要确保 props 有默认值时,它显得特别有用。以下是一个简单的示例:
js
<template>
<div>
<p>{{ message }}</p>
<p>{{ count }}</p>
</div>
</template>
<script setup>
import { withDefaults, defineProps } from 'vue';
// 使用 withDefaults 来为 props 设置默认值
const props = withDefaults(defineProps<{
message?: string;
count?: number;
}>(), {
message: 'Hello, Vue 3!',
count: 0
});
</script>深度选择器写法
/deep/ 选择器(Webpack特有)
css
<style scoped>
.parent /deep/ .child {
color: blue;
}
</style>::v-deep 伪元素(Vue 2.x推荐)
三种使用形式:
css
/* 形式1:伪元素语法 */
.parent ::v-deep .child {
color: green;
}
/* 形式2:组合选择器 */
.parent ::v-deep(.child) {
background: yellow;
}
/* 形式3:Sass/Less嵌套 */
<style lang="scss" scoped>
.parent {
&::v-deep .child {
border: 1px solid;
}
}
</style>:deep() 新标准方案(Vue 3.x首选)
css
<style scoped>
.parent :deep(.child) {
opacity: 0.8;
}
</style>pinia
下面就是 pinia API 的基本用法
js
//1.选项式写法
// stores/counter.js
import { defineStore } from 'pinia';
// `defineStore()` 的返回值的命名是自由的
// 但最好含有 store 的名字,且以 `use` 开头,以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 };
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++;
},
},
});js
//2.组合式写法
import { defineStore } from 'pinia';
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
});然后在组件中使用它:
html
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
//第一种方法
counter.count++
// 第二种方法(它允许你用一个 state 的补丁对象在同一时间更改多个属性:)
counter.$patch({ count: counter.count + 1 })
counter.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
// 第三种方法:调用action
counter.increment()
</script>
<template>
<!-- 直接从 store 中访问 state -->
<div>Current Count: {{ counter.count }}</div>
</template>如果你还不熟悉 setup() 函数和组合式 API,Pinia 也提供了一组类似 Vuex 的 映射 state 的辅助函数。
你可以用和之前一样的方式来定义 Store,然后通过 mapStores()、mapState() 或 mapActions() 访问:
js
const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
const useUserStore = defineStore('user', {
// ...
})
// 使用
import { mapState, mapStores, mapActions } from 'pinia'
export default defineComponent({
computed: {
// 其他计算属性
// ...
// 允许访问 this.counterStore 和 this.userStore
...mapStores(useCounterStore, useUserStore)
// 允许读取 this.count 和 this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// 允许读取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
})从 Store 解构
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
js
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 都是响应式引用
// 下面的代码同样会提取那些来自插件的属性的响应式引用
// 但是会跳过所有的 action 或者非响应式(非 ref 或者 非 reactive)的属性
const { name, doubleCount } = storeToRefs(store)
// 名为 increment 的 action 可以被解构
const { increment } = store
</script>TypeScript写法
ts
interface State {
userList: UserInfo[]
user: UserInfo | null
}
const useStore = defineStore('storeId', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
interface UserInfo {
name: string
age: number
}Vue脚手架
脚手架一:Vue CLI (基于 webpack)
bash
npm install -g @vue/cli使用 vue create 命令创建项目,my-vue-cli-project 是你的项目文件夹名称 :
bash
vue create 项目名称脚手架二:create-vue(基于Vite)
bash
npm i create-vue@latest -g新建项目
bash
create-vue组合式API与TS
props
以前的写法:
html
<script setup lang="ts">
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
</script>现在的写法:
html
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>props解构默认值:
ts
//3.5+(推荐)
interface Props {
msg?: string
labels?: string[]
}
const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()ts
//3.4以版本以下
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})emits
以前的写法:
html
<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])
emit('change', 1)
emit('update', 'hello')
</script>ts
// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
emit('change', 1)
emit('update', 'hello')为 ref() 标注类型
以前的写法:
js
import { ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)现在的写法:
ts
import { ref } from 'vue'
const year = ref<string | number>('2020')如果你指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined 的联合类型:
ts
// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()为reactive()标注类型
以前的写法:
ts
import { reactive } from 'vue'
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })现在的写法:
ts
import { reactive } from 'vue'
interface Book {
title: string
year?: number
}
//不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。
const book: Book = reactive({ title: 'Vue 3 指引' })为computed()标注类型
ts
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})为methods()标注类型
ts
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}为 provide / inject 标注类型
ts
//注意注入的值仍然可以是 undefined,因为无法保证提供者一定会在运行时 provide 这个值
const foo = inject<string>('foo') // 类型:string | undefined
//当提供了一个默认值后,这个 undefined 类型就可以被移除:
const foo = inject<string>('foo', 'bar') // 类型:string为模板引用标注类型(ref取DOM)
之前的写法:
html
<input type="text" ref="inputEl" />
<script>
const inputEl = ref();
</script>现在的写法:(vue3.5+)
html
<input type="text" ref="inputEl" />
<script setup lang="ts">
import { useTemplateRef } from 'vue'
const inputEl = useTemplateRef<HTMLInputElement>("inputRef");
</script>为组件模板引用标注类型
html
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'
type FooType = InstanceType<typeof Foo>//定义组件实例类型
type BarType = InstanceType<typeof Bar>//定义组件实例类型
const compRef = useTemplateRef<FooType | BarType>('comp')
</script>
<template>
<component :is="Math.random() > 0.5 ? Foo : Bar" ref="comp" />
</template>