阅读视图

发现新文章,点击刷新页面。

Vue3 JSX 语法速查:v-model、事件、插槽一网打尽

Vue3 JSX 以 JS 原生逻辑替代模板语法,核心转换规则如下:

  • 条件渲染v-if&&/ 三元表达式;v-show 直接保留
  • 循环渲染v-for → 数组 map 遍历
  • 事件处理:事件名驼峰化,修饰符用 withModifiers 包裹
  • 双向绑定v-model 支持基础、自定义名、修饰符及多 model 场景
  • 插槽:支持默认 / 具名 / 作用域插槽,渲染与传递写法清晰
import { ref } from 'vue'
export default function add(props, ctx) {
    let visible = props.visible
    let form = props.form
    let plus = props.isPlus
    let addFormRef = ref()
    async function handleOk() {
        const error = await addFormRef.value.validate()
        if (error) return
        let url = plus ? 'insert' : 'update'
        let msg = plus ? '新增成功' : '更新成功'
        proxy.post(`/api/mapbus/pm/project/${url}`, form).then((res) => {
            proxy.$message.success(msg)
            ctx.emit('success')
        })
    }
    return (
        <a-drawer visible={visible} title={plus ? '新建' : '编辑'} width="30%" onOk={handleOk}>
            <a-form ref={addFormRef} model={props.form} auto-label-width>
                <a-form-item field="projectName" label="项目名称" validate-trigger="blur" rules={{required: true, message: '项目名称必填'}}>
                    <a-input v-model={form.projectName}></a-input>
                </a-form-item>
                <a-form-item field="remark" label="说明">
                    <a-input v-model={form.remark}></a-input>
                </a-form-item>
            </a-form>
        </a-drawer>
    )
}

一、常规逻辑

  • v-if: 转换成 js逻辑,三元表达式也可;
  • v-show: 支持;可直接写成v-show;
  • v-for:转换成js逻辑,forEach,map...等数组循环方式;
  • 事件:依驼峰命名方式写,onClickonMouseOver...等等
  • js: 用花括号包起来;
  • 对象:用两个花括号,外围的括号是js的括号,里面括号才是对象的括号;

二、事件

  1. 事件以驼峰命名方式定义;

  2. 事件要是有修饰符的话:

  • 以常规驼峰命名写;
<input
  onClickCapture={() => {}}
  onKeyupOnce={() => {}}
  onMouseoverOnceCapture={() => {}}
  />
  • 可以使用 withModifiers 函数
<div onClick={withModifiers(() => {}, ['self'])} />

三、v-model

Vue3 jsx新特性,支持v-model使用

(一)、modelValue

如果组件的v-modelmodelValue的话,那使用很简单;

renderDropdown(h){
const value = "value"
return <custom-component v-mode={value}>
code...
</custom-component>
}

自定义value

比如v-model:visible=show写法如下:

renderDropdown(h){
  const show = "true"
  return <el-popover v-model={[show, 'visible']}>
    code...
  </el-popover>
}

修饰符

  1. v-model后面跟着,使用(_)代替(.);vModel_trim = {value}
  2. withModifiers
// template<input v-model="val" />
<input v-model:name="val">
<input v-model.trim="val">
<input v-model:name.trim="val">

// tsx
<input v-model={val} />
<input v-model={[val, 'name']} />
<input v-model={[val, ['trim']]} />
<input v-model={[val, 'name', ['trim']]} />

多个model

// template
<A v-model="foo" v-model:bar="bar" />

// tsx
<A v-models={[[foo], [bar, "bar"]]} />

四、插槽

(一·)、渲染插槽

  1. js的方式
// 默认插槽
<div>{slots.default()}</div>

// 具名插槽
<div>{slots.footer({ text: props.message })}</div>
  1. dom的形式
export default function common(props, ctx) {
    const children = ctx.slots.default()[0]
    function handleBack() {
        ctx.emit('back', 12)
    }
    return (
        <div className={commonCss.panelContainer}>
            <div className={commonCss.header}>
                <MyIcon name="return" size={16} style={{cursor: 'pointer'}} onClick={handleBack}></MyIcon>
            </div>
            <children></children>
        </div>
    )
}

(二)、传递插槽

// 默认插槽
<MyComponent>{() => 'hello'}</MyComponent>

// 具名插槽
<MyComponent>{{
  default: () => 'default slot',
  foo: () => <div>foo</div>,
  bar: () => [<span>one</span>, <span>two</span>]
}}</MyComponent>

也可以如下:

// 具名插槽
<MyComponent v-slots={{
  default: () => 'default slot',
  foo: () => <div>foo</div>,
  bar: () => [<span>one</span>, <span>two</span>]
}}></MyComponent>

(四)、作用域插槽

<MyComponent>{{
  default: ({ text }) => <p>{ text }</p>  
}}</MyComponent>

五、总结

模板语法(Template) JSX/TSX 语法 说明
v-if="show" {show && <div>内容</div>} 三元表达式也可:{show ? <div>显示</div> : <div>隐藏</div>}
v-show="show" <div v-show={show}>内容</div> 直接支持 v-show
v-for="item in list" {list.map(item => <div>{item}</div>)} 需加 key
@click.stop="handleClick" onClick={withModifiers(handleClick, ['stop'])} 修饰符用 withModifiers 包裹
v-model:visible="show" <el-popover v-model={[show, 'visible']}>

感谢您抽出宝贵的时间观看本文;本文是 Vue3 核心 API 系列的第 2 篇,后续会持续更新 computed、ref/reactive、生命周期等实战内容,同时正在整理「Vue3 完整项目实战小册」(包含从 0 到 1 开发小程序 / 管理系统的全流程),欢迎关注~

《Vue3 watch详情:deep/immediate/flush/once 全用法 + 踩坑总结》

本文全面解析 Vue3 watch 所有用法,包含监听基础类型、引用类型、多个数据源、停止监听、深度监听、新旧值获取、与 watchEffect 区别,适合前端开发日常使用与面试准备。

《Vue3 watch详情:deep/immediate/flush/once 全用法 + 踩坑总结》

1. API介绍

watch(WatcherSource, Callback, [WatchOptions])

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

interface WatchOptions extends WatchEffectOptions {
    deep?: boolean // 默认:false 
    immediate?: boolean // 默认:false 
    flush?: string // 默认:'pre'
}

参数说明:

WatcherSource: 用于指定要侦听的响应式数据源。侦听器数据源可以是返回值的 getter 函数,可以直接 是 ref reactive

callback : 执行的回调函数,可依次接受 newValue , oldValue 作为参数。

watchOptions: deep immediate flush once(3.4新增) 可选

  • 当需要对响应式对象进行深度监听时,设置 deep: true

  • 默认情况下watch是惰性的,当我们设置 immediate: true 时,watch会在初始化时立即执行回调函数

  • flush 选项可以更好地控制回调的时间。它可设置为 pre、post 或 sync

    • 默认值是 pre,指定的回调应该在DOM渲染前被调用。
    • post 值是可以用来将回调推迟到DOM渲染之后的。如果回调需要通过 $refs 访问更新的 DOM 或子组件,那么则使用该值。
    • 如果 flush 被设置为 sync,一旦值发生了变化,回调将被同步调用(少用,影响性能)。
  • once: true : 一次性侦听器;只生效一次(3.4新增参数)

WatchSource必须是引用对象;因此它的写法有两种;

  • 如果是响应式的引用对象,如ref,reactive; 直接写变量名即可;
  • 如果是基础数据,需要使用getter函数;

getter函数的使用除了上面的情况还有一个就是获取引用对象新旧值的时候会用到;

2. 侦听单个数据源及停止侦听

<script setup>
  import { watch, ref, reactive } from 'vue'
  // 侦听一个 getter
  const person = reactive({name: '小松菜奈'})
  watch(
    () => person.name,
    (value, oldValue) => {
      console.log(value, oldValue)
    }, {immediate:true}
  )
  person.name = '有村架纯'

  // 直接侦听ref  停止侦听
  const ageRef = ref(16)
  const stopAgeWatcher = watch(ageRef, (value, oldValue) => {
    console.log(value, oldValue)
    if (value > 18) {
      stopAgeWatcher() // 当ageRef大于18,停止侦听
    }
  })

  const changeAge = () => {
    ageRef.value += 1
  }
</script>

现象

配置了immediate:truewatch,在初始化时触发了一次watch的回调。我们连续点击增加年龄,当年龄 的当前值大于18时,watch停止了侦听。

结论

侦听器数据源可以是返回值的 getter 函数,也可以直接是 refwatch函数是有返回值的,返回值是停止器,然后通 过执行停止器() 函数来停止侦听。

3. 监听多个数据源

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

  const name = ref('小松菜奈')
  const age = ref(25)

  watch([name, age], ([name, age], [prevName, prevAge]) => {
    console.log('newName', name, 'oldName', prevName)
    console.log('newAge', age, 'oldAge', prevAge)
  })

  // 如果你在同一个函数里同时改变这些被侦听的来源,侦听器只会执行一次
  const change1 = () => {
    name.value = '有村架纯'
    age.value += 2
  }

  // 用 nextTick 等待侦听器在下一步改变之前运行,侦听器执行了两次
  const change2 = async () => {
    name.value = '新垣结衣'
    await nextTick()
    age.value += 2
  }
</script>

现象

以上,当我们在同一个函数里同时改变nameage两个侦听源,watch的回调函数只触发了一次;当我们 在nameage的改变之间增加了一个nextTickwatch回调函数触发了两次。

结论

我们可以通过watch侦听多个数据源的变化。如果在同一个函数里同时改变这些被侦听的来源,侦听器只会 执行一次。若要使侦听器执行多次,我们可以利用 nextTick ,等待侦听器在下一步改变之前运行。

4. 侦听引用对象

<template>
  <div>
    <div>ref定义数组:{{arrayRef}}</div>
    <div>reactive定义数组:{{arrayReactive}}</div>
  </div>
  <div>
    <button @click="changeArrayRef">改变ref定义数组第一项</button>
    <button @click="changeArrayReactive">改变reactive定义数组第一项</button>
  </div>
</template>

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

  const arrayRef = ref([1, 2, 3, 4])
  const arrayReactive = reactive([1, 2, 3, 4])

  // ref not deep, 不能深度侦听
  const arrayRefWatch = watch(arrayRef, (newValue, oldValue) => {
    console.log('newArrayRefWatch', newValue, 'oldArrayRefWatch', oldValue)
  })

  // ref deep, 深度侦听,新旧值一样
  const arrayRefDeepWatch = watch(arrayRef, (newValue, oldValue) => {
    console.log('newArrayRefDeepWatch', newValue, 'oldArrayRefDeepWatch', oldValue)
  }, {deep: true})

  // ref deep, getter形式 , 新旧值不一样
  const arrayRefDeepGetterWatch = watch(() => [...arrayRef.value], (newValue, oldValue) => {
    console.log('newArrayRefDeepGetterWatch', newValue, 'oldArrayRefDeepGetterWatch', oldValue)
  })

  // reactive,默认深度监听,可以不设置deep:true, 新旧值一样
  const arrayReactiveWatch = watch(arrayReactive, (newValue, oldValue) => {
    console.log('newArrayReactiveWatch', newValue, 'oldArrayReactiveWatch', oldValue)
  })

  // reactive,getter形式 , 新旧值不一样
  const arrayReactiveGetterWatch = watch(() => [...arrayReactive], (newValue, oldValue) => {
    console.log('newArrayReactiveFuncWatch', newValue, 'oldArrayReactiveFuncWatch', oldValue)
  })

  const changeArrayRef = () => {
    arrayRef.value[0] = 3
  }
  const changeArrayReactive = () => {
    arrayReactive[0] = 6
  }
</script>

现象

  • 当将引用对象采用ref形式定义时,如果不加上deep:true watch侦听不到值的变化的;而加 deep:truewatch可以侦听到数据的变化,但是当前值和先前值一样,即不能获取旧值。
  • 当将引用对象采用 reactive形式定义时,不作任何处理,watch可以侦听到数据的变化,但是当前值和旧值一样。
  • 两种定义下,把watch的数据源写成getter函数的形式并进行深拷贝返回,可以在watch回调中同时获得当前值和旧值。
    const objReactive = reactive({user: {name: 'aa', age: '18'}, brand: 'Channel'});
    
    /** 对象深度监听的最佳实践- reactive且源采用函数式返回,返回深拷贝后的数据 */
    watch(() => _.cloneDeep(objReactive), (newVal, oldVal) => {
      console.log('newVal', newVal);
      console.log('oldVal', oldVal);
    })

结论: 当我们使用watch侦听引用对象时

  • 若使用ref定义的引用对象:
    • 只要获取当前值,watch第一个参数直接写成数据源,另外需要加上deep:true选项
    • 若要获取当前值和旧值,需要把数据源写成getter函数的形式,并且需对数据源进行深拷贝
  • 若使用 reactive定义的引用对象:
    • 只要获取当前值,watch第一个参数直接写成数据源,可以不加deep:true选项
    • 若要获取当前值和旧值,需要把数据源写成getter函数的形式,并且需对数据源进行深拷贝

5. watchEffect

watchEffect(callback, options): 只有两个参数,第一个是回调函数,第二个是配置项,配置项参数与watch一样;

watchEffect会立即执行,不像watch是惰性的;当然也可以通过watch配置项加{immediate: true}实现;

const number = reactive({ count: 0 });
const countAdd = () => {
  number.count++;
};
watchEffect(()=>{
  console.log("新的值:", number.count);
})

TIP

watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。

以便dom更新之后运行watchEffect, 有个简单写法

watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})

6. watch, watchEffect的区别

  1. watchwatchEffect 都能监听响应式数据的变化,不同的是它们监听数据变化的方式不同。
  2. watch 会明确监听某一个响应数据,而 watchEffect则是隐式的监听回调函数中响应数据。
  3. watch 在响应数据初始化时是不会执行回调函数的,watchEffect 在响应数据初始化时就会立即执行回调函数。

7. FAQ

通常来说,我们的一个组件被销毁或者卸载后,监听器也会跟着被停止,并不需要我们手动去关闭监听器。但是总是有一些特殊情况,即使组件卸载了,但是监听器依然存在,这个时候其实式需要我们手动关闭它的,否则容易造成内存泄漏。

比如下面这中写法,我们就需要手动停止监听器:

<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

上段代码中我们采用异步的方式创建了一个监听器,这个时候监听器没有与当前组件绑定,所以即使组件销毁了,监听器依然存在。

关闭方法很简单,代码如下:

const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()

感谢您抽出宝贵的时间观看本文;本文是 Vue3 核心 API 系列的第 1 篇,后续会持续更新 computed、ref/reactive、生命周期等实战内容,同时正在整理「Vue3 完整项目实战小册」(包含从 0 到 1 开发小程序 / 管理系统的全流程),欢迎关注~

❌