hooks

useClickOutside

点击下拉框外部时,触发开关事件。
使用html的node.contains进行判断

  1. import { Ref, ref, onMounted, onUnmounted } from 'vue';
  2. // element为绑定的下拉框的元素
  3. function useClickOutside(element: Ref<HTMLElement | null>): Ref<boolean> {
  4. // 定义一个响应数据,处理是否点击的是下拉框内。
  5. const isClickOutside = ref<boolean>(false);
  6. const handler = (e: MouseEvent) => {
  7. if (element.value) {
  8. if (element.value.contains(e.target as HTMLElement)) {
  9. isClickOutside.value = false;
  10. } else {
  11. isClickOutside.value = true;
  12. }
  13. }
  14. };
  15. onMounted(() => {
  16. document.addEventListener('mousedown', handler);
  17. });
  18. onUnmounted(() => {
  19. document.removeEventListener('mousedown', handler);
  20. });
  21. return isClickOutside;
  22. }
  23. export default useClickOutside;

Form表单开发

v-model语法

vue2中的model用法

存在不足:

  • 无法绑定多个model
  • 处理checkbox等非input事件时,需要设定model来覆盖默认的value/input。有些鸡肋。
    1. <Child v-model = "title />
    2. // 等价于 >>
    3. <Child :value = "title" @input = "title = $event" />
    为了解决第二个问题,处理非input事件,vue2.2加入了model属性,可以自定义事件和绑定的值
    1. // 父组件中引用自组件
    2. <Child v-model="gender" />
    1. // 在子组件中通过定义model
    2. export default {
    3. model: {
    4. prop: 'gender', // v-model绑定的属性名称
    5. event: 'change' // v-model绑定的事件
    6. },
    7. props: {
    8. value: String, // 此时value跟v-model无关
    9. gender: { // gender是v-model绑定的属性
    10. type: String,
    11. default: 'M'
    12. }
    13. }
    14. }

    但是,仍然无法解决子组件中有多个model值的绑定。

vue3中的model用法

vue3为了解决在vue2中存在的问题,把默认value/input类型绑定给去除掉。而是使用了modelValue/update:modelValue作为默认绑定字段

  1. <Child v-model = "email">
  2. // 相当于
  3. <Child :modelValue="email" @update:modelValue = "handleUpdateValue">
  1. // 在子组件中定义
  2. export default defineComponent({
  3. props:{
  4. modelValue:String, // v-model绑定的属性值
  5. },
  6. setup(){
  7. const updateValue = (e: KeyboardEvent) => {
  8. context.emit("update:modelValue",targetValue); //传递targetValue给父组件的handleUpdateValue
  9. }
  10. }
  11. }

设置自定义参数

在vue3中可以给v-model:defineName,定义一个语意化的字段。并且可以定义多个model。

  1. <Child v-model:useName="useName" v-model:email = "email" />
  2. //等价于
  3. <ChildComponent
  4. :useName="useName" @update:useName = "handleUseName"
  5. :email="email" @update:email = "handleEmail"/>

在子组件定义属性props和事件event。

  1. export default defineComponent({
  2. props:{
  3. useName:String,
  4. email:String,
  5. },
  6. setup(){
  7. const updateUseName = (e: KeyboardEvent) => {
  8. context.emit("update:useName", e.target.value);
  9. }
  10. const updateEmail = (e: KeyboardEvent) => {
  11. context.emit("update:email", e.target.value);
  12. }
  13. }
  14. }

组件属性的绑定

inheritAttrs

组件进行属性传值时,如果没有props定义,会把属性值给定义到子组件的根节点上。为了组织把属性绑定到跟节点,可以给子组件添加属性inheritAttrs:false

  1. export default defineComponent({
  2. props:{ // ... },
  3. inheritAttrs: false,
  4. })

v-bind=”$attrs”

去除了绑定到跟节点后,可以给需要绑定属性的标签添加v-bind=”$attrs”

  1. <template>
  2. <div class="form">
  3. <input
  4. class="input"
  5. :class="{ 'is-danger': inputRef.error }"
  6. v-bind="$attrs"
  7. />
  8. </div>
  9. </template>

插槽slot

插槽模版值

子组件中设置插槽命名和位置。

  1. <div>
  2. // 具名插槽
  3. <slot name="header"></slot>
  4. // 默认插槽
  5. <slot></slot>
  6. // 具名插槽
  7. <slot name="footer"></slot>
  8. </div>

在父组件中传递slot的值,v-slot:header或者简写#header

  1. <template v-slot:header>
  2. <button class="button is-danger">submit</button>
  3. </template>

slot插槽元素上的事件,父子组件通讯

在父组件上通过定义ref获取子组件的属性或方法

  1. <ValidateInput ref="demoRef">
  2. </ValidateInput>
  3. <script lang="ts">
  4. setup(){
  5. const demoRef = ref<any>(null);
  6. onMounted(()=>{
  7. console.log('demoRef', demoRef.value.validateInput());
  8. })
  9. }
  10. </script>

使用插件mitt,传递数据或事件

如果子组件被放置在父组件的slot元素上,就无法使用ref标签。如何获取子组件的事件和属性?
通过公用的事件监听,引入外部第三方模块mitt。

  1. // 在App组件中引入ValidateForm和ValidateInput,共用组件
  2. <ValidateForm @form-submit="handleFormSubmit">
  3. <ValidateInput :rules="emailRules"
  4. v-model="emailVal" type="email"
  5. placeholder="请输入邮箱地址" ref="demoRef">
  6. <label class="label">邮箱地址:</label>
  7. </ValidateInput>
  8. <ValidateInput :rules="passwordRules"
  9. v-model="passwordVal"
  10. type="password"
  11. placeholder="请输入密码">
  12. <label class="label">密 码:</label>
  13. </ValidateInput>
  14. <template #submit="slotProps">
  15. <button class="button is-danger">submit{{ slotProps }}</button>
  16. </template>
  17. </ValidateForm>
  18. <script lang="ts">
  19. export default defineComponent({
  20. setup(){
  21. const handleFormSubmit = (val: boolean) => {
  22. console.log('handleFormSubmit', val);
  23. // console.log('demoRef', demoRef.value.validateInput());
  24. };
  25. }
  26. })
  27. </script>
  1. // form父组件中引入并定义emitter
  2. import mitt from 'mitt';
  3. type ValidateFunc = () => boolean;
  4. type Events = {
  5. 'form-item-created': ValidateFunc;
  6. };
  7. export const emitter = mitt<Events>();
  8. export default defineComponent({
  9. emits: ['form-submit'],
  10. setup(props, context) {
  11. let inputFuncArr: ValidateFunc[] = [];
  12. const callback = (func: ValidateFunc): void => {
  13. inputFuncArr.push(func);
  14. };
  15. emitter.on('form-item-created', callback);
  16. onUnmounted(() => {
  17. emitter.off('form-item-created', callback);
  18. inputFuncArr = [];
  19. });
  20. return { submitForm, item };
  21. }
  22. });
  1. // 在input子组件中使用。首先引入form中导出的emitter对象
  2. import { emitter } from './ValidateForm.vue';
  3. export default defineComponent({
  4. setup(props, context) {
  5. onMounted(() => {
  6. // 放在生命周期,测试
  7. emitter.emit('form-item-created', validateInput);
  8. });
  9. }
  10. })