vue3快速学习(vue2转vue3学习)

2022年05月10日 阅读数:5
这篇文章主要向大家介绍vue3快速学习(vue2转vue3学习),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

首先要知道的一些方法,基础api。避免看不懂vue3。vue

1.    reactivereact

返回对象的响应式副本,实际上reactive 将解包全部深层的 refs,同时维持 ref 的响应性。json

我的理解:至关于把reactive里面的内容a,push到一个对象中,并且还互相相应。我称这个对象为依赖于a的副本。内容为空,后续经过  obj.count = count  这样的写法,也能将其绑定响应。api

const obj = reactive({ count: 0 })
响应式转换是“深层”的——它影响全部嵌套 property。在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象。数组

类型声明:安全

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>app

例子:async

const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true

// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3

当将 ref 分配给 reactive property 时,ref 将被自动解包。ide

const count = ref(1)
const obj = reactive({})

obj.count = count

console.log(obj.count) // 1
console.log(obj.count === count.value) // true


2.    readonly函数

接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。

和reactive同样也是push给一个对象,可是他这个对象是不能改变它的值的。它自己是能够改变的,并且若是给这个对象设置watch了的话,还会触发它的监听器。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用于响应性追踪
  console.log(copy.count)
})

// 变动 original 会触发依赖于副本的侦听器
original.count++

// 变动副本将失败并致使警告
copy.count++ // 警告!
与 reactive 同样,若是任何 property 使用了 ref,当它经过代理访问时,则被自动解包:

const raw = {
  count: ref(123)
}

const copy = readonly(raw)

console.log(raw.count.value) // 123
console.log(copy.count) // 123

3.    isProxy

检查对象是不是由 reactive 或 readonly 建立的 proxy。


4.    isReactive

检查对象是不是由 reactive 建立的响应式代理

注意:从普通对象建立的只读 proxy,用isReactive检查会是false.如: const plain = readonly({ name: 'Mary' })    console.log(isReactive(plain)) // -> false

例子:

import { reactive, isReactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'John'
    })
    console.log(isReactive(state)) // -> true
  }
}

若是该代理是 readonly 建立的,但包裹了由 reactive 建立的另外一个代理,它也会返回 true。

import { reactive, isReactive, readonly } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'John'
    })
    // 从普通对象建立的只读 proxy
    const plain = readonly({
      name: 'Mary'
    })
    console.log(isReactive(plain)) // -> false

    // 从响应式 proxy 建立的只读 proxy
    const stateCopy = readonly(state)
    console.log(isReactive(stateCopy)) // -> true
  }
}


5.    isReadonly

检查对象是不是由 readonly 建立的只读代理。


6.    toRaw

返回 reactive 或 readonly 代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。
不建议保留对原始对象的持久引用。请谨慎使用。例子:

const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true


7.    markRaw

标记一个对象,使其永远不会转换为 proxy。返回对象自己。
也就是它设置的值,对象,数组等。是否是响应的。

const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 嵌套在其余响应式对象中时也可使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false


重要

markRaw 和下方的 shallowXXX API 使你能够有选择地退出默认的深度响应式/只读转换模式,并将原始的,未被代理的对象嵌入状态图中。它们能够根据状况灵活运用:

有些值不该该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
当渲染具备不可变数据源的大列表时,跳过 proxy 转换能够提升性能。


这些例子是进阶的运用,由于原始选择退出仅在根级别,所以,若是将嵌套在内的、未标记的原始对象添加进响应式对象,而后再次访问该响应式对象,就会获得原始对象被代理后的版本。这可能会致使同一性风险——即执行一个依赖于对象自己的操做,但同时使用同一对象的原始版本和被代理后的版本:

const foo = markRaw({
  nested: {}
})

const bar = reactive({
  // 虽然 `foo` 被标记为原始,但 foo.nested 不是。
  nested: foo.nested
})

console.log(foo.nested === bar.nested) // false


同一性风险一般不多见。然而,为了正确地使用这些 API,同时安全地避免同一性风险,就须要对响应性系统的工做原理有一个充分的理解。


8.    shallowReactive

建立一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
我的理解:相似浅拷贝。

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 自己的性质是响应式的
state.foo++
// ...可是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式

与 reactive 不一样,任何使用 ref 的 property 都不会被代理自动解包。


9.    shallowReadonly
建立一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 自己的 property 将失败
state.foo++        // 失败
// ...但适用于嵌套对象
isReadonly(state.nested) // false
state.nested.bar++ // 适用


与 readonly 不一样,任何使用 ref 的 property 都不会被代理自动解包。


下面是主要使用的api

Refs

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
示例:

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

注意:若是将对象分配为 ref 值,则它将被 reactive 函数处理为深层的响应式对象。能够连着上面的reactive一块儿看着理解。

类型声明:

interface Ref<T> {
  value: T
}

function ref<T>(value: T): Ref<T>

有时咱们可能须要为 ref 的内部值指定复杂类型。能够在调用 ref 时传递一个泛型参数以覆盖默认推断,从而简洁地作到这一点:

const foo = ref<string | number>('foo') // foo 的类型:Ref<string | number>

foo.value = 123 // ok!

若是泛型的类型未知,则建议将 ref 转换为 Ref<T>:

function useState<State extends string>(initial: State) {
  const state = ref(initial) as Ref<State> // state.value -> State extends string
  return state
}

Ref 解包


当 ref 做为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并能够在模板中被访问时,它将自动浅层次解包内部值。只有访问嵌套的 ref 时须要在模板中添加 .value:

<template>
  <div>
    <span>{
  
  { count }}</span>
    <button @click="count ++">Increment count</button>
    <button @click="nested.count.value ++">Nested Increment count</button>
  </div>
</template>

<script>
  import { ref } from 'vue'
  export default {
    setup() {
      const count = ref(0)
      return {
        count,

        nested: {
          count
        }
      }
    }
  }
</script>


 

若是你不想要访问实际的对象实例,可将其用 reactive 包裹:

nested: reactive({
  count
})


访问响应式对象
当 ref 做为响应式对象的 property 被访问或更改时,为使其行为相似于普通 property,它会自动解包内部值:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

若是将新的 ref 赋值给现有 ref 的 property,将会替换旧的 ref:

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1

Ref 解包仅发生在被响应式 Object 嵌套的时候。当从 Array 或原生集合类型如 Map访问 ref 时,不会进行解包:

const books = reactive([ref('Vue 3 Guide')])
// 这里须要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里须要 .value
console.log(map.get('count').value)


响应式状态解构
当咱们想使用大型响应式对象的一些 property 时,可能很想使用 ES6 解构来获取咱们想要的 property:

import { reactive } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = book

遗憾的是,使用解构的两个 property 的响应性都会丢失。对于这种状况,咱们须要将咱们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:

import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // 咱们须要使用 .value 做为标题,如今是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'

isRef

检查值是否为一个 ref 对象。

unref

若是参数是一个 ref,则返回内部值,不然返回参数自己。这是 val = isRef(val) ? val.value : val 的语法糖函数。

这个通常用在ts文件里面。

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 如今必定是数字类型
}


toRef

能够用来为源响应式对象上的某个 property 新建立一个 ref。而后,ref 能够被传递,它会保持对其源 property 的响应式链接。

通常用来对某个对象里面的一个属性进行一个响应连接。

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

当你要将 prop 的 ref 传递给复合函数function时,toRef 颇有用:

export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))    // 把props继承的对象中的foo属性传入useSomeFeature这个函数。
  }
}

即便源 property 不存在,toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用,可选 prop 并不会被 toRefs 处理。


toRefs

将响应式对象转换为普通对象,其中结果对象的每一个 property 都是指向原始对象相应 property 的 ref

结合 ref对象 仅有一个 .value property,指向该内部值。根据原型链就能理解下面     ref 和原始 property 已经“连接”起来了    这句话了.

更简单理解toRef只能传一个,toRefs能传多个。

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:

{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// ref 和原始 property 已经“连接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3


当从组合式函数返回响应式对象时,toRefs 很是有用,这样消费组件就能够在不丢失响应性的状况下对返回的对象进行解构/展开:

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // 操做 state 的逻辑

  // 返回时转换为ref
  return toRefs(state)
}

export default {
  setup() {
    // 能够在不失去响应性的状况下解构
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar
    }
  }
}


customRef

建立一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它须要一个工厂函数,该函数接收 track 和 trigger 函数做为参数,而且应该返回一个带有 get 和 set 的对象。

使用自定义 ref 经过 v-model 实现 debounce 的示例:

<input v-model="text" />

function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
}


类型声明:

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

shallowRef


建立一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。

const foo = shallowRef({})
// 改变 ref 的值是响应式的
foo.value = {}
// 可是这个值不会被转换。
isReactive(foo.value) // false

triggerRef


手动执行与 shallowRef 关联的任何做用 (effect)。

const shallow = shallowRef({
  greet: 'Hello, world'
})

// 第一次运行时记录一次 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这不会触发做用 (effect),由于 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 记录 "Hello, universe"
triggerRef(shallow)

vue2中的建立变量 在3中写法是
props是你上一层组件穿过来的值,你能够本身在这接受,而且定义其类型。如:
props: {
    user: {
      type: String,
      required: true
    }
  },

回归正题

setup(props){
    const a = ref(0)
}

下面开始分析项目中的一些写法:

vue2在data里定义的a,在vue3中这么定义,ref里面定义其初始值。如:a:9, 在vue3写法就是const a = ref(9)watch的写法变成

setup(props){
    const a = ref(0)

watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

// 到最后要讲里面写的方法和变量return回来

return {a}
}

独立的 computed 属性

const twiceTheCounter = computed(() => counter.value * 2)
return {twiceTheCounter}


下面是最新总的写法

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'

// in our component
setup (props) {
  // 使用 `toRefs` 建立对 props 中的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })

  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

使用 setup 函数时,它将接受两个参数:

props
context

setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。

例子:

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}


若是须要解构 prop,能够经过使用 setup 函数中的 toRefs 来完成此操做

const { title } = toRefs(props)

使用toRefs解绑后会安全消除 prop 的响应性。如:

const { title } = toRefs(props)

console.log(title.value)

context 是一个普通的 JavaScript 对象,它暴露三个组件的 property:

// MyBook.vue

export default {
  setup(props, context) {
    // Attribute (非响应式对象)
    console.log(context.attrs)

    // 插槽 (非响应式对象)
    console.log(context.slots)

    // 触发事件 (方法)
    console.log(context.emit)
  }
}


它不是响应式的,这意味着你能够安全地对 context 使用 ES6 解构。

// MyBook.vue
export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}


attrs slots 是有状态的对象,它们老是会随组件自己的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。
请注意,与 props 不一样,attrs 和 slots 是非响应式的。
若是你打算根据 attrs 或 slots 更改应用反作用,那么应该在 onUpdated 生命周期钩子中执行此操做

执行 setup 时,组件实例还没有被建立。所以,你只能访问如下 property:

props
attrs
slots
emit

换句话说,你将没法访问如下组件选项:

data
computed
methods

注意,从 setup 返回的 refs 在模板中访问时是被自动解开的,所以不该在模板中使用 .value

setup 还能够返回一个渲染函数,该函数能够直接使用在同一做用域中声明的响应式状态:

// MyBook.vue

import { h, ref, reactive } from 'vue'

export default {
  setup() {
    const readersNumber = ref(0)
    const book = reactive({ title: 'Vue 3 Guide' })
    // Please note that we need to explicitly expose ref value here
    return () => h('div', [readersNumber.value, book.title])
  }
}

注册组件


#2.x 语法
在 2.x 中,注册一个组件后,把组件名做为字符串传递给渲染函数的第一个参数,它能够正常地工做:// 2.x

Vue.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Clicked {
  
  { count }} times.
    </button>
  `
})

export default {
  render(h) {
    return h('button-counter')
  }
}


#3.x 语法
在 3.x 中,因为 VNode 是上下文无关的,不能再用字符串 ID 隐式查找已注册组件。取而代之的是,须要使用一个导入的 resolveComponent 方法:// 3.x

import { h, resolveComponent } from 'vue'

export default {
  setup() {
    const ButtonCounter = resolveComponent('button-counter')
    return () => h(ButtonCounter)
  }
}

Vue 3 如今提供一个 emits 选项,和现有的 props 选项相似。这个选项能够用来定义一个组件能够向其父组件触发的事件。

在 Vue 2 中,你能够定义一个组件可接收的 prop,可是你没法声明它能够触发哪些事件:

<template>
  <div>
    <p>{
  
  { text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text']
  }
</script>


vue3.的行为
和 prop 相似,如今能够经过 emits 选项来定义组件可触发的事件:

<template>
  <div>
    <p>{
  
  { text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text'],
    emits: ['accepted']
  }
</script>


vue3新增:如今能够在同一个组件上使用多个 v-model 绑定;
新增:如今能够自定义 v-model 修饰符。

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

<!-- 是如下的简写: -->

<ChildComponent
  :title="pageTitle"
  @update:title="pageTitle = $event"
  :content="pageContent"
  @update:content="pageContent = $event"
/>


2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先做用。

#3.x 语法


v-if 会拥有比 v-for 更高的优先级。


在 2.x 中,若是一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么这个独立 attribute 老是会覆盖 object 中的绑定。

<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="red"></div>


在 3.x 中,若是一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么绑定的声明顺序将决定它们如何被合并。

换句话说,相对于假设开发者老是但愿独立 attribute 覆盖 object 中定义的内容,如今开发者可以对本身所但愿的合并行为作更好的控制。

<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="blue"></div>

<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 结果 -->
<div id="red"></div>

实用技巧

script setup是vue3中新引入的语法糖,目的是简化使用Composition API时冗长的模板代码。
如:

<script lang="ts">
import { defineComponent, ref } from 'vue'
import { MyButton } from '@/components'
 
export default defineComponent({
  components: {
    MyButton
  },
  props: {
      name: String
  },
  setup() {
    const count = ref(0)
    const inc = () => count.value++
 
    return {
      count,
      inc,
    }
  },
})
<script>

当咱们须要引入一个components时,不只须要在文件头部显式import进行导入,并且须要components字段加入声明。

不只如此,在setup中声明的变量若是须要被模板使用,那么须要在setup的尾部显式return返回,若是你的组件模板使用的变量很少,还能够勉强接受。可是当你的变量和方法逐渐增长时,每次都要在setup后进行return返回,这无疑是一件枯燥的事情,在重构代码时,你也会面临巨大挑战。

为了解决这个问题,vue3添加了script setup语法糖提案。

像上面这段代码,使用script setup语法糖重构后,将变成:

<script setup lang="ts">
import { defineComponent, ref, defineProps } from 'vue'
import { MyButton } from '@/components'
 
defineProps<{
    name:string
}>()
 
const count = ref(0)
const inc = () => count.value++
 
<script>


基本用法
若要使用script setup语法,只需在原vue文件中的script标签加入setup属性。

<script setup lang="ts">
 
<script>

使用后意味着,script标签内的内容至关于本来组件声明中setup()的函数体,不过也有必定的区别。

使用setup中的参数
<script setup="props, context" lang="ts">
 
<script>


像这样,只要在setup处声明便可自动导入,同时也支持解构语法:

<script setup="props, { emit }" lang="ts">
 
<script>

还有一种写法 每次return的时候,结合三点运算符实现

setup() {
    const state = reactive({
        listParam: {
          searchKey: "",
          mainFollower: ""
        }
    })
    watch([() => state.listParam.customerName, () => state.listParam.mainFollower],
      ([newCustomerName, newMainFoller],[oldCustomerName,oldMainFoller]) => {
        state.listParam.customerName = newCustomerName.trim()
        state.listParam.mainFollower = newMainFoller.trim()
    },{
       immediate: true
    })
    return {
        ...toRefs(state)  // 三点运算符实现多个返回
    }
}

await语法支持
在script setup内能够直接使用await语法:

<script setup>
  const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

Computed 与 watch

computed

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变响应式 ref 对象。

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

或者,接受一个具备 get set 函数的对象,用来建立可写的 ref 对象。

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

类型声明:

// 只读的
function computed<T>(
  getter: () => T,
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写的
function computed<T>(
  options: {
    get: () => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>
interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

用ts的泛类,你能够本身定义computed的类型

watchEffect

当即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变动时从新运行该函数。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)


使用watchEffect监听数据,能够单个或多个,不须要传入监听的数据源,而是直接执行里面的方法,获取到更新后的值

vue文件
<el-form :model="listParam" label-position="left" inline>
    <el-form-item prop="searchKey">
       <el-input v-model="listParam.searchKey" placeholder="请输入关键字" clearable size="small"></el-input>
    </el-form-item>
    <el-form-item prop="mainFollower">
      <el-input v-model="listParam.mainFollower" placeholder="请输入跟进人姓名" clearable size="small"></el-input>
    </el-form-item>
</el-form>

js文件

setup() {
    const state = reactive({
        listParam: {
          searchKey: "",
          mainFollower: ""
        }
    })
    watchEffect(() => {
      state.listParam.searchKey = state.listParam.searchKey ? state.listParam.searchKey.trim() : ""
      state.listParam.mainFollower= state.listParam.mainFollower? state.listParam.mainFollower.trim() : ""
    })
    return {
        ...toRefs(state)
    }
}

类型声明:

function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) => void

type StopHandle = () => void


watch

watch能够获取改变先后的值,watchEffect不能够

watch API 与选项式 API this.$watch (以及相应的 watch 选项) 彻底等效。watch 须要侦听特定的数据源,并在单独的回调函数中执行反作用。默认状况下,它也是惰性的——即回调仅在侦听源发生变化时被调用。

与 watchEffect 相比,watch 容许咱们:

惰性地执行反作用;
更具体地说明应触发侦听器从新运行的状态;
访问被侦听状态的先前值和当前值。


watch侦听单一源
侦听器数据源能够是一个具备返回值的 getter 函数,也能够直接是一个 ref:

// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})


watch侦听多个源
侦听器还可使用数组以同时侦听多个源:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})


[fooRef, barRef]这是你要监听的2个变量。


[foo, bar], [prevFoo, prevBar],这是2个改变先后监听到的值。

例子:

监听单一数据:

setup() {
    const state = reactive({
        listParam: {
          searchKey: ""
        }
    })
    watch(() => state.listParam.searchKey, (newVal,oldVal) => {
        console.log(newVal, oldVal)
        state.listParam.searchKey = newVal.trim()
    })
    return {
        ...toRefs(state)
    }
}


监听多数据:

setup() {
    const state = reactive({
        listParam: {
          searchKey: "",
          mainFollower: ""
        }
    })
    watch([() => state.listParam.customerName, () => state.listParam.mainFollower],
      ([newCustomerName, newMainFoller],[oldCustomerName,oldMainFoller]) => {
        state.listParam.customerName = newCustomerName.trim()
        state.listParam.mainFollower = newMainFoller.trim()
    },{
       immediate: true
    })
    return {
        ...toRefs(state)
    }
}


与 watchEffect 相同的行为
watch 与 watchEffect 在手动中止侦听、清除反作用 (将 onInvalidate 做为第三个参数传递给回调)、刷新时机和调试方面有相同的行为。

类型声明:

// 侦听单一源
function watch<T>(
  source: WatcherSource<T>,
  callback: (
    value: T,
    oldValue: T,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options?: WatchOptions
): StopHandle

// 侦听多个源
function watch<T extends WatcherSource<unknown>[]>(
  sources: T
  callback: (
    values: MapSources<T>,
    oldValues: MapSources<T>,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options? : WatchOptions
): StopHandle

type WatcherSource<T> = Ref<T> | (() => T)

type MapSources<T> = {
  [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}

// 参见 `watchEffect` 共享选项的类型声明
interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean
}