title: 关于组件二次封装的又一步思考 date: 2023-09-13 16:04:06 tags:

  • Vue
  • 技巧 categories:
  • Vue

以二次封装一个 el-dialog 为例

# 利用 computed

父组件使用

<template>
  <div>
    <Dialog v-model="visible">
      <template #header>132132132</template>
      <span>Open the dialog from the center from the screen</span>
    </Dialog>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Dialog from './components/Dialog.vue'
const visible = ref(false)

const onClick = () => {
  console.log(form.value)
}

const open = () => {
  visible.value = true
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

利用 computed get set

<template>
  <el-dialog v-model="visible" v-on="$attrs">
    <template v-for="(_value, name) in $slots" #[name]="slotData">
      <slot :name="name" v-bind="slotData || {}" />
    </template>
  </el-dialog>
</template>

<script setup>

const props = defineProps({
  /**
   * 是否显示
   */
  modelValue: {
    type: Boolean,
    default: false,
  },
})

const emit = defineEmits(['update:modelValue'])

const visible = computed({
  get() {
    return props.modelValue
  },
  set(newValue) {
    emit('update:modelValue', newValue)
  },
})
</script>

<style lang="scss" scoped></style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 利用 Proxy 代理

如果是 visible 是一个对象的话单单只用 computed 的 get set 就显得乏力了,这个时候可以考虑利用 Proxy 代理整个对象,这里以封装一个form 为例

父组件使用

<template>
  <div>
    <Form v-model="form"></Form>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Form from './components/form.vue'
const form = ref({
  name: '张麻子',
  age: 18,
  sex: 'man',
})

watch(
  form,
  (newValue) => {
    console.log(newValue)
  },
  {
    deep: true,
  }
)

</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

使用 proxy 代理

// Form.vue
<template>
  <div>
    <el-input v-model="form.name"></el-input>
    <el-input v-model="form.age"></el-input>
    <el-input v-model="form.sex"></el-input>
  </div>
</template>

<script setup>
const props = defineProps({
  modelValue: {
    type: Object,
    default: () => ({}),
  },
})

const emit = defineEmits(['update:modelValue'])

const form = computed({
  get() {
    return new Proxy(props.modelValue, {
      get(target, key) {
        return Reflect.get(target, key)
      },
      set(target, key, value, receiver) {
        emit('update:modelValue', {
          ...target,
          [key]: value,
        })
        return true
      },
    })
  },
  set(newValue) {
    emit('update:modelValue', newValue)
  },
})

</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 封装为一个 useVModel 的 hook

// useVModel.js
import { computed } from "vue";

export default function useVModle(props, propName, emit) {
  return computed({
    get() {
      return new Proxy(props[propName], {
        get(target, key) {
          return Reflect.get(target, key)
          },
        set(target, key, newValue) {
          emit('update:' + propName, {
            ...target,
            [key]: newValue
            })
            return true
            }
          })
      },
      set(value) {
        emit('update:' + propName, value)
      }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

使用:

<!-- 子组件使用 -->
<template>
  <div>
    <el-input v-model="form.name"></el-input>
    <el-input v-model="form.age"></el-input>
    <el-input v-model="form.sex"></el-input>
  </div>
</template>
<script setup>
import useVModel from "../hooks/useVModel";

// 使用 vueuse 封装的 useVModel
// import { useVModel } from '@vueuse/core'

const props = defineProps({
  modelValue: {
    type: Object,
    default: () => {},
  },
});

const emit = defineEmits(["update:modelValue"]);

const form = useVModel(props, "modelValue", emit);

</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

useVModel 可以换成 vueuse 里对应 hook,其实现更完备

# 参考

妙用computed拦截v-model,面试管都夸我细 (opens new window)

Vue3 组件封装进阶,简简单单实现一个弹窗表单组件 (opens new window)

Vue3 组件封装的一些技巧和心得 (opens new window)

Last Updated: 9/13/2023, 4:43:48 PM