关于Vue3+pinia+ts的学习笔记

这篇博客详细介绍了Vue3中的ref使用,包括如何判断ref对象、triggerRef和customRef。接着,博主探讨了虚拟DOM的diff算法及其特点。此外,还讲解了Vue3的组件基础,包括组件生命周期、全局和局部组件配置,以及递归组件的实现。最后,文章提到了异步组件和watch、watchEffect的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue的模板语法:

v- 开头都是vue 的指令

v-text 用来显示文本

v-html 用来展示富文本

v-if 用来控制元素的显示隐藏(切换真假DOM)

v-else-if 表示 v-if 的“else if 块”。可以链式调用

v-else v-if条件收尾语句

v-show 用来控制元素的显示隐藏(display none block Css切换)

v-on 简写@ 用来给元素添加事件

v-bind 简写:  用来绑定元素的属性Attr

v-model 双向绑定

v-for 用来遍历元素

v-on修饰符 冒泡案例
//v-bind 绑定class 案例 1

<template>
  <div :class="[flag ? 'active' : 'other', 'h']">12323</div>
</template>
 
 
<script setup lang="ts">
const flag: boolean = false;
</script>
 
 
 
<style>
.active {
  color: red;
}
.other {
  color: blue;
}
.h {
  height: 300px;
  border: 1px solid #ccc;
}
</style>
//v-bind 绑定class 案例 2
<template>
  <div :class="flag">{{flag}}</div>
</template>
 

<script setup lang="ts">
type Cls = {
  other: boolean,
  h: boolean
}
const flag: Cls = {
  other: false,
  h: true
};
</script>
 
 
 
<style>
.active {
  color: red;
}
.other {
  color: blue;
}
.h {
  height: 300px;
  border: 1px solid #ccc;
}
</style>
//v-bind 绑定style案例
<template>
  <div :style="style">2222</div>
</template>
 
 
 
<script setup lang="ts">
 
 
type Style = {
  height: string,
  color: string
}
 
const style: Style = {
  height: "300px",
  color: "blue"
}
 
</script>
 
 
<style>
</style>
//v-model 案例
<template>
  <input v-model="message" type="text" />
  <div>{{ message }}</div>
</template>
 
 
<script setup lang="ts">
import { ref } from 'vue'
const message = ref("v-model")
</script>
 
 
 
<style>
.active {
  color: red;
}
.other {
  color: blue;
}
.h {
  height: 300px;
  border: 1px solid #ccc;
}
</style>

________________________________分段_______________________________________

Vue——ref

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。

//我们这样操作是无法改变message  的值 应为message 不是响应式的无法被vue 跟踪要改成ref
<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
let message: string = "我是message"
 
const changeMsg = () => {
   message = "change msg"
}
</script>
 
 
<style>
</style>

 改为ref

//Ref TS对应的接口
interface Ref<T> {
  value: T
}

 注意被ref包装之后需要.value 来进行赋值!!!!!
 

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
import {ref,Ref} from 'vue'
let message:Ref<string> = ref("我是message")
 
const changeMsg = () => {
   message.value = "change msg"
}
</script>
 
 
<style>
</style>
//--------------------------------ts两种方式
<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
import { ref } from 'vue'
let message = ref<string | number>("我是message")
 
const changeMsg = () => {
  message.value = "change msg"
}
</script>
 
 
<style>
</style>

 isRef

 判断是不是一个ref对象

import { ref, Ref,isRef } from 'vue'
let message: Ref<string | number> = ref("我是message")
let notRef:number = 123
const changeMsg = () => {
  message.value = "change msg"
  console.log(isRef(message)); //true
  console.log(isRef(notRef)); //false
  
}

shallowRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的

例子

修改其属性是非响应式的这样是不会改变的

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "小满"
})
 
const changeMsg = () => {
  message.value.name = '大满'
}
</script>
 
 
<style>
</style>

 例子2

这样是可以被监听到的修改value

import { Ref, shallowRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "小满"
})
 
const changeMsg = () => {
  message.value = { name: "大满" }
}

triggerRef 

强制更新页面DOM

这样也是可以改变值的

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
import { Ref, shallowRef,triggerRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "小满"
})
 
const changeMsg = () => {
  message.value.name = '大满'
 triggerRef(message)
}
</script> 
 
 
<style>
</style>

customRef

自定义ref 

customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set

<script setup lang="ts">
import { Ref, shallowRef, triggerRef, customRef } from 'vue'
 
function Myref<T>(value: T) {
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newVal: T) {
        console.log('set');
        value = newVal
        trigger()
      }
    }
  })
}
 
let message = Myref('小满')
const changeMsg = () => {
  message.value = '大满'
  // triggerRef(message)
}
</script> 

________________________________分段_______________________________________

diff算法:

关于我的个人理解,diff算法不是用暴力算法来解决DOM的相同或者不相同

特点 :

 (1.)  diff 算法就是将两个新旧的虚拟 DOM 进行对比并且返回一个 patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。

diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁

(2.) 比较只会在同层级别进行,不会跨层级比较。

 (3.) 在 diff 比较 过程汇总,循环从两边向中间比较。

(4.) diff 算法的步骤:

  1、用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文 档当中
2、当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异
3、把第二棵树所记录的差异应用到第一棵树所构建的真正的DOM树上(patch),视图就更新了

________________________________分段_______________________________________

组件基础

每一个.vue 文件呢都可以充当组件来使用

每一个组件都可以复用

 例如 helloWorld 充当子组件

 父组件使用

引入子组件 helloWorld 然后直接就可以去当标签去使用 (切记组件名称不能与html元素标签名称一样)

组件的生命周期

简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期

在我们使用Vue3 组合式API 是没有 beforeCreate 和 created 这两个生命周期的

 

onBeforeMount()

在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。

onMounted()

在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问。

onBeforeUpdate()

数据更新时调用,发生在虚拟 DOM 打补丁之前。

updated()

DOM更新后,updated的方法即会调用。

onBeforeUnmount()

在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

onUnmounted()

卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

配置全局组件

例如组件使用频率非常高(table,Input,button,等)这些组件 几乎每个页面都在使用便可以封装成全局组件

案例------我这儿封装一个Card组件想在任何地方去使用↓

<template>
  <div class="card">
     <div class="card-header">
         <div>标题</div>
         <div>副标题</div>
     </div>
     <div v-if='content' class="card-content">
         {{content}}
     </div>
  </div>
</template>
 
<script setup lang="ts">
type Props = {
    content:string
}
defineProps<Props>()
 
</script>
 
<style scoped lang='less'>
@border:#ccc;
.card{
    width: 300px;
    border: 1px solid @border;
    border-radius: 3px;
    &:hover{
        box-shadow:0 0 10px @border;
    }
 
    &-content{
        padding: 10px;
    }
    &-header{
        display: flex;
        justify-content: space-between;
        padding: 10px;
        border-bottom: 1px solid @border;
    }
}
</style>

卡片效果:

 使用方法:

在main.ts 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用(使用的是vite脚手架)

其次调用 component 第一个参数组件名称 第二个参数组件实例

import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset/index.less'
import Card from './components/Card/index.vue'
 
               //(调用↓):component
createApp(App).component('Card',Card).mount('#app')

↑这里Card全局组件就已经装载完毕了

Card组件的使用方法(直接在其他vue页面 立即使用即可 无需引入)↓

<template>
 <Card></Card>
</template>

配置局部组件:

<template>
  <div class="wraps">
    <layout-menu :flag="flag" @on-click="getMenu" @on-toogle="getMenuItem" :data="menuList" class="wraps-left"></layout-menu>
    <div class="wraps-right">
      <layout-header> </layout-header>
      <layout-main class="wraps-right-main"></layout-main>
    </div>
  </div>
</template>
 
<script setup lang="ts">
import { reactive,ref } from "vue";
import layoutHeader from "./Header.vue";
import layoutMenu from "./Menu.vue";
import layoutMain from "./Content.vue";
/*
这里分别使用到的组件(layoutHeader)(layoutMenu)(layoutMain)
*/
</script>

就是在一个组件内(A) 通过import 去引入别的组件(B) 称之为局部组件

应为B组件只能在A组件内使用 所以是局部组件

如果C组件想用B组件 就需要C组件也手动import 引入 B 组件

配置递归组件:

原理跟我们写js递归是一样的 自己调用自己 通过一个条件来结束递归 否则导致内存泄漏

案例递归树

在父组件配置数据结构 数组对象格式 传给子组件

type TreeList = {
  name: string;
  icon?: string;
  children?: TreeList[] | [];
};
const data = reactive<TreeList[]>([
  {
    name: "no.1",
    children: [
      {
        name: "no.1-1",
        children: [
          {
            name: "no.1-1-1",
          },
        ],
      },
    ],
  },
  {
    name: "no.2",
    children: [
      {
        name: "no.2-1",
      },
    ],
  },
  {
    name: "no.3",
  },
]);

子组件接收值 第一个script

type TreeList = {
  name: string;
  icon?: string;
  children?: TreeList[] | [];
};
 
type Props<T> = {
  data?: T[] | [];
};
 
defineProps<Props<TreeList>>();
 
const clickItem = (item: TreeList) => {
  console.log(item)
}

子组件增加一个script 定义组件名称为了 递归用 

<script lang="ts">
export default {
  name:"TreeItem"
}
</script>

template 

TreeItem 其实就是当前组件 通过import 把自身又引入了一遍 如果他没有children 了就结束

  <div style="margin-left:10px;" class="tree">
    <div :key="index" v-for="(item,index) in data">
      <div @click='clickItem(item)'>{{item.name}}
    </div>
    <TreeItem @on-click='clickItem' v-if='item?.children?.length' :data="item.children"></TreeItem>
  </div>
  </div>

效果图如下↓

关于递归组件的使用完整说明:

(创建了[一个Index组件和一个Test组件还有一个View组件])

{   

(1)Index组件是放需要递归的假数据和引入Test组件还有传值给Test数据(父子组件传值)

(2)Test组件是接收了父组件的值也就是Index组件的值,重点:(如果想要递归一个不确定层级的树结构,就需要再一次引入本身这个组件也就是再一次引入Test这个组件

(3)View组件就只是一个视图而已

}

下面展示组件的详细代码:

Index组件:

<script lang="ts" setup>
import { reactive } from 'vue';
import Test1 from './test-1.vue'
/*
定义一个不确定层级的树结构
为了演示递归组件
因为它的层级是不固定的
*/


type TestList = {
  name: string;
  icon?: string;
  children?: TestList[] | [];
};

const data = reactive<TestList[]>([
  {
    name: "no.1",
    children: [
      {
        name: "no.1-1",
        children: [
          {
            name: "no.1-1-1",
          },
        ],
      },
    ],
  },
  {
    name: "no.2",
    children: [
      {
        name: "no.2-1",
      },
    ],
  },
  {
    name: "no.3",
  },
])
</script>
<template>
<Test1 :data="data"></Test1>
</template>






<style lang="less" scoped></style>

Test组件:

重点:这里引入了本身也就是Test这个组件我给它起名叫:QuoteItself 后来我再使用了QuoteItself 这个组件(注意下方代码要牢记)

<script lang="ts" setup>
import QuoteItself from './test-1.vue'
/*
接收index里组件的值
通过defineProps接收数据
*/

type TestList = {
  name: string;
  icon?: string;
  children?: TestList[] | [];
};
 
type Props<T> = {
  data?: T[] | [];
};
 
defineProps<Props<TestList>>();
 
const clickItem = (item: TestList) => {
  console.log(item)
}
</script>


<template>
<!--(1) 这里循环了父组件传入的数据还有使用v-for进行循环这样 -->
<div v-for="(item,index) in data" :key="index">
<!--(2) 如果想要将数据展现出来需要用item里的name -->
{{item.name}}
<!--(3) 这里用v-if是确定index里的data下的children是否存在因为递归组件需要一个能结束它的条件 -->
<QuoteItself v-if="item?.children?.length" :data="item.children"></QuoteItself>
</div>
</template>


<style lang="less" scoped></style>

View组件:

<script setup lang='ts'>
</script>
<template>
    <div>

<!-- 这里是我在main.ts里注册了一个全局组件 -->
    <Test></Test>

    </div>
</template>

<style lang='less' scoped>
</style>

异步组件:

如果我用yarn build进行打包

 打包结果是这样的结构:

 这样vite会把所有的组件打包到一个js里这样是很消耗性能的。所谓异步组件就是把请求后台的异步任务封装成一个组件众,所周知的是如果请求后台数据会有可能因各种因素让后台数据无法展示到页面上,这样就会使页面出现白屏现象这样是很影响用户的用户体验。所以就需要把异步组件打包成另一个js;而不是像上面把所有组件都打包到一个js文件里面。

下面我将实现封装一个异步组件:

首先我需要创建一个假的后台接口我将它命名为data.json

 而后我要写一个接口去拿到这里的数据

type NameList = {
    name:String
}

export function Axios(url:string):Promise<NameList[]> {
   return new Promise((resolve)=>{
     let xhr:XMLHttpRequest = new XMLHttpRequest()

     xhr.open('GET',url)

     xhr.onreadystatechange=function(){
        if(xhr.readyState === 4 && xhr.status === 200){
          setTimeout(() => {
            resolve(JSON.parse(xhr.responseText))
          }, 2000);
        } 
     }

     xhr.send(null)
   })
}

在我的项目目录下创建一个用于测试使用的 test-4.vue 组件

随后引入刚刚写的接口

再去读取刚刚data.json里的数据

 随后我们去浏览器观察才发现在网页里并没有展示出我们想要的数据

 其实没展示出来也是正确的我们现在已经把这个组件变成异步组件了

所以我们需要用Vue的defineAsyncComponent()函数封装一下还需要用Vue里内置组件<Suspense></Suspense>来配合Test4的使用才能在页面上显示数据

<script setup lang='ts'>
import { defineAsyncComponent } from 'vue'
/*
import Test4 from '@/components/recursion&&Test/test-4.vue';
要删除上方这样的引入方式
需要采用的是下方的引入方式
*/
const Test4 = defineAsyncComponent(() => import('@/components/recursion&&Test/test-4.vue'))
</script>

<template>
    <div class="Menu">
        菜单区域

        <Suspense>
            <template #default>
           <!--这里就是放入Test4的地方上方的template提供了两个插槽一个是#default
还有一个是#fallback;   #fallback是数据在等待的时候显示的内容
-->
                <Test4></Test4>
            </template>
            <template #fallback>
                <div>
                    loading...
                </div>
            </template>
        </Suspense>

    </div>
</template>

页面大概就是以下的效果:

 当我再次使用yarn build打包之后:

 可以看到多了一个js文件这个文件就是我们分出来的异步组件

----------------------------------------------------------分段-----------------------------------------------------------------

watch侦听器

watch是能监听复杂的数据结构的,但是它会有个bug

 

 

 

现在我传入了一个复杂的数据结构

然后watch本身有第二个参数deep这是深度监听如果不传入第二个参数是不能监听复杂的数据结构

我没再来看看变化

 页面效果:

 如果watch监听的是reactive的数据那么就不需要写第二个参数“deep”

watch的几种语法写法:

  (1)如果你想监听多个数据可以用数组的方式写列如:

let Test1 = ref<string>('');


let Test2 = ref<string>('');


watch([Test1,Test2 ], (newVal, oldVal) => {
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
})

(2)如果你想只监听一个数据结构可以用回调函数的方式写列如:

let data = reactive({
 name1:'<>',
 name2:'[]'
})

watch(()=>data.name1,(newVal, oldVal) => {
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
})

----------------------------------------------------------分段-----------------------------------------------------------------

认识watchEffect高级侦听器:

watchEffect

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次

let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect(() => {
    //console.log('message', message.value);
    console.log('message2', message2.value);
})

 清除副作用

就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖

import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
        
    })
//oninvalidate()里会比watchEffect函数里所有东西都提前调用
    console.log('message2', message2.value);
})

停止跟踪 watchEffect 返回一个函数 调用之后将停止更新

const stop =  watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
 
    })
    console.log('message2', message2.value);
},{
    flush:"post",
    onTrigger () {
 
    }
})
stop()

更多的配置项

副作用刷新时机 flush 一般使用post

presyncpost
更新时机组件更新前执行强制效果始终同步触发组件更新后执行

onTrigger  可以帮助我们调试 watchEffect

import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect((oninvalidate) => {
    //console.log('message', message.value);
    oninvalidate(()=>{
 
    })
    console.log('message2', message2.value);
},{
    flush:"post",
    onTrigger () {
        
    }
})

----------------------------------------------------------分段-----------------------------------------------------------------

<think>由于问题涉及多个技术栈,我们将分步骤搭建项目。这里将详细说明如何使用Vue3Vue Router、Pinia、Eslint、TypeScript以及Pinia持久化来搭建一个项目。 步骤: 1. 创建Vue3项目(使用Vite) 2. 安装Vue Router 3. 安装Pinia和持久化插件 4. 配置TypeScript 5. 配置Eslint 6. 项目结构组织 ### 1. 创建Vue3项目 使用Vite创建Vue3项目,选择TypeScript模板。 ```bash npm create vite@latest my-vue-app -- --template vue-ts cd my-vue-app npm install ``` ### 2. 安装Vue Router ```bash npm install vue-router@4 ``` 创建路由配置文件:在`src`目录下创建`router`文件夹,然后创建`index.ts`文件。 ```typescript // src/router/index.ts import { createRouter, createWebHistory, RouteRecordRaw } from &#39;vue-router&#39; const routes: Array<RouteRecordRaw> = [ { path: &#39;/&#39;, name: &#39;Home&#39;, component: () => import(&#39;@/views/Home.vue&#39;) // 需要创建Home视图 }, // 其他路由... ] const router = createRouter({ history: createWebHistory(), routes }) export default router ``` 在`main.ts`中引入路由: ```typescript // src/main.ts import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import router from &#39;./router&#39; createApp(App).use(router).mount(&#39;#app&#39;) ``` ### 3. 安装Pinia和持久化插件 ```bash npm install pinia npm install pinia-plugin-persistedstate ``` 创建Pinia实例并应用持久化插件: ```typescript // src/store/index.ts import { createPinia } from &#39;pinia&#39; import piniaPluginPersistedstate from &#39;pinia-plugin-persistedstate&#39; const pinia = createPinia() pinia.use(piniaPluginPersistedstate) export default pinia ``` 在`main.ts`中使用Pinia: ```typescript // src/main.ts import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import router from &#39;./router&#39; import pinia from &#39;./store&#39; createApp(App).use(router).use(pinia).mount(&#39;#app&#39;) ``` 创建一个store示例(例如用户store): ```typescript // src/store/user.ts import { defineStore } from &#39;pinia&#39; export const useUserStore = defineStore(&#39;user&#39;, { state: () => ({ name: &#39;John Doe&#39;, age: 30, }), actions: { updateName(name: string) { this.name = name }, }, persist: true // 开启持久化 }) ``` ### 4. 配置TypeScript Vite创建的Vue3项目已经自带了TypeScript,我们只需要确保`tsconfig.json`配置正确。可以根据需要调整配置。 ### 5. 配置Eslint 安装Eslint及相关插件: ```bash npm install eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev ``` 创建`.eslintrc.js`文件: ```javascript module.exports = { env: { browser: true, es2021: true, node: true, }, extends: [ &#39;eslint:recommended&#39;, &#39;plugin:vue/vue3-recommended&#39;, &#39;plugin:@typescript-eslint/recommended&#39;, ], parser: &#39;vue-eslint-parser&#39;, parserOptions: { ecmaVersion: &#39;latest&#39;, parser: &#39;@typescript-eslint/parser&#39;, sourceType: &#39;module&#39;, }, plugins: [&#39;vue&#39;, &#39;@typescript-eslint&#39;], rules: { // 自定义规则 &#39;vue/multi-word-component-names&#39;: &#39;off&#39;, // 关闭组件名必须多单词的限制 }, } ``` 在`package.json`中添加lint脚本: ```json "scripts": { "lint": "eslint . --ext .vue,.ts,.tsx --fix" } ``` ### 6. 项目结构 建议项目结构如下: ``` src/ ├── assets/ ├── components/ ├── views/ │ ├── Home.vue │ └── ... ├── router/ │ └── index.ts ├── store/ │ ├── index.ts │ └── user.ts ├── App.vue └── main.ts ``` ### 示例代码 这里给出一个简单的`Home.vue`示例,展示如何使用Pinia store: ```vue <template> <div> <h1>Home Page</h1> <p>User: {{ userStore.name }}</p> <button @click="changeName">Change Name</button> </div> </template> <script setup lang="ts"> import { useUserStore } from &#39;@/store/user&#39; const userStore = useUserStore() const changeName = () => { userStore.updateName(&#39;Jane Doe&#39;) } </script> ``` ### 解释 - 使用Vite创建项目,速度快且配置简单。 - Vue Router用于路由管理,采用历史模式。 - Pinia作为状态管理库,替代Vuex,更轻量且支持Composition API。 - Pinia持久化插件`pinia-plugin-persistedstate`可以将store的状态持久化到localStorage或sessionStorage。 - TypeScript提供类型支持,减少错误。 - Eslint统一代码风格,提高代码质量。 ### 注意事项 - 持久化配置:在store定义中设置`persist: true`即可开启,也可以配置详细选项(如存储方式、key等)。 - 路由懒加载:使用`() => import(&#39;...&#39;)`实现懒加载,优化首屏加载速度。 §§相关问题§§: 1. 如何在Vue Router中配置动态路由? 2. Pinia持久化可以存储到cookie吗?如何配置? 3. 如何配置Eslint和Prettier一起工作? 4. 在TypeScript中如何为Vue组件定义Props的类型?</think>下面我将详细介绍如何使用 Vue 3 + Vue Router + Pinia + ESLint + TypeScript + Pinia 持久化搭建项目,并提供完整的代码示例和解释。 ### 完整项目搭建指南 #### 1. 创建项目(使用 Vite) ```bash npm create vite@latest my-vue-app -- --template vue-ts cd my-vue-app npm install ``` #### 2. 安装必要依赖 ```bash npm install vue-router@4 pinia pinia-plugin-persistedstate npm install -D eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin ``` #### 3. 项目结构 ```markdown src/ ├── assets/ ├── components/ ├── router/ │ └── index.ts ├── store/ │ ├── index.ts │ └── userStore.ts ├── views/ │ ├── Home.vue │ └── About.vue ├── App.vue ├── main.ts .eslintrc.cjs ``` #### 4. 配置 Vue Router ```typescript // src/router/index.ts import { createRouter, createWebHistory } from &#39;vue-router&#39; import Home from &#39;../views/Home.vue&#39; import About from &#39;../views/About.vue&#39; const routes = [ { path: &#39;/&#39;, component: Home }, { path: &#39;/about&#39;, component: About } ] const router = createRouter({ history: createWebHistory(), routes }) export default router ``` #### 5. 配置 Pinia 和持久化 ```typescript // src/store/index.ts import { createPinia } from &#39;pinia&#39; import piniaPluginPersistedstate from &#39;pinia-plugin-persistedstate&#39; const pinia = createPinia() pinia.use(piniaPluginPersistedstate) export default pinia ``` ```typescript // src/store/userStore.ts import { defineStore } from &#39;pinia&#39; export const useUserStore = defineStore(&#39;user&#39;, { state: () => ({ name: &#39;John&#39;, age: 30, preferences: { theme: &#39;dark&#39;, notifications: true } }), actions: { updateName(newName: string) { this.name = newName } }, persist: { paths: [&#39;name&#39;, &#39;preferences.theme&#39;], // 指定持久化字段 storage: localStorage // 默认使用 localStorage } }) ``` #### 6. 配置 ESLint ```javascript // .eslintrc.cjs module.exports = { env: { browser: true, es2021: true }, extends: [ &#39;eslint:recommended&#39;, &#39;plugin:vue/vue3-recommended&#39;, &#39;plugin:@typescript-eslint/recommended&#39; ], parser: &#39;vue-eslint-parser&#39;, parserOptions: { parser: &#39;@typescript-eslint/parser&#39;, ecmaVersion: &#39;latest&#39;, sourceType: &#39;module&#39; }, plugins: [&#39;vue&#39;, &#39;@typescript-eslint&#39;], rules: { &#39;vue/multi-word-component-names&#39;: &#39;off&#39;, &#39;@typescript-eslint/no-explicit-any&#39;: &#39;off&#39;, &#39;vue/html-self-closing&#39;: [&#39;error&#39;, { html: { void: &#39;always&#39;, normal: &#39;never&#39;, component: &#39;always&#39; } }] } } ``` #### 7. 配置 main.ts ```typescript // src/main.ts import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import router from &#39;./router&#39; import pinia from &#39;./store&#39; const app = createApp(App) app.use(router) app.use(pinia) app.mount(&#39;#app&#39;) ``` #### 8. 示例组件使用 ```vue <!-- src/views/Home.vue --> <script setup lang="ts"> import { useUserStore } from &#39;@/store/userStore&#39; const userStore = useUserStore() </script> <template> <div> <h1>Home Page</h1> <p>User: {{ userStore.name }}</p> <input v-model="userStore.name" /> <p>Theme: {{ userStore.preferences.theme }}</p> </div> </template> ``` #### 9. 配置 package.json 脚本 ```json { "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build", "lint": "eslint . --ext .vue,.ts,.tsx --fix", "preview": "vite preview" } } ``` ### 关键配置解释 1. **Pinia 持久化**: - 使用 `pinia-plugin-persistedstate` 插件 - 在 store 定义中添加 `persist` 配置 - `paths` 可指定需要持久化的具体字段 - 默认使用 localStorage,可替换为 sessionStorage 2. **ESLint 配置**: - 使用 Vue3 推荐规则 + TypeScript 规则 - 关闭多单词组件名限制(开发时可开启) - 配置 Vue/HTML 自闭合标签规则 3. **TypeScript 支持**: - Vite 默认提供 Vue+TS 模板 - 组件中使用 `<script setup lang="ts">` 语法 - 为 Pinia store 提供完整的类型推断 4. **路由配置**: - 使用 createWebHistory 实现 SPA 路由 - 组件使用懒加载(示例中为静态引入) ### 运行项目 ```bash npm run dev npm run lint # 检查代码规范 ``` ### 持久化效果验证 1. 在页面修改用户名 2. 刷新页面后数据保持不变 3. 检查 localStorage 中的 `pinia.user` 值
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值