组合式 Composition API

组合式:使用导入的 API 函数来描述组件逻辑。Vue3 组合式 API 通常会与 script setup搭配使用。用函数的方式,让代码更加优雅,并且相关代码有序的组织在一起。

<script setup>
import { ref, onMounted } from 'vue'

// 响应式状态
const count = ref(0)

// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

选项式(Options API):通过对象的多个选项(对象属性和方法:data,methods...)描述组件(vue2x)。缺陷:修改一个功能或新增一个功能,分别需要在data,methods,computed等方法里新增代码,不便于维护和复用。

组合式函数

“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。

与组合式组件类似,组合式组件是在组件内使用vue组合式方法和属性。而组合式函数是将vue3的方法和属性直接定义到函数内部,便于多组件复用。

组合式api写法:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const x = ref(0)
const y = ref(0)

function update(event) {
  x.value = event.pageX
  y.value = event.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

组合式函数写法:

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}

组合式函数 有点类似vue2里面的混入 mixins ,但是弥补了混入的以下缺点:

  • 不清楚数据来源

  • 命名空间冲突

  • 跨mixin数据耦合

基础知识

1.模板语法

文本插值

跟vue2一样,都使用 2个花括号 表示HTML模板。

<span>Message: {{ msg }}</span>

HTML代码

跟vue2一样,都使用v-html插入一段html代码。

<div> <span v-html="rawHtml"></span></div>
<!-- 或 -->
<div> <span :html="rawHtml"></span></div>

属性绑定

跟vue2一样,都使用v-指定绑定自定义属性。在绑定自定义属性时,绑定的值如果是null,undefined时,属性会自动移除。

<div v-bind:id="dynamicId"></div>
<!-- 或 -->
<div :id="dynamicId"></div>

布尔类型,同样的绑定值为假时,属性将会被忽略。

<button :disabled="isButtonDisabled">Button</button>

多个属性绑定

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}

<div v-bind="objectOfAttrs"></div>

动态参数

<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

2.class和style

动态class

const isActive = ref(true)
const hasError = ref(false)

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>

也可以使用对象的形式绑定:

const classObject = reactive({
  active: true,
  'text-danger': false
})

<div :class="classObject"></div>

使用数组绑定多个class:

const activeClass = ref('active')
const errorClass = ref('text-danger')

<div :class="[activeClass, errorClass]"></div>
<div :class="[isActive ? activeClass : '', errorClass]"></div>

在组件上使用动态class,单一根元素样式属性会在组件内最外层元素生效。多个根元素,需要通过 $attrs 接收样式。

<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>

<MyComponent class="baz" />

动态style

绑定对象

const activeColor = ref('red')
const fontSize = ref(30)

<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="{ 'font-size': fontSize + 'px' }"></div>

直接绑定对象

const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})

<div :style="styleObject"></div>

样式多值的情况

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

属性透传 - attribute

属性透传是指元素属性通过父组件传递进来,但是子组件没有声明定义这样的属性。透传的属性会与现有的属性合并。

  • 单个元素为根元素时,透传的 attribute 会自动被添加到根元素上。
  • 多个子元素为根元素时,可以通过 :class="$attrs.class" 接收属性

比仅仅属性可以透传,事件也可以透传到子组件内。如果不需要透传(inheritAttrs: false),可以在组件这样声明:

<script setup>
defineOptions({
  inheritAttrs: false
})
</script>
透传属性获取

通过useAttrs方法获取透传属性。

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>
// 或
export default {
  setup(props, ctx) {
    // 透传 attribute 被暴露为 ctx.attrs
    console.log(ctx.attrs)
  }
}

3.条件和列表

v-if、v-else、v-else-if

条件渲染 通过v-if、v-else、v-else-if ,当满足条件后渲染。

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-show

v-show不能写在template上,v-if可以。因为v-show是通过元素的display属性控制显示隐藏的。

列表渲染

v-for渲染数组

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])

<li v-for="(item, index) in items">
   {{ index }} - {{ item.message }}
</li>

v-for渲染对象

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<ul>
  	<li v-for="(value, key) in myObject">
      {{ key }}: {{ value }}
    </li>
</ul>

v-for 和v-if 为什么不同时在一个元素上?

vue3 因为 v-if的优先级高于v-for ,这意味 v-if条件无法访问到v-for作用域的变量。

// 这时会有报错:未定义
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

解决办法就是分到2个元素上。

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

4.事件处理

与vue2一样,v-on(简写@)指令来监听DOM事件。

内联事件

const count = ref(0)

<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

方法事件

function say(message) {
  alert(message)
}

<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>

事件修饰符

  • stop 停止传递
  • prevent 阻止事件的默认行为
  • self 元素本身的触发时生效
  • capture
  • once 仅触发一次
  • passive 触摸事件监听,改善移动端滚动性能

按键修饰符

<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />
// 组合键
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>

<!-- .exact 修饰符:仅符合条件的时候才触发 -->
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

5.表单model

<input v-model="message" placeholder="edit me" />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
<!-- 单选框 -->
<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>
<!-- 选择器 -->
<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事件后更新,而不是input之后
  • number - 自动转数字,转失败返回原始值
  • trim - 去除输入两端的空格

defineModel

defineModel实现双向绑定。

<!-- 子组件.vue -->
<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

<template>
  <div>parent bound v-model is: {{ model }}</div>
</template>

<!-- 父组件.vue -->
<Child v-model="count" />

以上写法在3.4之前如下:

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

定义必填和默认值:

// 使 v-model 必填
const model = defineModel({ required: true })

// 提供一个默认值
const model = defineModel({ default: 0 })

v-model接收参数

// 父组件 v-model:参数名
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

// 子组件 defineModel(参数名) 获取
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

自定义model修饰符

// 父组件 - 实现输入框第一个字母大写功能
<MyComponent v-model.capitalize="myText" />

// 子组件通过definedModel的setter方法实现自定义修饰符
<script setup>
 // 解构参数
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>

6.模板引用

与vue2不同,vue3里面的选择器ref定义需要和模板名字同名。

<input ref="input">

<script setup>
import { ref, onMounted } from 'vue'
const input = ref(null)
onMounted(() => {
  input.value.focus()
})
</script>

v-for中模板引用

在循环列表上使用ref模板引用得到包含所有元素的集合数组,但不保证顺序一致!

<script setup>
import { ref, onMounted } from 'vue'
const list = ref([])
// ref同名
const itemRefs = ref([])
// 得到元素集合数组
onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

7.组件

定义组件

单文件组件定义

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

也可以在js文件中定义

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // 也可以针对一个 DOM 内联模板:
  // template: '#my-template-element'
}

使用组件

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

传递props

同vue2一样,父组件通过prop向子组件传递参数,不过写法有所不同。

// 在父组件 定义一个title属性的参数
 <ButtonCounter title="组件标题" />
// 子组件
<script setup>
import { ref} from 'vue'
const props = defineProps(['title'])
console.log(props.title)
</script>

<template>
  <button>{{props.title}}</button>
</template>

子组件通过 defineProps 声明props参数,如果没有使用setup语法糖,就需要先定义参数,通过setup参数传递进去:

export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

props 数据都遵循单向数据流的原则,子组件不可修改props参数。

props 类型校验

defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // Number 类型的默认值
  propD: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propE: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propF: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})

子组件emit触发父方法

同vue2一样,子组件通过emit调用父组件方法,从而实现子传父。不过写法不同。

<script setup>
import { ref } from 'vue'
const tt = ref('组件标题')
// 父级方法
const TitleHandle =(e)=>{
    tt.value = e
}
</script>
// 父
<ButtonCounter :title="tt.value" @changeTitle='TitleHandle' />
// 子组件
<script setup>
const emit = defineEmits(['changeTitle'])
emit('changeTitle','修改标题了')
</script>

// 没有使用setup语法糖
export default {
  emits: ['changeTitle'],
  setup(props, ctx) {
    ctx.emit('changeTitle')
  }
}

8.插槽 - slot

插槽用于存放自定义的html结构数据。

默认插槽

插槽只有一个时,就是默认插槽。

<button type="submit">
  <slot></slot>
</button>

插槽可以有默认值,当插槽未传值时,就显示默认值。有数据传递时显示传递的数据。

<button type="submit">
  <slot>保存</slot>  // 默认值
</button>

具名插槽

一个组件内有多个插槽时,就可以给每个插槽定义一个name属性分配唯一的ID。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot name="main"></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

// 使用具名插槽
<BaseLayout>
  <template v-slot:header>
    <!-- header 插槽的内容放这里 -->
  </template>
  <template #main>
    <!-- header 插槽的内容放这里 -->
  </template>
</BaseLayout>

指定具名插槽通过 v-slot:name 或者 #name 。

动态插槽名

插槽名称也可以是一个变量,写法:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

插槽作用域

由于插槽的内容无法访问子组件的状态和数据,插槽数据绑定的时候就需要把数据传递出去,便于子组件插槽内容使用。插槽作用域内容返回的是一个对象。

// 插槽定义
<slot name="header" message="hello"></slot>

// 组件
<MyComponent>
  <template #header="{message}">
    {{ message }}
  </template>
</MyComponent>

9.自定义指令

自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。

自定义指令写法

export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
      /* ... */
    }
  }
}

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

全局自定义指令:

const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

指令钩子

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

钩子参数

指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。
  • binding:一个对象,包含以下属性。
    • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
    • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。
  • prevNode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用。

简化写法

对于自定义指令来说,一个很常见的情况是仅仅需要在 mountedupdated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令。

app.directive('color', (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
})

<div v-color="color"></div>

获取自定义指令对象的属性

<div v-demo="{ color: 'white', text: 'hello!' }"></div>

app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

10.插件

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。

插件安装使用

// 定义插件
const myPlugin = {
  install(app, options) {
    // 配置此应用
  }
}
// plugins/i18n.js
export default {
  install: (app, options) => {
    // 在这里编写插件代码
  }
}

// 入口js
import { createApp } from 'vue'

const app = createApp({})
app.use(myPlugin, {
  /* 可选的选项 */
})

11 .内置组件

Transition过渡动画组件

进入或离开时触发transition动画组件,注意需要定义css

<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>


/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

过渡组件命名

<Transition name="name">
  ...
</Transition>

// css可以根据指定这个过渡组件
.name-enter-active,
.name-leave-active {
  transition: opacity 0.5s ease;
}

.name-enter-from,
.name-leave-to {
  opacity: 0;
}

同样通过name可以使用css3的动画属性animation。

<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Hello here is some bouncy text!
  </p>
</Transition>

//css
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}

TransitionGroup

用法同Transition,区别

  • 默认不渲染元素,需要通过tag绑定渲染的元素。
  • 元素列表都必须有唯一key
  • 过渡动画会在列表的子元素上
<TransitionGroup name="list" tag="ul">
  <li v-for="item in items" :key="item">
    {{ item }}
  </li>
</TransitionGroup>

.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

keepAlive

缓存组件在动态组件切换的时候为了保存组件的数据,可以使用keepAlive组件,非活跃的组件数据会被缓存下来。不然数据每次都会被清空。

<!-- 非活跃的组件将会被缓存! --> 
<KeepAlive>   
	<component :is="activeComponent" />
</KeepAlive>
  • include 那些组件生效- 接收组件name名,多个以,分割 或数组

  • exclude 排除那些组件- 接收组件name名,多个以,分割 或数组

  • max 设置最大缓存组件数 - 超过最长限度后,最早缓存数据会被释放缓存生命周期函数

 <script setup> 
 import { onActivated, onDeactivated } from 'vue'  
 onActivated(() => {   // 调用时机为首次挂载   // 以及每次从缓存中被重新插入时 })  
 onDeactivated(() => {   // 在从 DOM 上移除、进入缓存   // 以及组件卸载时调用 }) 
 </script>

Teleport

模态组件用于把弹出层“传送”到DOM结构的指定位置。to 通过to指定到元素节点。disabled 指定场景下禁用

<button @click="open = true">Open Modal</button>  
<Teleport to="body" :disabled="isMobile">   
    <div v-if="open" class="modal"> 
        <p>Hello from the modal!</p>     
		<button @click="open = false">Close</button>   
	</div> 
</Teleport>

Suspense

异步组件用于处理异步数据组件的处理。Suspense 组件有2个插槽:#default 和 #fallback。Suspense 遇到任何异步都会进入挂起状态,显示fallback组件,所有异步完成之后进入完成状态,显示默认组件。

<Suspense>   
<!-- 具有深层异步依赖的组件 -->   
<Dashboard />   
<!-- 在 #fallback 插槽中显示 “正在加载中” -->   
<template #fallback>Loading... </template> 
</Suspense>

生明周期

与vue2相比,vue3的生命周期函数移除了beforeCreat和created阶段,改成了setup,卸载函数从destroyed 改成了 onUnmounted,vue3其他的生命周期函数在前面加on,具体如下:

  • setup() 创建时
  • onBeforeMount 挂载前
  • onMounted 挂载时
  • onBeforeUpdate 组件更新前
  • onUpdated 组件更新时
  • onBeforeUnmount 卸载前
  • onUnmounted 卸载时

当页面组件用到缓存keep-alive时,会出现2个特殊的生命周期:

  • onActivated 缓存组件页面进入时
  • onDeactivated 缓存组件页面离开时

捕获子组件异常状态的钩子函数:

  • onErrorCaptured 异常函数

vue3定义全局变量:app.config.globalProperties 。

TS 扩展全局属性:

import axios from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: typeof axios
    $translate: (key: string) => string
  }
}

函数

setup

关键字setup这个表示,在编译时进行一些处理,让我们更简洁的使用组合式API。setup在解析其它组件选项之前被调用(beforeCreat之前)。this指向了undefined。

默认两种写法:

// 方法一:语法糖版本
<script setup>
...
</script>
// 方法二:函数版本
<script>
export default {
  setup() {
  	...
    // 必须有return
    return {
        
    }
  }
}
</script>

函数版本接收2个参数:props和context。同时setup函数必须返回一个对象,用于将组件的参数和方法抛出,让其他组件访问。

  • props 组件接收到的所有参数
  • context 组件实例相关的属性和方法
<script>
import { ref } from 'vue';
export default {
  name:'HIVUE',
  setup(props,context) {
  	const msg = ref('你好,vue!')
    const changeMsg = ()=>{
        msg.value = 'vue3'
    }
    // 必须有return
    return {
        msg,
        changeMsg
    }
  }
}
</script>

注意:vue3在setup函数中,不能使用this访问选项式的方法和数据。

setup返回值有2种:

  • 包含页面变量的对象
  • 页面渲染函数
export default {
  name:'vue3',
  setup() {
    // 返回一个渲染函数,页面内容被渲染成hhhh
    return function(){
    	return 'hhhhhh'
    }
  }
}
setup语法糖写法缺点 - 组件无法命名缺失了name属性,默认使用文件名作为组件 name。

如何在setup语法糖前提下,自定义组件的名字?

使用插件:vite-plugin-vue-setup-extend

然后再 script 上加 name属性:

<script setup name='组件名称'>
...
</script>

Ref 和Reactive

声明响应数据,使用Ref和Reactive 。

Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。Ref 会使它的值具有深层响应性。Ref定义的响应式基本类型数据,需要通过.value访问。

ref 既可以定义基本类型,也可以定义 对象类型的数据。

<script setup>
import { ref } from 'vue'
const count = ref(0)
// 定义对象
const obj = ref({name:'2222'})  
function increment() {
  count.value++
  // 响应式的vue变成的对象,同理定义数组响应式的value就是数组类型
  obj.value.name = '张三'
}
</script>

Reactive 声明响应式对象(引用类型变成响应式对象)。ref声明对象时,也是调用了reactive。reactive返回的事代理后(proxy)的对象。

import { reactive } from 'vue'
const state = reactive({ count: 0 })

reactive 的局限性:

  • 只能用于对象类型(对象,数组,和Map,Set集合),不能声明 string,number原始类型

  • 使用解构赋值后,响应式会丢失,需要使用 toRefs 转一下响应式数据

    const obj = reactive({name:'qeqw'})
    // 这样得到的name还是响应式的数据
    let {name} = toRefs(obj)
    
  • 重复赋值,响应式会丢失

在项目中,常用ref定义基本类型,使用reactive 定义引用类型。

computed -计算属性

计算属性会根据响应数据依赖值变化,然后同步更新。当依赖值不发生变化时,计算属性就具备缓存功能。而方法总是会在调用时执行,计算属性依赖值不变,不会重新执行。

// 只读的计算属性
const now = computed(() => Date.now())

计算属性(函数表达式)默认是只读的。但特殊场景也可写。计算属性可以通过 getter和setter来创建。

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
// 下面会触发计算属性的可写方法
fullName.value = 'John Doe'
</script>

计算属性的值类型其实就是 ref类型的响应式数据,需要通过调用 ref.value 进行读写。

watch

监听一个属性,发生变化时触发。

watch(name,(newv,oldv)=>{
	// 回调
},{
  //开启首次监听
  immediate: true, // 首次监听
  deep:true, // 深度监听
  once: true // 仅监听一次
})

watch不能监听响应式对象的属性值,需要通过getter方法

const obj = reactive({ count: 0 })
// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

watch 只能监听以下4中数据:

  • ref定义的数据
  • reactive定义的数据,隐式的就是强制深度监听,无法关闭监听!!!
  • 函数返回值(getter函数)
  • 一个包含上述内容的数组

如何解除watch监听事件?

watch创建时返回的是一个函数,解除时调用该函数就可以自动解除对应的监听事件。

const watEvent =watch(name,(newv,oldv)=>{
	// 回调
}
// 移除监听
watEvent()

watchEffect

watchEffect会自动追踪其内部回调函数中使用的任何响应式数据,并在这些数据变化时重新运行该回调函数,无需指定依赖项。

const state = reactive({
  count: 0,
  message: 'Hello'
})
watchEffect(() => {
  // 当count,message变化时自动触发
  console.log(`Count is now ${state.count}.`)
  console.log(`Message is now "${state.message}".`)
})

停止watchEffect监听使用 watchEffect(() => {})

const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()

依赖注入Provide/inject

依赖注入有2部分组成:Provide - 提供数据,Inject - 注入数据 。主要是用于解决深层次数据传递的问题(祖孙组件通信)。

Provide提供数据

写法:provide(key,value)

<script setup>
import { provide } from 'vue'

provide( 'message', 'hello!')
</script>

全局提供依赖:

import { createApp } from 'vue'
const app = createApp({})
app.provide( 'message',  'hello!')

Inject注入数据

接收provide的数据,需用inject。在需要的子组件写:

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

inject 接收默认数据,当收到数据之后会把默认数据替换。

const value = inject('message', '这是默认值')

toValue

toValue() 是一个在 3.3 版本中新增的 API。它的设计目的是将 ref 或 getter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。

import { toValue } from 'vue'

function useFeature(maybeRefOrGetter) {
  // 如果 maybeRefOrGetter 是一个 ref 或 getter,
  // 将返回它的规范化值。
  // 否则原样返回。
  const value = toValue(maybeRefOrGetter)
}