都是基于最新的Vue3版本 "vue": "^3.4.29"
1 vue组建样式设置
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" class="test-baz"/>
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
.test-baz {
color: red;
}
</style>
父组建里面的test-baz并不会被字组建HelloWorld用到,字组建只会用它自己里面的test-baz样式。
2 vue中getter函数理解
在JavaScript中箭头函数 () => x.value + y.value
是一个匿名函数,并不是getter函数。
在Vue中,watch
函数用于观测一个表达式或函数(通常称为“getter”)的变化。当这个表达式的值发生变化时,watch
中定义的回调函数会被触发。因此,尽管上面提到的箭头函数 () => x.value + y.value
本身并不是一个严格意义上的JavaScript getter函数(如在对象属性定义中的那种),在Vue框架的上下文中,它可以被视作一个“getter”功能的实现,因为它用来获取并计算状态(即 x.value
和 y.value
的和)。
这里的匿名箭头函数 ( ) => x.value + y.value
被Vue用作一个getter来监控依赖项(x.value
和 y.value
)的变化。当这些依赖项的值发生变化导致计算结果(即两者的和)改变时,Vue就会调用第二个参数中的回调函数,打印出新的求和结果。因此,在Vue的语境下,将其称为一个“getter函数”是为了强调其观测数据变化并响应的功能,而非严格遵循JavaScript语言中getter的定义。
3 watch和 watchEffect
修改状态数据就是副作用
const testNumber = ref(0)
// 可以直接侦听一个 ref
watch(testNumber, async (newValue, oldValue) => {
testNumber.value = testNumber.value + 1
},)
上面代码会造成循环依赖报错
Uncaught (in promise) Maximum recursive updates exceeded. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.
可以修改成只会执行一次,
const testNumber = ref(0)
// 可以直接侦听一个 ref
watch(
testNumber,
async (newValue, oldValue) => {
testNumber.value = testNumber.value + 1
},
{ once: true }
)
或者使用watchEffect
const testNumber = ref(0)
watchEffect(async () => {
testNumber.value+=1
})
watchEffect修改自己内部ref不会造成循环依赖。
JPT:
在Vue中,watch
用于观察数据变化并在变化时执行一些操作。关于“避免在发生副作用时追踪依赖”的说法,这实际上与Vue的响应式系统如何处理数据变化和依赖收集有关,尤其是在Vue 3引入的Composition API中,通过ref
或reactive
创建的状态自动具有响应式特性。
当你在watch
的回调函数内部修改正在被观察的数据时,如果没有正确处理,可能会导致无限循环或者不期望的行为。这是因为修改数据会再次触发依赖更新,进而重新触发watch
,形成一个循环。为了避免这种情况,Vue在设计上要求开发者注意副作用的管理。
const testNumber = ref(0)
// 可以直接侦听一个 ref
watch(
testNumber,
async (newValue, oldValue) => {
testNumber.value = testNumber.value + 1
}
)
执行这段代码会导致以下问题:
- 副作用:直接修改响应式数据是一种副作用,因为它改变了应用的状态。
-
依赖追踪与无限循环:修改
testNumber.value
会触发表达式的重新计算,如果这个修改又满足watch
的触发条件,那么watch
回调将再次执行,从而可能形成无限循环。
为了避免这种情形,Vue提供了几种策略:
-
使用异步
watch
: 如果你的副作用需要修改触发它的数据,考虑使用异步watch
(通过将回调设为返回Promise的函数,或使用immediate
和flush: 'post'
选项)。这样Vue会在当前tick的微任务队列结束后再执行副作用,避免了即时的循环触发。 -
在副作用中谨慎修改数据: 确保修改数据的操作不会立即触发正在运行的
watcher
的重新计算。有时候可以通过条件判断来避免不必要的修改。 -
使用
watchEffect
: 如果你的副作用逻辑依赖于多个状态,并且修改这些状态是预期行为的一部分,考虑使用watchEffect
。watchEffect
更加灵活,会自动管理依赖,并确保副作用在依赖变更时执行,但同样需要注意避免循环调用的问题。
总之,“避免在发生副作用时追踪依赖”意味着在执行可能改变响应式数据的操作时,要小心不要无意中触发了自己正在依赖的数据变化,从而进入无限循环或不可控的状态变更。正确的做法是合理安排数据修改的位置和时机,以及利用Vue提供的机制来控制副作用的执行。
4 DOM内模板和<template>区别
在Vue中,<template>
标签有特定的用途,根据上下文的不同,它扮演着不同的角色。以下是两种主要的使用场景:
1. Vue单文件组件内的 <template>
在Vue的单文件组件(SFC,Single File Component)中,<template>
标签用于包裹组件的HTML模板部分。这部分定义了组件的结构和布局,Vue会解析这个模板并将其编译为虚拟DOM。例如:
vue
<template>
<div class="example">
<h1>{{ message }}</h1>
<button @click="reverseMessage">Reverse Message</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
};
},
methods: {
reverseMessage() {
this.message = this.message.split('').reverse().join('');
}
}
};
</script>
在这个例子中,<template>
包含了组件的HTML结构,其中可以绑定数据和事件处理器。
2. DOM内模板插值
在Vue 2中,有一种较为少见的用法是在DOM中直接使用模板字符串插值,这种方式不推荐在现代Vue应用中使用,但在某些老版本的应用或特定场景下可能存在。这通常指的是直接在HTML元素内使用双大括号 {{ }}
来插入数据。例如:
html
<div id="app">
{{ message }}
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello World!'
}
});
</script>
这里虽然没有直接使用<template>
标签,但双大括号 {{ }}
实现了数据绑定,相当于简单模板的使用。然而,对于复杂的结构和逻辑,应该使用Vue组件和<template>
标签。
总结
-
Vue SFC中的
<template>
定义了组件的HTML结构,是Vue组件的核心部分,支持指令、插槽等高级功能。 -
DOM内模板插值 使用双大括号
{{ }}
直接在HTML中插入数据,适用于非常简单的数据展示,但不适用于复杂组件结构和逻辑。
在现代Vue开发实践中,推荐使用Vue组件和单文件组件内的<template>
来构建应用界面,以充分利用Vue的响应式系统和组件化优势。
5 v-model
自定义修饰符
子组件
const [myDemo, myDemoModifiers] = defineModel('myDemo',{
// get我们这里不需要
set(value:string) {
if (myDemoModifiers.uppercase) {
return value?.toUpperCase();
}
},
});
<input type="text" v-model="myDemo" />
父组件
const name = ref(0)
<HelloWorld
msg=""
v-model:my-demo.uppercase="name"
/>
6 什么是构建步骤
构建步骤就是将源码转化为可执行的代码的过程。例如使用vite、webpack进行转换!
7 组件Class 与 Style 绑定
//父组件
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld
class="test-baz"
:style="{ backgroundColor:'pink'}"
/>
</template>
<style scoped>
</style>
//子组件
<script setup lang="ts">
</script>
<template>
<p class="input-color">Hi!</p>
</template>
<style scoped >
.test-baz {
color: green ;
}
.input-color {
color: red;
}
</style>
渲染结果
外部样式放在默认样式后面class="input-color test-baz"
按道理应该是test-baz的优先级比input-color高(错误认为)
当一个元素指定多个 class 时,class 的优先级与指定顺序无关,而是和 class 的定义顺序有关。后声明的优先级高。
.test-baz {
color: green ;
}
.input-color {
color: red;
}
input-color定义在下面所以优先级高。