En En

Запуск изменений с помощью действий


Вы можете воспринимать компонент как «черный ящик» функциональности интерфейса пользователя. К настоящему времени вы узнали, как родительские компоненты могут передавать атрибуты потомкам и как последние могут использовать эти атрибуты из JavaScript и из своего шаблона.

Но что насчет обратного направления? Как передать поток данных от компонента родителю? В Ember компоненты используют действия, чтобы взаимодействовать с событиями и изменениями.

Взглянем на простой пример того, как компонент может использовать действие, чтобы взаимодействовать с родителем.

Представьте, что мы создаем приложение, где у пользователей есть аккаунты. Нам нужно сделать для пользователей интерфейс, который позволит им удалить аккаунт. Так как мы не хотим, чтобы пользователи случайно это сделали, мы создадим кнопку, которая потребуется для подтверждения запуска действия.

После создания компонента «кнопка для подтверждения» мы будем повторно его использовать по всему приложению.

Создание компонента

Назовем компонент button-with-confirmation. Мы можем создать его так:

ember generate component button-with-confirmation

Мы будем использовать компонент в шаблоне примерно таким образом:

app/templates/components/user-profile.hbs

{{button-with-confirmation text="Click OK to delete your account."}}

И также нам нужно использовать компонент в других местах, как здесь:

app/templates/components/send-message.hbs

{{button-with-confirmation text="Click OK to send your message."}}

Планирование действия

При реализации в компоненте действия, которое будет обрабатываться вне компонента, процесс нужно разделить на два этапа:

  1. В родительском компоненте: решите, какую вы хотите получить реакцию на действие. Здесь мы хотим удалить пользовательский аккаунт в одном месте и отправить сообщение в другом месте.
  2. В компоненте: определите, когда происходит событие и когда сообщать об этом внешнему миру. Здесь нам нужно запустить внешнее действие (удаление аккаунта или отправку сообщения) после того, как пользователь нажмет кнопку и подтвердит действие.

Проделаем все это шаг за шагом.

Реализация действия

Сначала определим в родительском компоненте, что должно произойти, когда пользователь нажмет кнопку и подтвердит действие. В первом случае мы найдем аккаунт пользователя и удалим его.

В Ember у каждого компонента может быть свойство actions, куда вы вкладываете функции, которые могут вызвать дочерние компоненты или пользователь при взаимодействии с самим компонентом.

Взглянем на файл JavaScript родительского компонента. В этом примере представьте, что у нас есть родительский компонент user-profile, который показывает профиль пользователя.

Мы реализуем действие в родительском компоненте под названием userDidDeleteAccount(), которое при вызове использует предполагаемую службу login и вызывает метод службы deleteUser().

app/components/user-profile.js

import Ember from 'ember';

export default Ember.Component.extend({
  login: Ember.inject.service(),

  actions: {
    userDidDeleteAccount() {
      this.get('login').deleteUser();
    }
  }
});

Мы реализовали действие, но не указали Ember, в какой момент мы хотим его запустить. Это будет следующим шагом.

Планирование дочернего компонента

Далее, в дочернем компоненте мы реализуем следующую логику, чтобы подтвердить, что пользователь хочет совершить действие по нажатию кнопки:

app/components/button-with-confirmation.js

import Ember from 'ember';

export default Ember.Component.extend({

  actions: {
    launchConfirmDialog() {
      this.set('confirmShown', true);
    },

    submitConfirm() {
      // trigger action on parent component
      this.set('confirmShown', false);
    },

    cancelConfirm() {
      this.set('confirmShown', false);
    }
  }
});

В шаблоне компонента будет кнопка и элемент div, который показывает диалог подтверждения на основе значения confirmShown.

app/templates/components/button-with-confirmation.hbs

<button {{action "launchConfirmDialog"}}>{{text}}</button>
{{#if confirmShown}}
  <div class="confirm-dialog">
    <button class="confirm-submit" {{action "submitConfirm"}}>OK</button>
    <button class="confirm-cancel" {{action "cancelConfirm"}}>Cancel</button>
  </div>
{{/if}}

Передача действия компоненту

Теперь нам нужно сделать так, чтобы действие userDidDeleteAccount(), определенное в родительском компоненте user-profile, можно было запустить из компонента button-with-confirmation. Чтобы сделать это, мы передадим действие дочернему компоненту тем же путем, которым мы передавали другие действия. Такое возможно, так как действия — это простые функции, как и любые другие методы в компоненте. Поэтому их можно передавать от одного компонента другому таким образом:

app/templates/components/user-profile.hbs

{{button-with-confirmation text="Click here to delete your account." onConfirm=(action "userDidDeleteAccount")}}

Этот фрагмент сообщает: «взять действие userDidDeleteAccount у родителя и сделать его доступным в дочернем компоненте в качестве свойства onConfirm». Обратите внимание на использование хелпера action. Он возвращает функцию userDidDeleteAccount, которую мы передаем компоненту.

Мы можем сделать похожее в компоненте send-message:

app/templates/components/send-message.hbs

{{button-with-confirmation text="Click to send your message." onConfirm=(action "sendMessage")}}

Теперь можно использовать onConfirm в дочернем компоненте, чтобы вызвать действие у родителя:

app/components/button-with-confirmation.js

import Ember from 'ember';

export default Ember.Component.extend({

  actions: {
    launchConfirmDialog() {
      this.set('confirmShown', true);
    },

    submitConfirm() {
      //call the onConfirm property to invoke the passed in action
      this.get('onConfirm')();
    },

    cancelConfirm() {
      this.set('confirmShown', false);
    }
  }
});

this.get('onConfirm') вернет переданную от родителя функцию в качестве значения onConfirm, и следующие () вызовут функцию.

Как и обычные атрибуты, действия могут быть свойствами компонента. Единственная разница между ними заключается в том, что свойство устанавливается функции, которая знает, как запускать поведение.

Так проще запомнить, как добавлять действие компоненту. Это как передача атрибута, но для передачи функции вы используете хелпер action.

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

Обработка завершения действия

Очень часто действия выполняют асинхронные задачи, например осуществляют ajax-запрос к серверу. Так как действия — это функции, которые может передавать родительский компонент, они способны возвращать при вызове значения. Распространенный сценарий: действие возвращает обещание, чтобы компонент мог обработать завершение действия.

В компоненте button-with-confirmation нам нужно удерживать открытым модальное окно подтверждения, пока мы не убедимся, что действие завершилось успешно. Это можно сделать, если вернуть обещание от onConfirm. После разрешения обещания мы назначим свойство, чтобы указать видимость модального окна подтверждения.

app/components/button-with-confirmation.js

import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    launchConfirmDialog() {
      this.set('confirmShown', true);
    },

    submitConfirm() {
      //call onConfirm with the value of the input field as an argument
      const promise = this.get('onConfirm')();
      promise.then(() => {
        this.set('confirmShown', false);
      });
    },

    cancelConfirm() {
      this.set('confirmShown', false);
    }
  }
});

Передача аргументов

Иногда у родительского компонента, который вызывает действие, есть некий контекст, необходимый действию; и его нет в дочернем компоненте. В таких случаях действия, переданные компоненту через хелпер action, можно вызывать с аргументами. Например, мы обновим действие send-message, чтобы принимать тип сообщения в дополнение к самому сообщению. Так как компонент button-with-confirmation не знает тип сообщений, которые он собирает, нам нужно предоставить тип сообщения из send-message при определении действия.

app/templates/components/send-message.hbs

{{button-with-confirmation text="Click to send your message." onConfirm=(action "sendMessage" "info")}}

В этом случае код в button-with-confirmation не меняется. Он по-прежнему будет вызывать onConfirm без аргументов.

Хелпер action добавит аргументы, предоставленные в шаблоне, к вызову. Аргументы действия каррируются, то есть вы можете предоставить отдельные аргументы хелперу action, а остальные аргументы — при вызове функции в файле javascript компонента. Например, компонент button-with-confirmation теперь будет выдавать (yield) содержимое диалога подтверждения, чтобы собрать дополнительную информацию, которая отправляется вместе с действием onConfirm:

app/templates/components/button-with-confirmation.hbs

<button {{action "launchConfirmDialog"}}>{{text}}</button>
{{#if confirmShown}}
  <div class="confirm-dialog">
    {{yield confirmValue}}
    <button class="confirm-submit" {{action "submitConfirm"}}>OK</button>
    <button class="confirm-cancel" {{action "cancelConfirm"}}>Cancel</button>
  </div>
{{/if}}

Компонент send-message предоставляет входные данные в качестве блочного контента для компонента button-with-confirmation, устанавливая confirmValue.

app/templates/components/send-message.hbs

{{#button-with-confirmation
    text="Click to send your message."
    onConfirm=(action "sendMessage" "info")
    as |confirmValue|}}
  {{input value=confirmValue}}
{{/button-with-confirmation}}

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

app/components/button-with-confirmation.js

import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    launchConfirmDialog() {
      this.set("confirmShown", true);
    },

    submitConfirm() {
      //call onConfirm with the value of the input field as an argument
      const promise = this.get('onConfirm')(this.get('confirmValue'));
      promise.then(() => {
        this.set('confirmShown', false);
      });
    },

    cancelConfirm() {
      this.set('confirmShown', false);
    }
  }
});

Это действие вызовет нашу привязанную функцию sendMessage с типом сообщения, который мы предоставили ранее, шаблоном и значением сообщения, предоставленными в JavaScript компонента.

app/components/send-message.js

import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    sendMessage(messageType, messageText) {
      //send message here and return a promise
    }
  }
});

Вызов действий напрямую во взаимодействующих объектах компонента

Действия можно вызвать в отдельных объектах, а не только в компоненте, напрямую из шаблона. Например, в компонент send-message мы можем включить службу, которая обрабатывает логику sendMessage.

app/components/send-message.js

import Ember from 'ember';

export default Ember.Component.extend({
  messaging: Ember.inject.service(),

  // component implementation
});

Мы можем указать действию, чтобы оно вызывало действие sendMessage напрямую через службу обмена сообщениями с атрибутом target.

app/templates/components/send-message.hbs

{{#button-with-confirmation
    text="Click to send your message."
    onConfirm=(action "sendMessage" "info" target=messaging)
    as |confirmValue| }}
  {{input value=confirmValue}}
{{/button-with-confirmation}}

Благодаря предоставлению атрибута target, хелпер action будет вызывать действие sendMessage напрямую через службу обмена сообщениями, избавляя нас от необходимости писать код в компоненте, чтобы передать действие службе.

app/services/messaging.js

import Ember from 'ember';

export default Ember.Service.extend({
  actions: {
    sendMessage(messageType, text) {
      //handle message send and return a promise
    }
  }
});

Деструктуризация объектов, переданных в качестве аргументов действия

Компонент часто не будет знать, какая информация нужна родителю для обработки действия, и будет просто передавать всю имеющуюся информацию. Например, наш компонент user-profile собирается уведомить родителя, system-preferences-editor, что аккаунт пользователя удален, и передает вместе с этой информацией весь объект профиля пользователя.

app/components/user-profile.js

import Ember from 'ember';

export default Ember.Component.extend({
  login: Ember.inject.service(),

  actions: {
    userDidDeleteAccount() {
      this.get('login').deleteUser();
      this.get('didDelete')(this.get('login.currentUserObj'));
    }
  }
});

Все, что компоненту system-preferences-editor действительно нужно знать для обработки удаления пользователя, это идентификатор аккаунта. В этом случае хелпер action предоставляет атрибут value, чтобы позволить родительскому компоненту подробно изучить переданный объект и получить то, что ему нужно.

app/templates/components/system-preferences-editor.hbs

{{user-profile didDelete=(action "userDeleted" value="account.id")}}

Теперь при обработке удаления system-preferences-editor получает только строку с id пользовательского аккаунта.

app/components/system-preferences-editor.js

import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    userDeleted(idStr) {
      //respond to deletion
    }
  }
});

Вызов действий в нескольких слоях компонента

Когда компоненты охватывают несколько шаблонных слоев, вам часто придется обрабатывать действие на нескольких уровнях дерева. С помощью хелпера action родительские компоненты могут передавать действия дочерним через одни только шаблоны, без добавления кода JavaScript в потомков.

Например, нам нужно передать удаление аккаунта из компонента user-profile его родителю system-preferences-editor для обработки.

Сначала мы перенесем действие deleteUser из user-profile.js в объект actions в system-preferences-editor.

app/components/system-preferences-editor.js

import Ember from 'ember';

export default Ember.Component.extend({
  login: Ember.inject.service(),
  actions: {
    deleteUser(idStr) {
      return this.get('login').deleteUserAccount(idStr);
    }
  }
});

Затем наш шаблон system-preferences-editor передаст локальное действие deleteUser в user-profile в качестве свойства deleteCurrentUser этого компонента.

app/templates/components/system-preferences-editor.hbs

{{user-profile deleteCurrentUser=(action 'deleteUser' login.currentUser.id)}}

Действие deleteUser стоит в кавычках, так как сейчас оно определено в system-preferences-editor. Кавычки указывают, что действие необходимо искать в объекте actions этого компонента, а не в тех, что были переданы от родителя.

В нашем шаблоне user-profile.hbs мы меняем действие на deleteCurrentUser.

app/templates/components/user-profile.hbs

{{button-with-confirmation onConfirm=(action deleteCurrentUser)
  text="Click OK to delete your account."}}

Обратите внимание, что deleteCurrentUser здесь стоит без кавычек. Кавычки используются, чтобы изначально передать действие вниз по дереву компонента, но на каждом последующем уровне вместо этого вы передаете фактическую ссылку на функцию (без кавычек) в хелпере action.

Теперь, когда вы подтвердили удаление, действие отправляется напрямую в system-preferences-editor для обработки в его локальном контексте.


Комментарии (0)

    Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.