Vue3 + Vite 学习
Vue3官网:Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)
1.Vue3基本介绍
① Vue3的优点
② Vue3 + Vite 项目创建
npm create vue@latest
可选功能配置:
③ 项目目录与关键文件
2.组合式API
① setup选项 与 setup语法糖
a. setup 特征
-
执行时机比beforeCreate早
-
setup 函数中 ,获取不到 this
-
数据和函数需要在 setup 中 return 才能在模板中使用
b. 选项式与语法糖对比
# 原始复杂代码(选项式代码) <script> export default{ setup(){ // 数据 const message = 'Hello Vue3' // 函数 const logMessage = () => { console.log(message) } return{ message, logMessage } } } </script> # setup 语法糖代码 <script setup> // 数据 const message = 'this is a message' // 函数 const logMessage = () => { console.log(message); } </script>
② reactive 和 ref 函数
a. reactive 函数
作用:接收 对象类型数据的参数 传入并返回一个 响应式的对象
<script setup> // 导入 import { reactive } from 'vue' // 执行函数 传入参数 变量接收 const state = reactive({ count:100 }) const setCount = () => { state.count++ } </script> <template> <div> {{ state.count }} </div> <button @click="setCount">+1</button> </template>
b. ref 函数 ( 推荐使用 )
作用:接收 简单类型或者对象类型的数据 传入并返回一个 响应式的对象
注意点:1. 在 script 中访问数据,需要通过 .value
2. 在 template 中, 不需要加 .value
<script setup> // 导入 import { ref } from 'vue' // 执行函数 传入参数 变量接收 const count = ref(0) const setCount = () => { // 获取 ref 对象中的数据 count.value++ } </script> <template> <div>{{ count }}</div> <button @click="setCount">+1</button> </template>
c. reactive VS ref
-
reactive 不能处理简单类型的数据
-
ref 参数类型支持更好但是必须通过 .value 访问修改
-
ref 函数的内部实现依赖于 reactive 函数
③ computed函数
核心步骤:
1.导入 computed 函数
2.执行函数在回调参数中 return 基于响应式数据做计算的值,用变量接收
const 计算属性 = computed(() => { return 计算返回的结果 }) <script setup> // 导入 import { computed, ref } from 'vue'; // 声明数据 const list = ref([1,2,3,4,5,6,7,8]) // 计算属性:基于 list 派生一个计算属性,从 list 中过滤出 > 2 的数 const computedList = computed(() => { return list.value.filter(item => item > 3) }) // 定义一个修改数组的方法 const addfn = () => { list.value.push(666) } </script> <template> <div> <div>计算前列表:{{ list }}</div> <div>计算后列表:{{ computedList }}</div> <button @click="addfn" type="button">修改数组</button> </div> </template>
注意:1. 计算属性中不应该出现"副作用":如异步请求/修改dom;
2. 避免直接修改计算属性的值。
④ watch函数
作用:侦听一个或者多个数据的变化,数据变化时执行回调函数
两个额外参数:
immediate ( 立即执行 ) :在侦听器创建时立刻触发回调,响应式数据变化后继续执行回调
deep ( 深度侦听 ) :当ref(复杂类型) 时,需开启 deep 监视复杂类型内部数据的变化
<script setup> // 导入 import { ref, watch } from 'vue'; // 声明数据( 对象 ) const count = ref(0) const nickname = ref('张三') const userInfo = ref({ name: 'zs', age: 18 }) //定义方法 const changeCount = () => { count.value++ } const changeNickname = () => { nickname.value = '李四' } const setUserInfo = () => { userInfo.value.age++ userInfo.value.name = 'ls' } // 1. 监听单个数据的变化 // watch(ref对象,(newValue, oldValue) => { ... }) watch(count, (newCount, oldCount) => { console.log(newCount, oldCount); }) // 2.监听多个数据的变化 // watch([ref对象1,ref对象2], (newArr,oldArr) => { ... }) watch([count, nickname], (newArr, oldArr) => { console.log(newArr, oldArr); }) // 3.immediate 立刻执行 watch(count, (newCount, oldCount) => { console.log(newCount, oldCount); },{ immediate: true }) // 4.deep 深度监视,默认 watch 进行的是 浅层监视 // const ref1 = ref(简单类型) 可以直接监视 // const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化 watch(userInfo,(newValue) => { console.log(newValue); },{ deep: true }) // 5. 在不开启deep的前提下,精确侦听对象的某个属性 watch(() => userInfo.value.age, (newValue, oldValue) => { console.log(newValue, oldValue); }) </script> <template> <div>{{ count }}</div> <button @click="changeCount">修改数字</button> <div>{{ nickname }}</div> <button @click="changeNickname">修改昵称</button> <div>-----------------------</div> <div>{{ userInfo }}</div> <button @click="setUserInfo">修改userInfo</button> </template>
⑤ 生命周期函数
<script setup> import { onMounted } from "vue" // beforeCreate 和 create 的相关代码 // 一律放在 setup 中执行 const getList = () => { setTimeout(() => { console.log('发送请求,获取数据') },2000) } // 一进入页面的请求 getList() // mounted生命周期中执行 onMounted(() => { console.log('mounted生命周期函数-1') }) // 写成函数的调用方式可多次调用,并不会冲突,而是按照顺序以此执行 onMounted(() => { console.log('mounted生命周期函数-2') }) </script>
⑥ 父子通信
a. 父传子
基本思想:1. 父组件中给 子组件绑定属性
2. 子组件内部通过 props 选项接收
// 父组件 <script setup> import { ref } from 'vue' // 引入子组件 import SonCom from "./components/son-com.vue" const num = ref(99) const numAdd = () => { num.value += 10 } </script> <template> <div> <h3> 父组件 - {{ num }} <button @click = "numAdd"> + </button> </h3> <!-- 1. 绑定属性 message 动态传递 num --> <!-- 给子组件,添加属性的方式传值 --> <SonCom message="this is a message" :num=num></SonCom> </div> </template> ------------------------------------------------------------------------- // 子组件 <script setup> // 2. 通过 defineProps "编译器宏" 接收子组件传递的数据 const props = defineProps({ message: String, num: Number }) console.log(props.message) console.log(props.num) </script> <template> <div class="son"> <!-- 对于 props 传递过来的数据,模板中可以直接使用 --> 子组件 - {{ message }} - {{ num }} </div> </template>
b. 子传父
基本思想:1. 父组件中给 子组件标签通过 @ 绑定事件
2. 子组件内部通过 emit 方法触发事件
// 父组件 <script setup> import { ref } from 'vue' // 引入子组件 import SonCom from "./components/son-com.vue" const num = ref(99) // 3. 触发自定义事件,并传递参数 const getNum = (newNum) => { num.value = newNum } </script> <template> <div> <h3>父组件 - {{ num }}</h3> <!-- 1. 通过 @ 绑定自定义事件 --> <SonCom @get_num = getNum :num=num></SonCom> </div> </template> ------------------------------------------------------------------------- // 子组件 <script setup> const props = defineProps({ num: Number }) // 2. 通过 defineEmits 编译器宏生成 emit 方法 const emit = defineEmits(['get_num']) const changeNum = () => { emit('get_num',66) } </script> <template> <div class="son"> <!-- 对于 props 传递过来的数据,模板中可以直接使用 --> 子组件 - {{ num }} <button @click="changeNum"></button> </div> </template>
⑦ 模板引用
概念:通过 ref标识 获取真实的 dom对象或者组件实例对象
模板引用步骤与注意事项:
-
调用 ref 函数,生成一个 ref对象 const 对象名 = ref(对象). 通过 ref 标识,进行绑定 ref = "对象名"
-
通过 ref对象.value 即可访问绑定的数据(必须渲染完成以后)
-
在默认情况下<script setup>语法糖下 组件内部的属性和方法时不开放给父组件访问的可以通过defineExpose 编译宏指定哪些属性和方法允许访问
<script setup> import TestCom from "./components/test-com.vue" import { onMounted, ref } from 'vue' // 获取 dom const inp = ref(null) // 1.进入页面就聚焦 onMounted(() => { // console.log(inp.value) // inp.value.focus() }) // 2.点击按钮聚焦 const clickFn = () => { inp.value.focus() } // 获取 组件 const testref = ref(null) const getCom = () => { console.log(testref.value); } </script> <template> <div> <input ref="inp" type="text"> <button @click="clickFn">获取焦点</button> </div> <TestCom ref="testref"></TestCom> <button @click="getCom">获取组件</button> </template> -------------------------------------------- [test-com.vue] <script setup> const count = 999 const sayHi = () => { console.log("打招呼"); } defineExpose({ count, sayHi }) </script> <template> <div> 测试组件 - {{ count }} </div> </template>
⑧ provide 和 inject
作用:顶层组件向任意的底层组件 传递数据和方法,实现 跨层组件通信
跨层传递数据:
-
顶层组件通过 provide函数提供 数据
-
底层组件通过 inject函数获取 数据
[top-com.vue] <script setup> import { provide, ref } from 'vue' import MiddleCom from './components/middle-com.vue' // 1. 跨层传递普通数据 provide('theme-color','blue') // 2. 跨层传递响应式数据 const count = ref(100) provide('count',count) setTimeout(() => { count.value = 500 },2000) // 3. 跨层传递函数 provide('changeCount', (newCount) => { count.value = newCount }) </script> <template> <div> <h1>顶层组件</h1> <MiddleCom></MiddleCom> </div> </template> ---------------------------------------------------------- [middle-com.vue] <script setup> import BottomCom from './bottom-com.vue' </script> <template> <div> <h2>中间层组件</h2> <BottomCom></BottomCom> </div> </template> ---------------------------------------------------------- [bottom-com.vue] <script setup> import { inject } from 'vue' const themeColor = inject('theme-color') const count = inject('count') const changeCount = inject('changeCount') const clickFn = () =>{ changeCount(1000) } </script> <template> <div> <h3>底层组件-{{ themeColor }}-{{ count }}</h3> <button @click="clickFn">更新count</button> </div> </template>
3. 新特性
① defineOptions
作用:用于定义 Options API 的选项,除 props,emits,expose,slots外。
<script setup> defineOptions({ name:'NameIndex' }) </script> <template> <div> defineOptions </div> </template>
② defineModel
<script setup> import { ref } from 'vue' import MyInput from './components/my-input.vue' const txt = ref('123456') </script> <template> <div> <MyInput v-model="txt"></MyInput> {{ txt }} </div> </template> ----------------------------------------------- <script setup> import { defineModel } from 'vue' const modelValue = defineModel() </script> <template> <div> <input type="text" :value="modelValue" @input="e => modelValue = e.target.value" > </div> </template> -------------------------------------------- [vite.config.js] export default defineConfig({ plugins: [ vue({ script:{ defineModel:true } }), ]
4. Pinia
① 安装与配置
npm install pinia [main.js] import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const pinia = createPinia() const app = createApp(App) // 注意 pinia 与 vue 的版本匹配,否则会出现报错: // 与类型“Pinia”和“Plugin<[]>”相比,堆栈深度过高。ts(2321) app.use(pinia) app.mount('#app')
② Store
[src/store/.js] import { defineStore } from "pinia" import axios from "axios" import { computed, ref } from "vue" // 定义 Store // defineStore(仓库的唯一标识,() => {...}) export const usePiniaStore = defineStore('Pinia',() => { // 声明数据 => state const count = ref(100) const msg = ref('pinia') const channelList = ref([]) // 声明操作数据的方法 => action (普通函数) const addCount = () => count.value++ const subCount = () => count.value-- // 支持异步获取数据 const getList = async () => { const { data: { data }} = await axios.get('http://geek.itheima.net/v1_0/channels') channelList.value = data.channels console.log(data.channels) } // 声明基于数据派生的计算属性 => getters (computed) const double = computed(() => count.value * 2) const half = computed(() => count.value / 2) // 声明 getters 相关 暴露数据、方法和计算属性,组件可直接使用 return { count, msg, addCount, subCount, double, half, channelList, getList } }) ------------------------------------------- [component/.vue] <script setup> <!-- 导入并调用 --!> import { storeToRefs } from 'pinia' import { usePiniaStore } from './store/piniaTest' const piniaStore = usePiniaStore() // 若直接解构不处理,数据会丢失响应式,需要添加 storeToRefs // 但解构方法不需要添加 const { count, msg } = storeToRefs(piniaStore) const { addCount } = piniaStore </script> <template> <div>Pinia - {{ msg }} - {{ count }} - {{ piniaStore.double }} - {{ piniaStore.half }} </div> <div> <button @click="piniaStore.subCount"> - </button> - {{ piniaStore.count }} - <button @click="addCount"> + </button> <hr> <button @click="piniaStore.getList">获取数据</button> <ul> <li v-for="item in piniaStore.channelList" :key="item.id">{{ item.name }}</li> </ul> </div> </template>
③ Pinia持久化插件
安装: npm i pinia-plugin-persistedstate -D // 使用/模块内 export const useStore = defineStore('use-store', () => { const ... return { ... } }, { persist:true } )
④ Pinia 配置仓库统一管理
// stores/index.js import { createPinia } from 'pinia' import persist from 'pinia-plugin-persistedstate' const pinia = createPinia() pinia.use(persist) export default pinia // 统一导出 export * from './modules/user' export * from './modules/counter' // main.js配置 import pinia from '../src/stores/index' app.use(pinia) // vue中调用方式: <script setup> improt {useUserStore, useCountStore} from '../src/stores' const userStore = useUserStore() const countStore = useCountStore() </script>