Provide/Inject с компонентами

В Element Plus для организации передачи данных между компонентами на разных уровнях вложенности часто используется сочетание Vue 3 API provide и inject. Это особенно актуально при работе с формами, таблицами, модальными окнами и другими сложными компонентами библиотеки, где требуется передача состояния или методов без прямого пропа через все промежуточные компоненты.

Основные принципы

  • provide используется в родительском компоненте для объявления данных или функций, которые должны быть доступны вложенным компонентам.
  • inject позволяет дочернему компоненту получать эти данные без явного пропа.
  • Данные, передаваемые через provide, могут быть реактивными, что позволяет автоматически обновлять дочерние компоненты при изменении состояния.

Пример базового использования:

import { ref, provide, inject } from 'vue';

export default {
  setup() {
    const themeColor = ref('primary');
    provide('themeColor', themeColor);

    return { themeColor };
  }
}

// В дочернем компоненте
export default {
  setup() {
    const themeColor = inject('themeColor');
    return { themeColor };
  }
}

В этом примере дочерний компонент автоматически реагирует на изменения themeColor в родительском компоненте.

Интеграция с Element Plus

Многие компоненты Element Plus внутри себя используют provide/inject для управления состоянием. Рассмотрим несколько практических сценариев.

1. Связка Form и FormItem

ElForm предоставляет общий контекст для всех вложенных ElFormItem, включая валидацию, модели данных и правила.

import { ElForm, ElFormItem, ElInput, ElButton } from 'element-plus';

export default {
  components: { ElForm, ElFormItem, ElInput, ElButton },
  setup() {
    const formData = reactive({
      username: '',
      password: ''
    });

    const rules = reactive({
      username: [{ required: true, message: 'Введите имя пользователя', trigger: 'blur' }],
      password: [{ required: true, message: 'Введите пароль', trigger: 'blur' }]
    });

    return { formData, rules };
  },
  template: `
    <el-form :model="formData" :rules="rules" ref="formRef">
      <el-form-item label="Имя пользователя" prop="username">
        <el-input v-model="formData.username"></el-input>
      </el-form-item>
      <el-form-item label="Пароль" prop="password">
        <el-input type="password" v-model="formData.password"></el-input>
      </el-form-item>
      <el-button type="primary" @click="formRef.validate()">Отправить</el-button>
    </el-form>
  `
}

Здесь ElForm через provide передаёт объект формы и правила в каждый ElFormItem. ElFormItem автоматически получает эти данные с помощью inject и выполняет валидацию без необходимости напрямую передавать пропсы.

2. Настройка глобальной темы компонентов

Можно создать компонент-обёртку, который через provide задаёт тему или стили для всех дочерних Element Plus компонентов:

import { provide, reactive } from 'vue';

export default {
  setup() {
    const themeSettings = reactive({
      size: 'small',
      buttonType: 'success'
    });

    provide('themeSettings', themeSettings);
    return { themeSettings };
  }
}

// В дочернем компоненте
import { inject } from 'vue';
import { ElButton } from 'element-plus';

export default {
  components: { ElButton },
  setup() {
    const themeSettings = inject('themeSettings');
    return { themeSettings };
  },
  template: `
    <el-button :type="themeSettings.buttonType" :size="themeSettings.size">
      Кнопка
    </el-button>
  `
}

Таким образом, изменение темы в родительском компоненте автоматически отражается на всех кнопках и компонентах, использующих этот контекст.

3. Передача методов управления модальными окнами

ElDialog часто используется совместно с provide/inject для передачи функций открытия и закрытия диалога:

import { ref, provide } from 'vue';
import { ElDialog, ElButton } from 'element-plus';

export default {
  components: { ElDialog, ElButton },
  setup() {
    const isDialogVisible = ref(false);

    const openDialog = () => isDialogVisible.value = true;
    const closeDialog = () => isDialogVisible.value = false;

    provide('dialogControl', { openDialog, closeDialog });

    return { isDialogVisible };
  },
  template: `
    <el-dialog v-model="isDialogVisible" title="Пример">
      Контент модального окна
    </el-dialog>
  `
}

// В дочернем компоненте
import { inject } from 'vue';
import { ElButton } from 'element-plus';

export default {
  components: { ElButton },
  setup() {
    const dialogControl = inject('dialogControl');
    return { dialogControl };
  },
  template: `
    <el-button type="primary" @click="dialogControl.openDialog()">
      Открыть диалог
    </el-button>
  `
}

Этот подход позволяет централизованно управлять состоянием диалогов из родителя, а дочерние компоненты просто используют переданные методы.

Важные моменты и рекомендации

  • Реактивность: Для реактивных данных используется ref или reactive. Несовместимые типы данных могут привести к отсутствию обновлений в дочерних компонентах.
  • Обновление данных: Изменения данных через inject отражаются на всех дочерних компонентах, если данные реактивны.
  • Изоляция: provide/inject работает только внутри иерархии компонентов. Если компонент не является потомком, он не сможет получить данные.
  • Типизация: В TypeScript рекомендуется указывать типы при provide/inject, чтобы избежать ошибок и поддерживать автокомплит.
  • Совместимость с Element Plus: Большинство контейнерных компонентов (Form, Table, Tabs, Collapse) используют provide/inject для внутренней логики, поэтому при кастомизации этих компонентов важно учитывать существующие ключи.

Использование provide/inject вместе с компонентами Element Plus позволяет создавать гибкую архитектуру, где данные, методы и состояния могут быть централизованно переданы и реактивно обновляться на любом уровне вложенности. Это значительно упрощает разработку крупных интерфейсов с повторно используемыми и настраиваемыми компонентами.