vue 学习文档
背景:前几个月入职了新的公司,遵循我每次入职都要学新东西的原则 😄,这次也没让我失望,入职时候我也没问这边用的技术,由于我之前大部分时间都在写
react
,面试也没问vue
,可是让我没想到的是,第一个接收的项目是vue3
,完全不会,但仗着无论是哪个框架都是html css js
的逻辑,硬着头皮就上了,上班第一天就开撕代码,可是看着看着我就晕了,这个是啥,哪个又是啥。经过这几个月的熟悉,现在虽然说常用的方法也都会用,但写出来的未必是最优的代码,我也在慢慢学习,这里就暂且记录我的学习笔记,随着学习的深入会更新,如果也有刚开始学vue3
的,可以一起探讨!!!
我在多年前也写过vue2
,可那是很多年前的事情,已经翻篇了,所以这里我就不进行对比了,不要问我,我已经想不起来了,哈哈哈哈哈哈哈 😂
如果想了解vue2
的,可以看这篇,期望能给你帮助
再啰嗦两句:
既然要学
vue3
,强烈建议大家直接用组合式的风格
,从vue2
过渡来的对于选项式更熟悉,组合式的写法更开放,局限性更小。当然你想继续用原来的方式,也不是不可以,官方也是支持的,只是我个人建议。 什么是组合式风格
基础常用功能
stup
钩子
stup
是一个函数,用于配置组件的初始状态、引入响应式数据、设置方法等。
setup
函数接收两个参数:
props
:用于接收来自父组件传递的props
属性。context
:提供了一个上下文对象,在setup
函数内部使用,包含了一些常用的属性和方法,如attrs
、slots
、emit
等。
官网推荐
: 对于结合单文件组件使用的组合式 API
,推荐通过 <script setup>
以获得更加简洁及符合人体工程学的语法。
简单说就是下面方式添加,就是每个页面都用它包一下即可:
xml
<script lang="ts" setup>
// 所有的js操作都写在这里面即可
// 引入变量 / 函数 / 组件
import b from './mock.ts'
// 接收props 参数
const props = defineProps({
toolbarList: {
type: Array<IToolbarItem>,
required: true,
},
activeToolType: {
type: Number,
required: true,
}
});
// 定义变量
const a=2;
console.log(a)
// 定义函数
const fn = ()=>{ dosomething...}
</script>
ref
声明一个响应式状态
这个
ref
有点类似react
里面的useState
,刚开始我理解这个名字就以为是我们常用的ref
,其实不是,它是定义变量的一种形式,我也理解了好一阵儿才接受
定义状态:
csharp
import {ref} from 'vue';
const count = ref(0);
修改状态:
ini
const count = ref(0);
console.log(count.value); // 0
count.value = 2;
console.log(count.value) // 2
注意,在模板中使用 ref
时,我们不 需要附加 .value
。为了方便起见,当在模板中使用时,ref
会自动解包 (有一些注意事项)。
你也可以直接在事件监听器中改变一个 ref
:
css
<button @click="count++"> {{ count }} </button>
reactive
声明复杂响应式状态
在上面的声明状态中,我们得知ref 可以声明一个普通变量,例如:字符串、布尔值、数值类型,这一讲reactive 更适合用在复杂对象上,
如何定义:
javascript
import { reactive } from 'vue'
const state = reactive({ count: 0 })
在模版中使用:
css
<button @click="state.count++">
{{ state.count }}
</button>
computed
监听一个可变的值,进行实时计算
默认创建的是一个
只读
的计算属性ref
:
xml
<script lang="ts" setup>
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 不生效
console.log(plusOne.value) // 2
</script>
创建一个可写的计算属性
ref
:
ini
<script lang="ts" setup>
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
</script>
watch
watch()
默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
侦听一个 getter
函数:
scss
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
// 处理监听到的结果...
}
)
侦听一个 ref
:
scss
const count = ref(0)
watch(count, (count, prevCount) => {
// 处理监听到的结果...
})
接收两个参数:
第一个参数是新值,第二个参数是变化前的值
scss
const state = reactive({ count: 0 })
watch(
() => state,
(newValue, oldValue) => {
// newValue === oldValue
}
)
深层级监听
当使用
getter
函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用{ deep: true }
强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。
javascript
const state = reactive({ count: 0 })
watch(
() => state,
(newValue, oldValue) => {
// newValue === oldValue
},
{ deep: true }
)
当直接侦听一个响应式对象时,侦听器会自动启用深层模式:
scss
const state = reactive({ count: 0 })
watch(state, () => {
// 深层级变更状态所触发的回调
})
停止侦听器:
scss
const stop = watch(source, callback)
// 当已不再需要该侦听器时:
stop()
watchEffect
相当于
react
中的useEffect
,只是它不用写依赖值,当值发生变化后自动执行
默认情况下,侦听器将在组件渲染之前执行。设置flush: 'post'
将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置flush: 'sync'
来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。
返回值是一个用来停止该副作用的函数。
scss
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 输出 0
count.value++
// -> 输出 1
停止侦听器:
scss
const stop = watchEffect(() => {})
// 当不再需要此侦听器时:
stop()
unref
unref()是一个便捷的实用函数,在你的值可能是一个ref
的情况下特别有用。在一个非ref
上调用.value
会抛出一个运行时错误,unref()
在这种情况下就很有用:
csharp
import { ref, unref } from 'vue'
const count = ref(0)
const unwrappedCount = unref(count)
// same as isRef(count) ? count.value : count`
如果unref()
的参数是一个ref
,就会返回其内部值。否则就返回参数本身。这是的val = isRef(val) ? val.value : val
语法糖。
toRefs
和 toRef
如果你确实需要解构 props
对象,或者需要将某个 prop 传到一个外部函数中并保持响应 性,那么你可以使用 toRefs()
和 toRef()
这两个工具函数:
javascript
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
readonly
返回一个只读的值
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
scss
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 用来做响应性追踪
console.log(copy.count)
})
// 更改源属性会触发其依赖的侦听器
original.count++
// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!
isRef()
判断当前的值是不是ref
定义出来的,返回布尔值
csharp
let foo: unknown
if (isRef(foo)) {
// foo 的类型被收窄为了 Ref<unknown>
foo.value
}
unref()
如果参数是 ref
,则返回内部值,否则返回参数本身
是
val = isRef(val) ? val.value : val
计算的一个语法糖。
- 示例
scss
function useFoo(x: number | Ref<number>) {
const unwrapped = unref(x)
// unwrapped 现在保证为 number 类型
}
provide
提供一个值,可以被后代组件注入
配合
inject
使用,provide
来提供值,inject
来接收,如果只是提供,不接收,也是没有意义的。
provide()
接受两个参数:第一个参数是要注入的key
,可以是一个字符串或者一个symbol
,第二个参数是要注入的值。
xml
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 提供静态值
provide('foo', 'bar')
// 提供响应式的值
const count = ref(0)
provide('count', count)
// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>
inject
注入一股由祖先组件或整个应用(通过app.provide()
) 提供的值
需要配合
provide
使用,需要在祖先组件通过provide
注入后,才能使用inject
来接收。
参数规则:
第一个参数是注入的
key
。Vue
会遍历父组件链,通过匹配key
来确定所提供的值。 如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会覆盖链上更远的组件所提供的值
。如果没有能通过 key 匹配到值,inject()
将返回undefined
,除非提供了一个默认值。
假设有一个父组件已经提供了一些值,如前面 provide()
的例子中所示:
xml
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入不含默认值的静态值
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的函数类型的默认值
const fn = inject('function', () => {})
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('factory', () => new ExpensiveObject(), true)
</script>
生命周期
想看全部生命周期的细节,请看这里,这里就写几个我常用的,也是大家常用的,
vue
的生命周期太多了,有些只有特殊场景才会用到,大家自行查阅文档即可。
对于写惯了react
的 函数式组件,现在再回头来看这么多生命周期,也是有点不习惯
官网的所有生命周期钩子(更新于2023-9-5):
onMounted()
组件挂载完成后onUpdated()
响应状态变更而更新DOM
树之后onUnmounted()
组件实例被卸载之后onBeforeMount()
组件被挂载之前onBeforeUpdate()
响应状态而更新DOM
树之前onBeforeUnmount()
组件实例被卸载之前onErrorCaptured()
捕获了后代组件传递的错误时调用onRenderTracked()
组件渲染过程中追踪到响应式依赖时onRenderTriggered()
响应式依赖的变更触发了组件渲染时onServerPrefetch()
组件实例在服务器上被渲染之前(SSR only
)
onMounted
组件挂载完成后
注册一个回调函数,在组件挂载完成后执行,类似
react
里的useEffect(()=>{},[])
,通常会在这里执行一些页面加载完即刻调用的函数,或者访问一些变量,请求接口等等
xml
<template>
<div ref="el"> 6666 </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const el = ref()
onMounted(() => {
console.log(el.value) // <div>
})
</script>
onUpdated
组件状态变更而更新DOM
树之后
注册一个回调函数,在组件因为响应式状态变更而更新其
DOM
树之后调用。
不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!
访问更新后的 DOM
xml
<template>
<button id="count" @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref, onUpdated } from 'vue'
const count = ref(0)
onUpdated(() => {
// 文本内容应该与当前的 `count.value` 一致
console.log(document.getElementById('count').textContent)
})
</script>
onUnmounted
组件卸载之后调用
注册一个回调函数,在组件实例被卸载之后调用。一般在这个周期取消订阅,清除定时器等。
xml
<script setup>
import { onMounted, onUnmounted } from 'vue'
let intervalId;
onMounted(() => {
intervalId = setInterval(() => {
// ...
})
})
// 清理
onUnmounted(() => clearInterval(intervalId))
</script>
刚开始我理解
onUnmounted
的生命周期,类似于react
的useEffect
的return
,可是查了官网后,vue3
的onUnmounted
标记的是组件卸载之后 ,react
里说的是组件销毁时 执行,这个销毁和卸载看起来类似,但两个如果放在一个组件里,时间也是有先后差异的。
ChatGPT 给的答案:在
Vue 3中,组件卸载和销毁是同时进行的。而在
React`中,组件的销毁通常是在组件卸载完毕后才会发生的。
scss
useEffect(() => {
// 需要是一个函数哦!!!
return () => {
// 以下内容只会在组件销毁时执行,清理effect
console.log(1);
};
}, []);
虽然理念有些差异,但对于我们的使用,无太大差异,都是在这个时机进行清理定时器或者取消订阅等卸载相关的操作。
下面两个是关于缓存实例的生命周期:
onActivated
当缓存组件被插入到DOM中时调用
注册一个回调函数,若组件实例是
<KeepAlive>
缓存树的一部分,当组件被插入到DOM
中时调用。
onDeactivated
当缓存组件从DOM
中被移除时调用
注册一个回调函数,若组件实例是
<KeepAlive>
缓存树的一部分,当组件从DOM
中被移除时调用
xml
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 调用时机为首次挂载
// 以及每次从缓存中被重新插入时
})
onDeactivated(() => {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
})
</script>
这几个月的开放我还没遇到过需要缓存的场景,上面两个我还没使用过,如果后续有需求,可以尝试一下。
内置指令
v-text
更新元素的文本内容
基本可以使用
{{}}
来代替,用于更新标签内的文本内容
xml
<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>
v-html
更新元素的innerHTML
v-html
的内容直接作为普通HTML
插入vue
模版内,接收的类型为string
。
注意:在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值
在单文件组件,scoped
样式将不会作用于 v-html
里的内容,因为 HTML 内容不会被 Vue 的模板编译器解析。如果你想让 v-html
的内容也支持 scoped CSS,你可以使用 CSS modules 或使用一个额外的全局 <style>
元素,手动设置类似 BEM 的作用域策略。
ini
<div
class="remark-content"
:style="{ fontSize: remarkFontSize + 'px' }"
v-html="currentSlideRemark"
></div>
...
const currentSlideRemark = computed(() => {
return `<div>${currentSlide?.value?.remark || ''}
});
v-show
通过设置内联样式的display
属性来改变元素的可见性
v-show
会在DOM
渲染中保留该元素;v-show
仅切换了该元素上名为display
的CSS
属性。接收一个布尔值
ini
<div
v-show="isShowTools"
class="device-tools"
>
</div>
...
const isShowTools = ref(false);
...
ini
<td
class="cell"
v-show="!hideCells.includes(`${rowIndex}_${colIndex}`)"
>
</td>
v-if
基于表达式值的真假,来条件性地渲染元素或者模版片段
当
v-if
元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那么其内部的内容根本都不会被渲染。
v-else
表示
v-if
或v-if
/v-else-if
链式调用的"else 块"。
v-else-if
表示
v-if
的"else if 块"。可以进行链式调用。
v-if
vs. v-show
v-if
是"真实的"按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display
属性会被切换。
总的来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show
较好;如果在运行时绑定条件很少改变,则 v-if
会更合适。
v-on
给元素绑定事件
缩写为
@
修饰符:
.stop
- 调用event.stopPropagation()
。.prevent
- 调用event.preventDefault()
。.capture
- 在捕获模式添加事件监听器。.self
- 只有事件从元素本身发出才触发处理函数。.{keyAlias}
- 只在某些按键下触发处理函数。.once
- 最多触发一次处理函数。.left
- 只在鼠标左键事件触发处理函数。.right
- 只在鼠标右键事件触发处理函数。.middle
- 只在鼠标中键事件触发处理函数。.passive
- 通过{ passive: true }
附加一个 DOM 事件。
示例:
xml
<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>
<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>
<!-- 内联声明 -->
<button v-on:click="doThat('hello', $event)"></button>
<!-- 缩写 -->
<button @click="doThis"></button>
<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>
<!-- 停止传播 -->
<button @click.stop="doThis"></button>
<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>
<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>
<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>
<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />
<!-- 点击事件将最多触发一次 -->
<button v-on:click.once="doThis"></button>
<!-- 对象语法 -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
v-bind 可动态绑定一个或多个attribute
,也可以时组件prop
。
缩写:
:
用于给组件绑定属性值,可动态绑定一个或多个attribute
,也可以是组件prop
xml
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />
<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>
<!-- 缩写 -->
<img :src="imageSrc" />
<!-- 缩写形式的动态 attribute 名 -->
<button :[key]="value"></button>
<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />
<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]"></div>
<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>
<!-- 绑定对象形式的 attribute -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- prop 绑定。"prop" 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />
<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />
<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>
v-model
双向数据绑定
在表单输入元素或组件上创建双向绑定
-
期望的绑定值类型:根据表单输入元素或组件输出的值而变化
-
仅限:
<input>
<select>
<textarea>
- components
-
修饰符
文本:
xml
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
复选框:
ini
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
我们也可以将多个复选框绑定到同一个数组或集合的值:
ini
const checkedNames = ref([]);
<div>Checked names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
单选按钮:
ini
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
下拉列表:
xml
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
修饰符:
- .lazy 在
change
后更新
默认情况下,
v-model
会在每次input
事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加lazy
修饰符来改为在每次change
事件后更新数据
xml
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
- .number 输入自动转为数字
如果你想让用户输入自动转换为数字,你可以在
v-model
后添加.number
修饰符来管理输入
ini
<input v-model.number="age" />
- .trim 去掉两端空格
如果你想要默认自动去除用户输入内容中两端的空格,你可以在
v-model
后添加.trim
修饰符:
ini
<input v-model.trim="msg" />
多个v-model 绑定
我们可以在单个组件实例上创建多个v-model 双向绑定
父组件:
ini
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
组件上的每一个 v-model
都会同步不同的 prop,而无需额外的选项:
ini
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
v-slot 插槽
用于声明具名插槽或是期望接收
props
的作用域插槽。使用v-slot
可以将子组件的内容分成多个块并插入到父组件中的不同位置
。
- 父组件模板定义一个或多个具名插槽。
- 子组件模板使用
v-slot
来匹配父组件中定义的具名插槽,并显示对应的内容。
父组件:
xml
<template>
<div>
<h1>父组件</h1>
<child-component>
<template v-slot:header>
这是头部插槽的内容
</template>
<template v-slot:footer>
这是底部插槽的内容
</template>
</child-component>
</div>
</template>
子组件:
xml
<template>
<div>
<h2>子组件</h2>
<header>
<slot name="header"></slot>
</header>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
上面的这个例子是网上找的,但我依然想不到什么时候使用
v-slot
这个指令,朋友们如果有场景,在评论区告诉我吧。
v-pre
跳过该元素及其所有子元素的编译
用于告诉
Vue
跳过对当前元素及其子元素的编译过程。这意味着,不会应用任何Vue.js
的指令或表达式,而元素的原始内容将直接输出
xml
<div id="app">
<p v-pre>{{ message }}</p>
</div>
在上面的代码中,我们在
<p>
元素上使用了v-pre
指令。这意味着Vue.js
不会对<p>
元素及其内容进行编译,而是将其中的{{ message }}
原封不动地输出。这样,无论message
的值是什么,都不会被 Vue.js 解析,而是直接以纯文本形式显示。
通常情况下,你不会经常使用 v-pre
指令,它通常用于优化性能或避免不必要的编译 。例如,当你有一些静态内容,不需要被 Vue.js
动态更新时,可以使用 v-pre
指令来跳过编译过程,提高渲染性能。
CSS 相关
scoped
如单词所意,当前样式只在当前文件/组件的作用域下生效
xml
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
:deep
即使是在
scoped
的模式下,如果你想让你父组件的样式影响到子组件,深度渗透,可以选择使用:deep
来实现
xml
<style scoped>
.element-content {
position: relative;
padding: 10px;
line-height: 1.5;
word-break: break-word;
cursor: move;
// 当前组件的
.text {
position: relative;
height: 100%;
padding-left: 12px;
overflow-y: auto;
}
// 给子组件的 a 添加样式
:deep(a) {
cursor: text;
}
}
</style>
给子组件的.play-icon
样式下的.svg-icon
添加样式:
xml
<style scoped>
:deep(.play-icon .svg-icon) {
display: inline-block;
width: 108px;
height: 48px;
}
</style>
:slotted
默认情况下,作用域的样式是不会影响到子组件,包括
<slot/>
的组件,可以使用:slotted
给插槽的内容添加样式
xml
<style scoped>
:slotted(div) {
color: red;
}
</style>
:global
如果你想让你的样式应用于全局,可以使用:global 来实现
xml
<style scoped>
:global(.red) {
color: red;
}
</style>
混合使用局部样式和全局样式
一些相关联的样式,如果你想在同一个组件定义全局样式和局部样式,也是可以的,如下:
xml
<style>
/* 全局样式 */
</style>
<style scoped>
/* 局部样式 */
</style>
添加动态样式
某些场景下,我们需要根据状态的不同,选择添加或者不添加样式,可以分为两种:动态的
style
样式 和动态的class
样式
ini
<div
class="bar-time"
:class="{ hidden: !playBarTimeVisible }"
:style="{ left: playBarTimeLeft }"
>
{{ playBarTime }}
</div>
在上面的代码里,
class="bar-time"
是我们常用的添加样式的方式,:class
是动态添加的class
样式,:style
是动态添加的行内style
样式
上面对应的样式:
css
...
// style 这里对应是一个字符串,当然你也可以定义数值,最后在行间自己拼接
const playBarTimeLeft = ref(0);
playBarTimeLeft.value = `${
(tx - (time >= 3600 ? 25 : 20)) * (1 / scale.value!)
}px`;
...
// 这个是普通的class 样式
.bar-time {
position: absolute;
left: 0;
top: -30px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.62);
color: #fff;
font-size: 12px;
text-align: center;
opacity: 1;
// 这个是动态的 class
&.hidden {
opacity: 0;
}
}
v-bind
将
js
中的变量的值应用于css
中,变量值一般用小写单词表示
xml
<script setup>
const theme = {
color: 'red'
}
const color1 = {
color: 'pink'
}
</script>
<template>
<p>hello</p>
<div>hhh</div>
</template>
<style scoped>
/* 如果是对象,需要加个括号 */
p {
color: v-bind('theme.color');
}
/* 如果是值,直接写就行 */
div{
color:v-bind(color1)
}
</style>
事件相关
拿到Event 对象
当我们想在某些时刻拿到事件里的Event的对象时,可以直接在添加事件那里加一个
$event
即可(当然此时的$event
是不会影响你正常的参数传递的)
bash
<div @mousedown="$event => handleSelectElement($event)">点击</div>
下面就是一个正常参数传递的例子:
ini
<div
@mousedown="$event => handleSelectElement($event,item)"
v-for="(item,index) in list" :key="item.id">
点击
</div>
...
const handleSelectElement=($event,item)=>{
// 依然可以接收到Event 和我们正常传递的参数
}
定义hooks
在开发过程中,我们可能会经常需要使用到当前鼠标的位置,我们说万物皆可响应式,那我们要怎么把鼠标的位置变成响应式数据呢?下面我们一起来实现下,我们先来定义一个useMouse.js的文件。
csharp
import { ref } from "Vue";
export default function useMouse () {
let x = ref(0);
let y = ref(0);
window.addEventListener('mousemove', (event: MouseEvent) => {
x.value = event.pageX
y.value = event.pageY
});
return {x, y};
}
在上面这个代码中,我们定义两个响应式的数据x,y,并且监听了鼠标的移动事件,在鼠标的移动事件被触发时,将鼠标的位置赋值给响应式数据x,y。
我们在另一个Vue文件中引入这个方法。
xml
<template>
<div>鼠标x: {{x}}</div>
<div>鼠标y: {{y}}</div>
</template>
<script setup>
import useMouse from "./useMouse.js";
const {x, y} = useMouse();
</script>
这样我们就已经把鼠标移动时的x,y坐标位置变成了响应式了!让我们看下效果。
我们还可以将添加和清除 DOM
事件监听器的逻辑也封装进一个组合式函数中
javascript
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
csharp
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (event) => {
x.value = event.pageX
y.value = event.pageY
})
return { x, y }
}
子组件调用父组件的方法
父给子组件传方法,子组件通过触发传过来的事件,来调用父组件的方法。可以使用
:onxxx
的形式进行传递,子组件通过props
来接收,接收到后直接用props.onxxx()
就可以调用
父组件:
ini
<div>
<ToolsButton
:isShowPages="isShowPages"
:onToggleChangeView="handleToggleChangeViewPages"
/>
</div>
子组件:
通过
props
来接收,接收后直接用就可以
xml
<template>
<div :style="{
boxShadow: isShowPages ? 'inset 0px 0px 99px 0px #3a77d4' : 'none'}"
@click="onToggleChangeView">浏览</div>
</template>
<script lang="ts" setup>
const props = defineProps({
isShowPages: {
type: Boolean,
required: true,
},
onToggleChangeView: {
type: Function as PropType<() => void>,
required: true,
},
});
方法二:
父组件:
xml
<template>
<PPTThumbnail @close="changeSlideThumbnailModelStatus(false)"/>
</template>
<script>
const changeSlideThumbnailModelStatus = (val: boolean) => {
this.slideThumbnailModelVisible = val;
}
</script>
子组件:
xml
<template>
<div class="close" @click="emit('close')">关闭</div>
</template>
<script>
const emit = defineEmits<{
(event: 'close'): void;
}>();
</script>
父组件调用子组件的方法
在子组件里定义一个方法,并使用
defineExpose
将其导出,在父组件里将子组件通过ref
来拿到子组件上的方法即可 子组件
xml
<script>
// 定义一个方法
const onSave = () => {
// dosomething...
};
// 导出
defineExpose({ onSave });
</script>
父组件:
xml
<template>
<RemindModal ref="remindModalComponent" @onSave="onSave" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import RemindModal from '../Modal/RemindModal/index.vue';
const remindModalComponent = ref<typeof RemindModal>();
const onSave = () => {
// 通过ref 的方式拿到子组件上的方法
remindModalComponent.value?.onSave();
};
</script>
开发中遇到的问题
在vue3
中配置环境变量
如果你的项目中使用了
Vue CLI
,那么你应该使用Vue CLI
的环境变量和模式,这样你就不需要手动引入dotenv-webpack
了。因为Vue CLI
已经内部集成了环境变量的加载
根目录下的 env
下的子文件名定义名称规则为:devlopment
testing
production
三个环境名与文件名需要一致,如下:
这样如果以VUE_APP_*
开头的的变量就会被载入到你的 Vue
应用中,所以你想在 Vue
应用中使用的环境变量都应该以VUE_APP_
开头。
假设你在 .env.development
中设置
那么你应该能在开发环境中通过
arduino
process.env.VUE_APP_SSO_HOST
访问到这个值
可是可是可是:当前我们的项目文件夹为:
所以如果要用默认的,就需要将文件名修改成规则中定义的名字,且jenkins
和 package.json
相关的变量就需要全部一起改,因为项目已经成型(当前项目中有多个子项目,修改起来影响范围较大),所以就去覆盖它,如果你的项目也是这样的,那么如何使环境变量生效呢? vue.config.js
文件中配置添加这个插件(dotenv-webpack
)
代码如下:
javascript
const Dotenv = require('dotenv-webpack');
const resolve = (dir) => {
return path.join(__dirname, '.', dir);
};
const envPath = resolve(`env/.env.${process.env.MODE}`);
configureWebpack: {
mode: 'development',
devServer: {
server: {
type: 'https',
},
host: 'dev.xxx.cn',
open: '',
proxy: {},
},
// 下面这个插件
plugins: [
new Dotenv({ path: envPath }),
],
},
这样有一个副作用,那就是在编辑完成后,终端会有一个提示,如下: