vue3学习

2022年03月10日 阅读1371次 分类:笔记 标签:vue

版权声明:本文为博主原创或转载自网络,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:http://www.zhuyanjun.cn/note/1935.html

以下示例使用组合式api。

在组合式api中获取this实例,需要使用:

import {getCurrentInstance} from "vue";

const instance = getCurrentInstance();
const _this = instance.ctx;

模版语法

  • 可以动态绑定多个值 v-bind="{id:'myId',class:'myClass'}"
  • 表达式的条件控制只支持三元写法
  • 模板调用一个方法时{{ formatDate(date) }}需注意组件每次更新时都会被重新调用,要保证不改变数据或触发异步操作,造成死循环。
  • 支持动态属性和动态事件的绑定<a :[attributeName]="url" @[eventName]="doSomething>,注意:不支持表达式,如需可使用计算属性。
  • 支持多个根元素

响应式

  • ref()函数用于任何类型数据深层响应式。
    • 专门用于组合式api,接收一个参数并返回一个带value属性的对象。
    • 在js中使用需要带.value,模版中使用无需,例如<button @click="count++">
    • 使用 const counter = ref(0);console.log(count.value);count.value ++;console.log(count.value);,结果0、1。
    • 如果需要再在改变数据后执行其他代码请使用nextTick()函数,例如:async function increment() { counter++;await nextTick(); // 其他执行代码}
    • 选项式api需要在setup()函数返回
  • reactive()用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)的深层响应式。
    • 不能替换整个对象,否则将失去响应式,如:let state = reactive({ count: 0 });state = reactive({ count: 1 }),state失去了响应式。
    • 使用解构时或属性传递给函数时,得到变量会失去响应式。如:let { count } = reactive({ count: 0 }),count将失去响应式。
  • shallowRef()函数同ref函数,区别是它是浅层响应式。
  • shallowReactive()行数同理它是浅层响应式。

计算属性

  • 计算属性会返回一个ref对象,使用时和ref对象一样

  • 计算属性可以通过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(' ')
    }
    })
    </script>
    现在当你再运行 fullName.value = 'John Doe' 时,setter 会被调用而 firstName 和 lastName 会随之更新。

class于style的绑定

  • 绑定对象
    • 模板中绑定一个对象const isActive = ref(true)在模板中<div :class="{ active: isActive }">
    • 直接绑定一个对象const classObject = reactive({active: true,'text-danger': false})在模板中<div :class="classObject">
    • 也可以绑定一个返回对象的计算属性
  • 绑定数组
    • 模板中绑定一个数组const activeClass = ref('active')在模板中<div :class="[activeClass]">
    • 模板中使用三元条件<div :class="[isActive ? activeClass : '']">
    • 可以嵌套对象<div :class="[{ active: isActive }]">
  • 对组件使用时,如果组件有多个根元素,可以通过组件的$attrs属性来指定作用于哪个根元素。

渲染

  • v-ifv-for同时存在时,v-if会先被执行,其中无法使用v-for的变量值进行判断。

  • 在定义v-for变量时,可以使用解构:

    <li v-for="{ message } in items">
      {{ message }}
    </li>
    
    <!-- 有 index 索引时 -->
    <li v-for="({ message }, index) in items">
      {{ message }} {{ index }}
    </li>
  • 可以使用of关键词代替in,作用类似js的迭代,<div v-for="item of items">

  • v-for可以遍历一个对象<li v-for="(value, key, index) in myObject">

  • v-for可以接受一个数字,值从1开始,<span v-for="n in 10">n的第一个值为1

  • 当使用<template v-for>时,key应该被放置在这个<template>容器上

事件

  • $event用于模板中的方法传递原生的事件参数<button @click="warn('123', $event)">

  • 事件修饰符,多个需注意调用顺序

  • .stop 单击事件将停止向上传递

  • .prevent 阻止事件的默认行为

  • .self 事件来源本身时才会触发,子元素不触发

  • 以下与原生 addEventListener 事件相对应:

    • .capture 捕获模式,指向内部元素的事件,在被内部元素处理前,先被外部处理
    • .once 点击事件最多被触发一次
    • .passive 强制执行,不会因为事件中有阻止行为就停止,一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。请不要和.prevent同时使用。
  • 按键修饰符可以监听键盘按键的事件<input @keyup.enter="submit" /> 常用的有.enter、.tab、.delete (捕获“Delete”和“Backspace”两个按键)、.esc、.space、.up、.down、.left、.right、.ctrl、.alt、.shift、.meta(在 Mac 键盘上,meta 是 Command 键 (⌘)。在 Windows 键盘上,meta 键是 Windows 键 (⊞))

  • 鼠标按键修饰符,.left、.right、.middle

  • .exact 精确修饰符,在使用按键修饰符时只能按指定键,才会触发例如:

    <!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
    <button @click.ctrl="onClick">A</button>
    
    <!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
    <button @click.ctrl.exact="onCtrlClick">A</button>
    
    <!-- 仅当没有按下任何系统按键时触发 -->
    <button @click.exact="onClick">A</button

双向绑定

  • 多个复选框绑定到同一个数组或集合

  • const checkedNames = ref([]);
    <input type="checkbox" value="Jack" v-model="checkedNames">
    <input type="checkbox" value="John" v-model="checkedNames">
    <input type="checkbox" value="Mike" v-model="checkedNames">
  • select需要提供一个空值选项,否则初始值为空时,ios下将无法选择<option disabled value="">Please select one</option>

  • 复选框true-value、false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。如:<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />

  • v-model 同样也支持非字符串类型的值绑定,如:<select v-model="selected"></select><option :value="{ number: 123 }">123</option></select>

  • 修饰符<input v-model.lazy="msg" />

    • .lazy在change时触发
    • .number输入自动转为数字
    • .trim去除内容中两端的空格

生命周期

  • 调用 onMounted 时,需在组件初始化时被同步注册,不能异步,例如放在setTimeout中是无效的。
  • onMounted() 也可以在一个外部函数中调用,只要调用栈是同步的,且最终起源自 setup() 就可以。
  • 生命周期顺序setup()、onMounted()、onUpdated()、onUnmounted()、onBeforeMount()、onBeforeUpdate()、onBeforeUnmount()、onErrorCaptured()、onRenderTracked()、onRenderTriggered()、onActivated()、onDeactivated()、onServerPrefetch()、onErrorCaptured(),如果使用了KeepAlive组件,还包括onActivated、onDeactivated

数据监听

  • watch 的第一个参数: ref (包括计算属性)、响应式对象、 getter 函数(() => x.value + y.value)、或多个数据源组成的数组

  • 要监听响应式对象的属性,需要使用getter函数,例如:

  • const obj = reactive({ count: 0 });
    
    watch(
      () => obj.count,
      (count) => {
        console.log(`count is: ${count}`)
      }
    )
  • 监听响应式对象,默认为深监听,使用getter函数将是浅监听,第三个参数加上deep会变为深监听{ deep: true }

  • 第三个参数加上{ immediate: true },将在初始时直接调用

  • 第三个参数加上{ once: true },将只会监听一次

  • watchEffect()会自动监听回调中所有被用到的属性,简化操作

  • // 将会自动监听todoId
    watchEffect(async () => {
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
      )
      data.value = await response.json()
    })
    watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。
  • 监听器的回调是在父组件更新后,所属组件Dom被更新前,此时在监听器回调中无法访问被更新后的dom的。

  • 第三个参数加上flush: 'post'可以在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM。

  • watchPostEffect()作用同上。

  • 第三个参数加上flush: 'sync'可以创建一个同步触发的侦听器,它会在 Vue 进行任何更新之前触发。注意:同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。

  • watchSyncEffect()作用同上。

  • 异步创建监听器时,需要手动停止防内存泄漏const unwatch = watchEffect(() => {});unwatch();,如果需要等待一些异步数据,可以看在监听器回调中使用if判断

访问dom元素

<input ref="input">属性ref可以直接访问dom元素,例如:

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

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

如果是在v-for模板中循环ref将是一个数组

<script setup>
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>
  • ref还可以绑定为一个函数,会在每次组件更新时都被调用<input :ref="(el) => { }">,当该元素被卸载时,el值为null
  • 在子组件上使用ref,这种情况下引用中获得的值是组件实例。
    • 如果子组件使用的是选项式 API,对子组件的每一个属性和方法都有完全的访问权

    • 使用了<script setup>的组件,默认是无法访问子组件的任何东西,除非子组件在其中通过 defineExpose() 显式暴露(无需导入)。

    • <script setup>
      import { ref } from 'vue'
      
      const a = 1
      const b = ref(2)
      
      // 像 defineExpose 这样的编译器宏不需要导入
      defineExpose({
        a,
        b
      })
      </script>

组件

  • defineProps()方法用于组件传递数据,无需导入,如:defineProps(['title'])

  • defineEmits()方法用于定义组件的事件,无需导入。

  • <script setup>
    const emit = defineEmits(['enlarge-text'])
    emit('enlarge-text')
    
    // 事件校验
    const emit2 = defineEmits({
      // 没有校验
      click: null,
    
      // 校验 submit 事件
      submit: ({ email, password }) => {
          if (email && password) {
          return true
          } else {
          console.warn('Invalid submit event payload!')
          return false
          }
      }
       emit2('submit', { email, password })
    })
    </script>
  • 动态组件<component :is="{被注册的组件名或导入的组件对象}"></component>

  • 组件书写必须闭合标签<my-component></my-component>

  • 元素标签限制解决,例如 table下必须tr等

  • <table>
       <!-- 会出现问题 -->
       <blog-post-row></blog-post-row>
    
       <!-- 需要使用is属性和vue:前缀,才可以解析 -->
       <tr is="vue:blog-post-row"></tr>
    </table>
  • 全局注册组件app.component('MyComponent', MyComponent).component('ComponentA', ComponentA)

  • props如果是对象或数据,默认值需要使用工厂函数返回

    • 如果props给出null或undefined则跳过类型检查

    • 自定义验证validator(value, props) {}

    • defineProps() 中的参数不可以访问 <script setup> 中定义的其他变量

    • props的type类型检测可以是自定义的类

    • 布尔值默认值类型转换,当type类型包含bool时,默认值为布尔值,只有和string同时使用且string在bool前默认值是为空字符串

    • // disabled 将被解析为空字符串 (disabled="")
      defineProps({
        disabled: [String, Boolean]
      })

v-model双向绑定

  • 建议使用 defineModel()

  • <script setup>
        const model = defineModel()
    </script>
    
    <template>
        <input v-model="model" />
    </template>
  • 以前实现

  • <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

  • <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>
    
    <UserName
    v-model:first-name="first"
    v-model:last-name="last"
    />

属性透传

只有一个根节点的组件,属性默认透传在根节点,如果根节点是组件,会继续传递给这个组件。

使用inheritAttrs: false来禁止这种传递。

<script setup>
defineOptions({
  inheritAttrs: false
})
// ...setup 逻辑
</script>

<template>
  <div class="btn-wrapper">
    <button class="btn" v-bind="$attrs">click me</button>
  </div>
</template>

如果是多根节点,默认不会透传。需要手动绑定v-bind="$attrs"

透传的属性不具备响应式特性,如需要请使用props。

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

// 访问组件中所有透传的属性
const attrs = useAttrs()
</script>

插槽

  • 具名插槽定义<slot name="header"></slot>,使用<template v-slot:header></template>

  • v-slot:可以简写为#,例如:<template #header></template>

  • 还可以定义动态插槽<template v-slot:[dynamicSlotName]><template #[dynamicSlotName]>

  • 插槽返回子组件的数据

  • <!-- <MyComponent> 的模板 -->
    <div>
        <slot :text="greetingMessage" :count="1"></slot>
    </div>
    
    <MyComponent v-slot="slotProps">
        {{ slotProps.text }} {{ slotProps.count }}
    </MyComponent>

注入

  • provide() 函数为后代组件提供数据

  • <script setup>
    import { provide } from 'vue'
    
    provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
    </script>
  • 还可以使用app应用全局的provide

  • import { createApp } from 'vue'
    
    const app = createApp({})
    
    app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
  • inject() 函数为当前组件注入父组件provide()提供的数据。

  • <script setup>
    import { inject } from 'vue'
    
    const message = inject('message','这里可以定义默认值,防止父级没有provide提供')
    </script>

逻辑复用

组合式函数使用use开头、自定义指令以v开头,驼峰法命名。

组合式函数

  • 组合式函数使用use开头,驼峰写法,例如:useMouse()

  • 一个组合式函数可以调用一个或多个其他的组合式函数。

  • toValue()格式值,如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。

  • 组合式函数结合watchEffect()就可以做到自动响应操作。

  • 组合式函数内返回的对象建议使用ref,这样在使用时返回的值可以结构后保持响应式

  • // x 和 y 是两个 ref
    const { x, y } = useMouse();
    
    // 如果不想用上面的结构,你可以看使用reactive包裹
    const mouse = reactive(useMouse())
    // mouse.x 链接到了原来的 x ref
    console.log(mouse.x)
  • 组合式函数只能在setup中同步调用

自定义指令

  • 自定义指令以v开头的驼峰写法。 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。

  • <script setup>
    // 在模板中自定义指令 v-focus
    const vFocus = {
      mounted: (el) => el.focus()
    }
    </script>
    
    <template>
      <!--使用-->
      <input v-focus />
    </template>
  • 可以在入口文件中定义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:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
    • vnode:代表绑定元素的底层 VNode。
    • prevNode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用
    • 钩子参数
    • 示例:<div v-example:foo.bar.bar2="baz"> foo为参数(参赛可以是动态的v-e:[arg].bar=),bar为修饰符,baz为值(值可以是对象)。
  • 简化指令,后跟一个函数会在动在mounted 和 updated 时都调用

  • app.directive('color', (el, binding) => {
      // 这会在 `mounted` 和 `updated` 时都调用
      el.style.color = binding.value
    })
  • 组件上使用时注意:组件只能有一个根节点

插件

插件形式:

import { createApp } from 'vue'
const app = createApp({})

// 1、安装函数,如:myPluginFun(app,options){  }
app.use(myPluginFun, {
  /* 可选的参数,会传递给 安装函数myPluginFun */
})

// 2、拥有 install() 方法的对象
const myPlugin = {
  install(app, options) {
    // 配置此应用
  }
}

插件常见场景

  • 通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令
  • 通过 app.provide() 使一个资源可被注入进整个应用
  • 向 app.config.globalProperties 中添加一些全局实例属性或方法

pinia状态管理器

创建

使用defineStore()函数创建一个Store,第一个参数为id,第二个参数可接受两类值:Setup 函数或 Option 对象

// stores/counter.js
import { defineStore } from 'pinia'

// 对象创建
export const useCounterStore = defineStore('counter', {
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    return { count: 0 }
  },
  getters:{
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

// 函数创建,需要return才行
export const useCounterStore = defineStore('counter', () => {
  // 等于state 属性
  const count = ref(0);
  // 等于getters属性
  const doubleCount = computed(() => count.value * 2)
  // 等于actions
  function increment() {
    count.value++
  }

  return { count, increment,doubleCount }
})

使用

<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量
const store = useCounterStore()

console.log(store.count)
</script>

store 是一个用 reactive 包装的对象,无需.value

使用 storeToRefs()可以从store结构,保持数据的响应式,继续上面例子:

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore();

// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store);

// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

其他

性能优化时,尽量减少不必要的组件抽象,使用组合式函数。可以使用shallowRefshallowReactive浅层响应式函数。

vue渲染html时,可以使用h()函数和jsx[查看语法](https://cn.vuejs.org/guide/extras/render-function.html#jsx-tsx])。


(本篇完)

是不是学到了很多?可以

版权声明:本文为博主原创或转载自网络,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:http://www.zhuyanjun.cn/note/1935.html