从react 转来写 【vue3】的实战笔记

vue 学习文档

背景:前几个月入职了新的公司,遵循我每次入职都要学新东西的原则 😄,这次也没让我失望,入职时候我也没问这边用的技术,由于我之前大部分时间都在写react ,面试也没问vue,可是让我没想到的是,第一个接收的项目是vue3 ,完全不会,但仗着无论是哪个框架都是 html css js 的逻辑,硬着头皮就上了,上班第一天就开撕代码,可是看着看着我就晕了,这个是啥,哪个又是啥。经过这几个月的熟悉,现在虽然说常用的方法也都会用,但写出来的未必是最优的代码,我也在慢慢学习,这里就暂且记录我的学习笔记,随着学习的深入会更新,如果也有刚开始学vue3的,可以一起探讨!!!


我在多年前也写过vue2,可那是很多年前的事情,已经翻篇了,所以这里我就不进行对比了,不要问我,我已经想不起来了,哈哈哈哈哈哈哈 😂

如果想了解vue2的,可以看这篇,期望能给你帮助


再啰嗦两句:

既然要学vue3,强烈建议大家直接用组合式的风格,从vue2 过渡来的对于选项式更熟悉,组合式的写法更开放,局限性更小。当然你想继续用原来的方式,也不是不可以,官方也是支持的,只是我个人建议。 什么是组合式风格


基础常用功能

stup 钩子

stup是一个函数,用于配置组件的初始状态、引入响应式数据、设置方法等。

setup 函数接收两个参数:

  1. props:用于接收来自父组件传递的 props 属性。
  2. context:提供了一个上下文对象,在setup函数内部使用,包含了一些常用的属性和方法,如attrsslotsemit等。

官网推荐 对于结合单文件组件使用的组合式 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语法糖。


toRefstoRef

如果你确实需要解构 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 来接收。

参数规则:

第一个参数是注入的 keyVue 会遍历父组件链,通过匹配 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的生命周期,类似于reactuseEffectreturn,可是查了官网后,vue3onUnmounted 标记的是组件卸载之后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 仅切换了该元素上名为 displayCSS 属性。接收一个布尔值

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-ifv-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
  • 修饰符

    • .lazy - 监听 change 事件而不是 input
    • .number - 将输入的合法字符串转为数字
    • .trim - 移除输入内容两端空格

文本:

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

访问到这个值


可是可是可是:当前我们的项目文件夹为:

所以如果要用默认的,就需要将文件名修改成规则中定义的名字,且jenkinspackage.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 }),
    ],
  },

这样有一个副作用,那就是在编辑完成后,终端会有一个提示,如下:


相关推荐
轻口味27 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami29 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda1 小时前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235951 小时前
web复习(三)
前端
迷糊的『迷』2 小时前
vue-axios+springboot实现文件流下载
vue.js·spring boot