首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >前端系列15集-watch,watchEffect,eventBus

前端系列15集-watch,watchEffect,eventBus

作者头像
达达前端
发布于 2023-10-08 19:10:11
发布于 2023-10-08 19:10:11
1.3K0
举报
文章被收录于专栏:达达前端达达前端

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

换句话说:watchEffect相当于将watch 的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watch,watchEffect 的回调函数会被立即执行(即 { immediate: true })

watchEffect的回调函数就是一个副作用函数,因为我们使用watchEffect就是侦听到依赖的变化后执行某些操作。

代码语言:javascript
复制
import { watchEffect, ref } from 'vue'

const count = ref(0)
watchEffect((onInvalidate) => {
  console.log(count.value)
  onInvalidate(() => {
    console.log('执行了onInvalidate')
  })
})

setTimeout(()=> {
  count.value++
}, 1000)

定义一个定时器,或者监听某个事件,我们需要在mounted生命周期钩子函数内定义或者注册,然后组件销毁之前在beforeUnmount钩子函数里清除定时器或取消监听。这样做我们的逻辑被分散在两个生命周期,不利于维护和阅读。

如果我利用watchEffect,创造和销毁逻辑放在了一起

代码语言:javascript
复制
// 定时器注册和销毁
watchEffect((onInvalidate) => {
  const timer = setInterval(()=> {
    // ...
  }, 1000)
  onInvalidate(() => clearInterval(timer))
})

const handleClick = () => {
 // ...
}
// dom的监听和取消监听
onMounted(()=>{
  watchEffect((onInvalidate) => {
    document.querySelector('.btn').addEventListener('click', handleClick, false)
    onInvalidate(() => document.querySelector('.btn').removeEventListener('click', handleClick))
  })
})

利用watchEffect做一个防抖节流

代码语言:javascript
复制
const id = ref(13)
watchEffect(onInvalidate => {
   // 异步请求
  const token = performAsyncOperation(id.value)
  // 如果id频繁改变,会触发失效函数,取消之前的接口请求
  onInvalidate(() => {
    // id has changed or watcher is stopped.
    // invalidate previously pending async operation
    token.cancel()
  })
})

可以使用 Vue.js 的三元运算符在 {{ }} 中展示不同的状态,具体代码如下:

代码语言:javascript
复制
<el-table-column label="状态" min-width="100">
  {{row.status === 1 ? '已完成' : row.status === 2 ? '进行中' : '未开始'}}
</el-table-column>

在上面的代码中,我们使用了两个嵌套的三元运算符来实现根据 row.status 的值显示不同的状态。如果 row.status 的值为 1,则显示 "已完成",如果值为 2,则显示 "进行中",否则显示 "未开始"。

在 Vue 3 中,可以使用 v-slot 的缩写语法(#)来指定插槽的位置。对于 el-table-column 组件中的插槽,您可以将模板语法放置在 <template v-slot:default> 或 <template #default> 标签内。

下面是根据您提供的代码示例修改后的 Vue 3 代码,用于在 el-table-column 组件中显示 sysUser.active 字段的值:

代码语言:javascript
复制
<el-table-column label="状态" min-width="100">
  <template #default="{ row }">{{ row.active }}</template>
</el-table-column>

在上面的代码中,我们使用了 #default 缩写语法来定义插槽,并使用对象解构来获取 row 参数。然后,我们在模板中使用 row.active 来显示 active 字段的值。

  1. el-table-column 组件没有绑定数据源:请确保你的表格组件已经正确地绑定了数据源,例如使用 :data 属性绑定数据源。
  2. .active 字段不存在或为空值:请确保 .active 字段存在且有值。如果该字段不存在或为空,您的模板代码将无法正确地解析。
  3. 样式问题:请检查您的样式是否正确,以确保表格列宽度足够容纳内容,并且字体颜色不会与背景颜色混淆。

要根据条件隐藏<el-form-item>,可以使用Vue的条件渲染指令v-if或v-show。

使用v-if指令时,只有当条件为真时,元素才会被渲染到页面上。如果条件变为假,则该元素从DOM中删除。

例如, 如果您想要在表单项不为空时才显示密码字段,您可以将v-if指令添加到包装表单项的

元素中:

代码语言:javascript
复制
<div v-if="formData.name !== ''">
  <el-form-item label="密码" prop="name">
    <el-input
      class="ls-input"
      v-model="formData.passwd"
      placeholder="请输入密码"
      clearable
    />
  </el-form-item>
</div>

在这个例子中,<el-form-item>只有在formData.name不为空时才会被渲染出来。

另一种选择是使用v-show指令。与v-if不同的是,当条件为假时,元素仍然存在于DOM中,只是样式设置为display:none。

例如,如果您希望在表单项不为空时显示密码字段,您可以将v-show指令添加到元素中:

代码语言:javascript
复制
<el-form-item label="密码" prop="name" v-show="formData.name !== ''">
  <el-input
    class="ls-input"
    v-model="formData.passwd"
    placeholder="请输入密码"
    clearable
  />
</el-form-item>

在这个例子中,当formData.name不为空时,<el-form-item>将显示出来,否则它将以display:none的方式隐藏起来。

代码语言:javascript
复制
data | 展示数据 |

empty-text | 内容为空的时候展示的文本 |
    
node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 |

render-after-expand | 是否在第一次展开某个树节点后才渲染其子节点 |

load 加载子树数据的方法,仅当lazy属性为true时生效

render-content 树节点的内容区的渲染Function

highlight-current 是否高亮当前选中节点,默认值是false

default-expand-all 是否默认展开所有节点

expand-on-click-node
是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。

这段代码使用了 Vue 3 Composition API 中的 defineProps 函数来定义一个名为 props 的响应式属性对象,并通过 withDefaults 函数设置了默认值。

首先,defineProps 函数是用于定义组件接收的 props 属性的函数。<TreeFilterProps> 泛型表示这个组件接收的 props 类型,即传递给组件的数据类型。这个函数返回一个 props 对象,其中每个属性都对应着组件中接收的 prop 属性。在这个例子中,这个 props 对象是空的,也就是说这个组件没有接收任何 props 属性。

接下来,withDefaults 函数是一个辅助函数,用于设置默认值。此函数接收两个参数:第一个是需要设置默认值的对象,第二个是一个包含默认值的对象。在这个例子中,我们将 defineProps<TreeFilterProps>() 返回的空对象作为第一个参数传入,表示我们需要给这个对象设置默认值;然后,我们将一个包含默认值的对象作为第二个参数传入,其中 id: 'id' 表示默认的 id 属性是 'id',label: 'label' 表示默认的 label 属性是 'label',multiple: false 表示默认的 multiple 属性是 false。

因此,这段代码的作用是定义一个响应式的 props 属性对象,并为其设置默认值。如果在父组件中没有传递对应的 props 属性,则会使用默认值。

defineProps<TreeFilterProps>() 是一个函数调用,它将 props 对象的预期类型定义为 TreeFilterProps。它返回一个所有属性均设置为 undefined 的对象。

withDefaults() 是一个函数,它接受两个参数:一个具有可选属性的对象(在这种情况下是 props),以及一个具有默认值的对象(在这种情况下是 { id: 'id', label: 'label', multiple: false })。它将这两个对象合并在一起,如果有任何冲突,则优先使用默认值。

生成的 props 对象将具有与 TreeFilterProps 相同的形状,还包括额外的属性 id、label 和 multiple,如果未提供,则将设置为它们的默认值。

defaultProps 是另一个对象,用于设置 children 和 label 属性的默认值。它使用与 props 相同的 label 值,除非提供了不同的值。此对象的目的是为可能未由组件的调用者指定的 props 提供回退值。

ref<T> 是一个函数,它创建对类型为 T 的值的响应式引用。常量 treeRef 被赋予一个 ElTree 类型实例的引用,该类是由 Element-Plus UI 库提供给 Vue.js 的组件之一。此引用可用于访问 ElTree 实例的属性和方法。

treeData 和 treeAllData 常量也是使用 ref 创建的。它们都被赋予了一个对象数组的空数组,其中包含键值对,其中键的类型为 string,值的类型为 any。这些引用可用于存储将由 ElTree 组件动态呈现的数据。

在 setSelected 函数中,首先检查了 props.multiple 参数是否为 true,如果是的话则判断 props.defaultValue 是否为数组,如果是则将其赋值给 selected.value 变量,否则将其包装成一个数组并赋值给 selected.value 变量。

接着,如果 props.multiple 参数不为 true,则判断 props.defaultValue 是否为字符串类型,如果是的话直接将其赋值给 selected.value 变量,否则将 selected.value 变量赋值为空字符串。

后端项目打包部署

在控制台中执行mvn clean package命令把项目打成一个jar包,在控制台日志中看到如下信息表明打包成功

使用XShell6 ssh客户端连接软件登录自己的Linux云服务器,执行cd /usr/local命令进入/usr/local目录

执行mkdir logs命令创建日志文件夹

给项目启动和关闭bash脚本文件授予读、写和执行权限

chmod 775 startup.sh stop.sh

执行vim ./conf/nginx.conf 命令修改nginx.conf配置文件

$ npm install --save vue3-eventbus

代码语言:javascript
复制
import eventBus from 'vue3-eventbus'
app.use(eventBus)
代码语言:javascript
复制
// Button.vue
import bus from 'vue3-eventbus'
export default {
    setup() {
        bus.emit('foo', { a: 'b' })
    }
}

mitt

代码语言:javascript
复制
export default class EventBus {
    constructor() {
     this.event = {}
    }
}
组合式API介绍

Vue3提供两种组织代码逻辑的写法:

  • 通过data、methods、watch 等配置选项组织代码逻辑是选项式(Options) API写法
代码语言:javascript
复制
<template>
  <p>计数器:{{ count }}</p>
  <button @click="increment">累加</button>
</template>

<script>
import { ref } fro 'vue'
export default {
  setup () {
    // 打印undefined
    console.log(this)
    // 定义数据和函数
    const count = ref(0)
    const increment = () => {
      count.value++
    }
    // 返回给模板使用
    return { count , increment}
  }
}
</script>
  • 在setup中通过Vue提供的内置函数组合,实现代码功能,就是Composition API写法。
  • Composition API有什么好处?可复用,可维护。
  • setup 函数是 Vue3 特有的选项,作为组Composition API的起点。
  • 函数中 this 不是组件实例,是 undefined。
  • 如果数据或者函数在模板中使用,需要在 setup 返回。
  • 在Vue3的Composition API项目中几乎用不到 this , 所有的东西通过函数获取。
代码语言:javascript
复制
<template>
  <p>计数器:{{ count }}</p>
  <button @click="increment">累加</button>
</template>

<script setup>
import { ref } from 'vue'
// 定义数据和函数
const count = ref(0)
const increment = () => {
  count.value++
}
</script>
  • 在 script setup 中声明的变量都可以在模板使用,数据,函数,组件。
  • 不需要export default。
  • 不需要return。

Vue3 的 setup 中无法使用 this 这个上下文对象,但是如果我想使用 this 上的属性和方法应该怎么办呢。虽然不推荐这样使用,但依然可以通过 getCurrentInstance 方法获取上下文对象:

注意

ctx 只能在开发环境使用,生成环境为 undefined 。 推荐使用 proxy ,在开发环境和生产环境都可以使用。

代码语言:javascript
复制
<script setup>
import { getCurrentInstance } from 'vue'
// 以下两种方法都可以获取到上下文对象
const { ctx } = getCurrentInstance()
const { proxy }  = getCurrentInstance()
</script>
代码语言:javascript
复制
<template>
  <p>计数器:{{ count }}</p>
  <button @click="increment">累加</button>
</template>

<script setup>
import { ref } from 'vue'
// 创建响应式数据对象
const count = ref(0)
const increment = () => {
  // 在JS中使用的时候需.value
  count.value++
}
</script>
  • ref 可以把简单数据或者复杂数据转换成响应式数据,
  • 注意
    • JS中使用加上 .value,
    • template模板中使用省略.value

reactive 函数通常定义复杂类型的响应式数据,不能使用简单的数据类型。

代码语言:javascript
复制
<template>
  <div>
    <p>姓名:{{state.name}}</p>
    <p>年龄:{{state.age}}</p>
    <button @click="addAgeHandle">+1</button>
  </div>
</template>

<script>
import { reactive } from "vue"; 
// 创建响应式数据对象
const state = reactive({ name: 'xxx', age: 25 })
const addAgeHandle = () => {
  state.age++
}
</script>
<template>
  <div>
    <p>姓名:{{state.name}}</p>
    <p>年龄:{{state.age}}</p>
    <button @click="addAgeHandle1">+1</button>
    <button @click="addAgeHandle2">+1</button>
  </div>
</template>

<script>
import { reactive, ref } from "vue"; 
// 创建响应式数据对象
let user1 = reactive({ name: 'xxx', age: 25 })
let user2 = ref({ name: 'xxx', age: 25 })
const addAgeHandle = () => {
  state.age++
}

// 下面是使用的对比
// 💥💥 重新赋值会导致响应式丢失
user1 = {
  name:'xxx',
  age: 123
}

// 👍👍 重新赋值不会导致响应式丢失
user2.value = {
  name:'xxx',
  age: 123
}
</script>
代码语言:javascript
复制
import { ref, reactive, isRef } from 'vue'
const count = ref(1)
const userInfo = reactive({
  name:'xxx',
  age: 18
})
const plusOne = computed(() => count.value + 1)
const plusUserInfo = computed(() => userInfo)
console.log(plusOne.value) // 2
// 也就是说,即使返回值写的是reactive对象,最后也会被转换为响应式的ref对象
console.log(isRef(plusUserInfo)) // true
plusOne.value++ // 错误
代码语言:javascript
复制
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
  • 作用:与Vue2相同
  • 语法:computed( () => 返回一个新的值 )
  • 注意:💥回调函数一定要有返回值
  • watch(基本数据,(newValue, oldValue) => { })
  • watch(复杂数据,(newValue, oldValue) => { }, {deep: true, immediate: true})
  • watch( [数据1, 数据2], (newValue, oldValue) => { })
  • watch( () => 复杂数据.属性名, (newValue, oldValue) => { } )
代码语言:javascript
复制
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
const count = ref(0)
const user = reactive({
  name:'chengang',
  age: 28
})
// 监听基本数据类型
watch(() => count, (newval, oldval) => {
  console.log(newval, oldval)
})
watch(() => count.value, (newval, oldval) => {
  console.log(newval, oldval)
})
watch(count, (newval, oldval) => {
  console.log(newval, oldval)
})
// 监听复杂数据类型
watch(user, (newval, oldval) => {
  console.log(newval, oldval)
}, { deep: true, immediate: true })
// 监听多个数据
watch([count, user], (newval, oldval) => {
  console.log(newval, oldval)
}, { deep: true})
// 监听对象的属性
watch(() => user.age, (newval, oldval) => {
  console.log(newval, oldval)
})
</script>

选项式API下的生命周期函数使用

组合式API下的生命周期函数使用

beforeCreate

不需要(直接写到setup函数中)

created

不需要(直接写到setup函数中)

beforeMount

onBeforeMount

mounted

onMounted

beforeUpdate

onBeforeUpdate

updated

onUpdated

beforeDestroyed

onBeforeUnmount

destroyed

onUnmounted

activated

onActivated

deactivated

onDeactivated

  1. 创建 ref:const xxxRef = ref()
  2. 绑定ref属性到标签上: ref=“xxxRef”
  3. 通过xxxRef.value访问dom
代码语言:javascript
复制
<template>
   <!-- 2. 绑定到标签ref属性上,注意:名字要和script定义的保持一致 -->
   <input type="text" ref="inputRef" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 1. 调用ref函数
const inputRef = ref(null)
const focusHandle = () => {
  inputRef.value.focus()
}
onMounted(() => {
  focusHandle()
})
</script>
  • 子组件内, 调用defineExpose() 暴露数据和方法,defineExpose方法无需导入即可使用
代码语言:javascript
复制
<template>
  <Child ref="childRef"/>
  <button @click="handleClick">点击</button>
</template>

<script lang="ts" setup>
import Child from './components/child1.vue'
import { ref } from 'vue'
const childRef =  ref()
const handleClick = () => {
  childRef.value?.countAddHandle()
}
</script>
父传子-defineProps函数
代码语言:javascript
复制
<template>
  <Child :msg="msg"/>
</template>

<script lang="ts" setup>
import Child from './components/child1.vue'
import { ref } from 'vue'
const msg = ref('hello world')
</script>
代码语言:javascript
复制
<script lang="ts" setup>
import { computed } from 'vue'
// 使用Vue提供的方法增加对类型的声明
import type { PropType } from 'vue'
type User = {
  username: string,
  age: number,
  address: string
}
// 第一种写法
const props = defineProps({
  msg: {
    type: String,
    default:''
  },
  userList: {
    type: Array as PropType<User[]>,
    default: () => []
  },
  userInfo: {
    type: Object as PropType<User>,
    default: () => {}
  }
})
// 第二种写法withDefaults可以赋予prop默认值,但是有一点需要注意的是,类型的声明不能使用d.ts,此为Vue3尚未解决的issur
interface ChildProps {
  foo: string
  userList: User[],
  userInfo: User
}
const props = withDefaults(defineProps<ChildProps>(), {
  foo: '1',
  // 复杂数据类型的默认值需要是函数类型
  userList: () => [
    { username:'admin', age: 18, address:'湖北'}
  ],
  userInfo: () => {
    return { username:'', age: 19, address:''}
  }
})
</script>
子传父-defineEmits函数

使用步骤:

  1. 子组件通过 defineEmits获取 emit 函数(因为没有this)
  2. 子组件通过 emit 触发事件,并且传递数据
  3. 父组件,监听自定义事件

语法:

  1. const emit = defineEmits(["自定义事件名1", "自定义事件名2"])
  2. emit("自定义事件名", 值)
代码语言:javascript
复制
<template>
  <Child :count="count" @add-count="count++" @reduce-count="count--" />
</template>

<script lang="ts" setup>
import Child from './components/child1.vue'
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <p>{{  count }}</p>
  <button @click="addHandle">+1</button>
  <button @click="reduceHandle">+1</button>
</template>

<script lang="ts" setup>
// 使用defineEmits的方式1
const emits = defineEmits<{
  // 向上传递参数
  (e:'add-count', value?: number): void,
  (e:'reduce-count'): void
}>()
// 使用defineEmits的方式二
const emits = defineEmits(['add-count', 'reduce-count'])
defineProps({
  count: {
    type: Number,
    default:0
  }
})
// 增加
const addHandle = () => {
  emits('add-count')
}
// 减少
const reduceHandle = () => {
  emits('reduce-count')
}
</script>
代码语言:javascript
复制
<template>
  <p>{{  count }}</p>
  <button @click="addHandle">+1</button>
  <button @click="reduceHandle">+1</button>
</template>

<script lang="ts" setup>
// 使用defineEmits的方式1
const emits = defineEmits<{
  // 向上传递参数
  (e:'add-count', value?: number): void,
  (e:'reduce-count'): void
}>()
// 使用defineEmits的方式二
const emits = defineEmits(['add-count', 'reduce-count'])
defineProps({
  count: {
    type: Number,
    default:0
  }
})
// 增加
const addHandle = () => {
  emits('add-count')
}
// 减少
const reduceHandle = () => {
  emits('reduce-count')
}
</script>
保持响应式-toRef/toRefs函数
  • toRef/toRefs用于解构响应式数据,因为如果直接解构响应式数据会使其失去响应性。
  • toRef返回的值是否具有响应性取决于被解构的对象本身是否具有响应性。响应式数据经过toRef返回的值仍具有响应性,非响应式数据经过toRef返回的值仍没有响应性。
  • 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
  • toRef基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。toRefs亦然。
代码语言:javascript
复制
<template>
  <p>{{ name }}</p>
  <p>{{ password }}</p>
  <p>{{ user }}</p>
  <button @click="changeHandle">修改name属性</button>
</template>
  
<script lang="ts" setup>
import { reactive, toRef, toRefs } from 'vue'
const user = reactive({
  name: 'chengang',
  password: 'admin123'
})
// 每次拿出一个
const name = toRef(user, 'name')
// 每次解构对象上全部的属性,可以只取部分
const { password } = toRefs(user)
// 这里特别需要注意:通过toRef/toRefs解构出来的新的响应式对象,值的改变会影响原来的响应式对象,换言之,他俩相互影响
const changeHandle = () => {
  password.value = 'xxxxxxx'
  name.value = 'this is changed'
}
</script>
unref语法糖

如果参数是 ref,则返回内部值,否则返回参数本身。

在Vue3中,由于认为on, off,

代码语言:javascript
复制
// utils/mitt.js
import mitt from 'mitt'
import type { Emitter } from 'mitt'
type Events ={
  // 事件名称:传递的参数类型
  change: string
}
const emitter: Emitter<Events> = mitt()
export default emitter
代码语言:javascript
复制
// utils/mitt.ts 
import mitt, { Emitter } from 'mitt'

type Events = { 
    // 事件名称:传递的参数类型 
    change: string 
}

const emitter: Emitter<Events> = mitt()
export default emitter
代码语言:javascript
复制
<template>
  <div class="app">
    <Child1 />
    <Child2 />
  </div>
</template>

<script lang="ts" setup>
import Child1 from './components/child1.vue'
import Child2 from './components/child2.vue'
</script>

<template>
  <button @click="changeHandle">触发修改组件2的msg</button>
</template>

<script lang="ts" setup>
import mitt from '@/utils/mitt'
const changeHandle = () => {
  mitt.emit('change', '这个是组件2需要修改的内容')
}
</script>

<template>
  <p>{{  msg }}</p>
</template>

<script lang="ts" setup>
import mitt from '@/utils/mitt'
import { ref } from 'vue'
const msg =  ref('hello world')
mitt.on('change', (value) => {
  msg.value = value
})
</script>
v-model语法糖
  • Vue2中的v-model 语法糖 完整写法
    • 原生标签,:value="count" 和 @input="count=$event.target.value"
    • 组件标签,:value="count" 和 @input="..."
  • Vue3中的v-model 语法糖 完整写法
    • 原生标签,:value="count" 和 @input="count=$event.target.value"
    • 组件标签,:modelValue="count" 和 @update:modelValue="..."
  • Vue2->Vue3为什么要这么改变?
    • Vue3 中组件标签上,可以使用多个v-model 语法糖
    • Vue3中已废弃使用.sync语法,一律使用v-model
代码语言:javascript
复制
<input v-model="msg" type="text">
<!-- 等价 -->
<input :value="msg" @input="msg=$event.target.value" type="text">

<Demo v-model="count"></Demo>
<!-- 等价 -->
<Demo :modelValue="count" @update:modelValue="count=$event"></Demo>

<Demo v-model:count1="count1" v-model:count2="count2"></Demo>
<!-- 等价 -->
<Demo :count1="count" @update:count1="count1=$event" :count2="count" @update:count2="count2=$event"></Demo>
useAttrs的使用

透传属性,所有未经props定义的属性都归纳到此处。使用useAttrs在setup语法中访问透传的属性。

代码语言:javascript
复制
<template>
  <div class="app">
    <Child name="name" msg="hello world" demo="123" />
  </div>
</template>

<script lang="ts" setup>
import Child from './components/child.vue'
</script>

<!-- 同一个.vue文件中可以同时书写两个script,一个用来书写setup语法的代码,一个用来使用Options API的属性来增加属性 -->
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  // 关闭透传
  inheritAttrs: false
})
// export default {
//   inheritAttrs: false
// }
</script>

child.vue

代码语言:javascript
复制
<template>
  <!-- 在模板中使用通过$attrs即可 -->
  <h1 v-bind="$attrs">透传属性$attrs</h1>
</template>
  
<script lang="ts" setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
// 在setup中使用
console.log(attrs, '在setup中使用')
// 通过v-bind="attrs"将父祖组件传递过来的未经props定义的属性赋给元素或者组件
</script>
Vue3的slot
具名插槽
代码语言:javascript
复制
<template>
  <div>
   <slot name="header">
    <h1>这里是header的默认内容</h1>
   </slot>
   <!-- default插槽可以写name="default",也可以不用写,默认都会归纳到default插槽 -->
   <slot name="default">
   </slot>
   <slot name="footer">
   </slot>
  </div>
</template>
代码语言:javascript
复制
<template>
  <div class="app">
    <Child>
      <!-- 具名插槽使用的时候,名称要作用到template上,除非是default -->
      <template #header>
        <p>header的内容</p>
      </template>
      <!-- #和v-slot写法都可以 -->
      <template #default>
       <p>默认的内容</p>
      </template>
      <!-- 或者default插槽直接放内容也可以 -->
      <!-- <p>默认插槽的内容</p> -->
      <!-- #和v-slot写法都可以 -->
      <template v-slot:footer>
        <p>footer的内容</p>
      </template>
    </Child>
  </div>
</template>
动态插槽
代码语言:javascript
复制
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>
代码语言:javascript
复制
<template>
  <div class="app">
    <!-- 这里需要注意一下的是,Vue2的作用域插槽是通过slot-scope="scope"来取值的,但是在Vue3中,是通过v-slot -->
    <!-- 如果是取具名插槽的作用域值,需要使用v-slot:插槽名称="变量名称",默认插槽是v-slot="变量名称" -->
    <Child6 v-for="item in userList" :user="item" :key="item.username">
      <template v-slot="{ age, username, address }">
        {{ age }} - {{ username }} - {{ address }}
      </template>
    </Child6>
  </div>
</template>

<script lang="ts" setup>
import Child6 from './components/child6.vue'
import { ref } from 'vue'
type User = {
  username: string,
  age: number,
  address: string
}
const userList = ref<User[]>([
  { username: '用户1', age: 24, address: '地址1' },
  { username: '用户2', age: 45, address: '地址2' },
  { username: '用户3', age: 27, address: '地址3' },
])
</script>
代码语言:javascript
复制
<template>
  <div class="child">
    <slot :username="user.username" :age="user.age" :address="user.address"></slot>
    <!-- 全部绑定所有的数据,使用下面的方法 -->
    <!-- <slot v-bind="user"></slot> -->
  </div>
</template>

<script lang="ts" setup>
import type { PropType } from 'vue'
type User = {
  username: string,
  age: number,
  address: string
}
defineProps({
  user: {
    type: Object as PropType<User>,
    default: () => {}
  }
})
</script>
  • 移除了children属性,官方建议访问子组件的时候使用refs,需要注意的是,在setup语法中使用子组件的数据需要defineExpose导出才能使用。
  • Vue3移除了.sync语法,一律使用v-model
  • pinia 中只有 state、getter、action,抛弃了 Vuex 中的 Mutation,Vuex 中 mutation 一直都不太受小伙伴们的待见,pinia 直接抛弃它了,这无疑减少了我们工作量。
  • pinia 中 action 支持同步和异步,Vuex 不支持
  • 良好的 Typescript 支持,毕竟我们 Vue3 都推荐使用 TS 来编写,这个时候使用 pinia 就非常合适了
  • 无需再创建各个模块嵌套了,Vuex 中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而 pinia 中每个 store 都是独立的,互相不影响。
  • 体积非常小,只有 1KB 左右。
  • pinia 支持插件来扩展自身功能。
  • 支持服务端渲染。

安装 Pinia

shell

代码语言:javascript
复制
npm install pinia -S

在 main.js 中引入 Pinia

代码语言:javascript
复制
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);

app.use(createPinia());
app.mount('#app');

使用 defineStore() 定义一个 Store 。defineStore() 第一个参数是 storeId,第二个参数是一个选项对象:

代码语言:javascript
复制
// src/stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

defineStore() 第一个参数是 storeId ,第二个参数传入一个函数来定义 Store :

代码语言:javascript
复制
// src/stores/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})
在组件中使用store
代码语言:javascript
复制
<script setup>
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()
// 以下三种方式都会被 devtools 跟踪
counterStore.count++
counterStore.$patch({ count: counterStore.count + 1 })
counterStore.increment()
</script>

<template>
  <div>{{ counterStore.count }}</div>
  <div>{{ counterStore.doubleCount }}</div>
</template>

prettier用来美化代码的结构,eslint用来检测代码的质量,但是二者一起使用会产生冲突,因此需要安装下面的这些依赖。

代码语言:javascript
复制
npm install prettier eslint eslint-plugin-prettier eslint-config-prettier eslint-define-config --save-dev
创建.prettierignore文件

忽略对指定文件的代码格式化

代码语言:javascript
复制
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*
创建.eslintignore文件

忽略对指定文件的代码质量的检测

代码语言:javascript
复制
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile
创建.eslintrc.js文件
代码语言:javascript
复制
// @ts-check
const { defineConfig } = require('eslint-define-config');

module.exports = defineConfig({
  root: true,
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    jsxPragma: 'React',
    ecmaFeatures: {
      jsx: true,
    },
  },
  extends: [
    "eslint:recommended",
    "plugin:vue/vue3-essential",
    "plugin:@typescript-eslint/recommended",
    "prettier",
  ],
  rules: {
    'vue/script-setup-uses-vars': 'error',
    '@typescript-eslint/ban-ts-ignore': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    'vue/custom-event-name-casing': 'off',
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      },
    ],
    'no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      },
    ],
    'space-before-function-paren': 'off',

    'vue/attributes-order': 'off',
    'vue/v-on-event-hyphenation': 'off',
    'vue/multi-word-component-names': 'off',
    'vue/one-component-per-file': 'off',
    'vue/html-closing-bracket-newline': 'off',
    'vue/max-attributes-per-line': 'off',
    'vue/multiline-html-element-content-newline': 'off',
    'vue/singleline-html-element-content-newline': 'off',
    'vue/attribute-hyphenation': 'off',
    'vue/require-default-prop': 'off',
    'vue/html-self-closing': [
      'error',
      {
        html: {
          void: 'always',
          normal: 'never',
          component: 'always',
        },
        svg: 'always',
        math: 'always',
      },
    ],
  },
});
创建prettier.config.cjs文件(在vite的项目里面,注意是cjs后缀)

特别需要注意的是,由于vite设置了"type": "module" 后你的所有js文件默认使用ESM模块规范,不支持commonjs规范,所以必须显式的声明成xxx.cjs才能标识这个是用commonjs规范的,因此需要把你的配置文件都改成.cjs后缀,例如prettier的配置文件。

代码语言:javascript
复制
// prettier.config.js
module.exports = {
 // 一行最多多少个字符
 printWidth: 120,
 // 指定每个缩进级别的空格数
 tabWidth: 2,
 // 使用制表符而不是空格缩进行
 useTabs: true,
 // 在语句末尾是否需要分号
 semi: false,
 // 是否使用单引号
 singleQuote: true,
 // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
 quoteProps: 'as-needed',
 // 在JSX中使用单引号而不是双引号
 jsxSingleQuote: false,
 // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
 trailingComma: 'none',
 // 在对象文字中的括号之间打印空格
 bracketSpacing: true,
 // jsx 标签的反尖括号需要换行
 jsxBracketSameLine: false,
 // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
 arrowParens: 'always',
 // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
 rangeStart: 0,
 rangeEnd: Infinity,
 // 指定要使用的解析器,不需要写文件开头的 @prettier
 requirePragma: false,
 // 不需要自动在文件开头插入 @prettier
 insertPragma: false,
 // 使用默认的折行标准 always\never\preserve
 proseWrap: 'preserve',
 // 指定HTML文件的全局空格敏感度 css\strict\ignore
 htmlWhitespaceSensitivity: 'css',
 // Vue文件脚本和样式标签缩进
 vueIndentScriptAndStyle: false,
 //在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
 //然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
 //对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
 // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
 endOfLine: 'auto'
}

在package.json中配置一个scripts:

代码语言:javascript
复制
"prettier": "prettier --write ."

执行下面的命令,将当前所有的文件按照prettier进行格式化

代码语言:javascript
复制
npm run prettier

Mac系统中使用Command + Shift + P打开

版本较低的浏览器不支持ES6的语法和新API,而Babel默认只转换新的JavaScript句法,不转换新的API,比如Proxy、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法都不会转码。

  • 安装插件:npm i @vitejs/plugin-legacy -D
  • 在vite.config.js中配置
代码语言:javascript
复制
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default {
  plugins: [
     legacy({
         // 需要兼容的目标列表,可以设置多个
         targets: ['defaults', 'ie >= 11', 'chrome >= 52'], 
         additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
         renderLegacyChunks:true,
         // 下面的数组可以自定义添加低版本转换的方法
         polyfills:[
            'es.symbol',
            'es.array.filter',
            'es.promise',
            'es.promise.finally',
            'es/map',
            'es/set',
            'es.array.for-each',
            'es.object.define-properties',
            'es.object.define-property',
            'es.object.get-own-property-descriptor',
            'es.object.get-own-property-descriptors',
            'es.object.keys',
            'es.object.to-string',
            'web.dom-collections.for-each',
            'esnext.global-this',
            'esnext.string.match-all'
        ]
     })
  ]
}
  1. 打包过程中使用@babel/preset-env转换浏览器不支持的语法和新API,为包中的每个块生成相应的转化块;
  2. 生成一个包含 SystemJS 运行时的 polyfill 块;
  3. 在打包文件中生成带有legacy名的文件,每个js脚本文件都有一个与其对应的转化版本;
  4. html文件中新增了一些script-nomodule脚本,这些脚本根据浏览器的支持程度来动态的引入正常版本文件还是带有legacy字样的转化版本文件

仓库地址:https://github.com/webVueBlog/WebGuideInterview

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 组合式API介绍
  • 父传子-defineProps函数
  • 子传父-defineEmits函数
  • 保持响应式-toRef/toRefs函数
  • unref语法糖
  • v-model语法糖
  • useAttrs的使用
  • Vue3的slot
    • 具名插槽
    • 动态插槽
  • 在组件中使用store
  • 创建.prettierignore文件
  • 创建.eslintignore文件
  • 创建.eslintrc.js文件
  • 创建prettier.config.cjs文件(在vite的项目里面,注意是cjs后缀)
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档