Ember.js Computed Property vs Observer vs didReceiveAttrs() в компоненте

Я создал верхнюю панель навигации в своем приложении ember.js и преобразовал ее в компонент, чтобы у меня было простое приветствие «Добро пожаловать {{currentUser.fullName}}» после входа пользователя. Есть и другие причины, по которым панель навигации будет компонентом, но пока это то, что я пытаюсь заставить работать правильно. Мы также используем аддон ember-simple-auth, и я использую значение session.isAuthenticated, чтобы это произошло.

Конечно, у меня есть маршрут «входа», когда пользователь входит в систему, он переходит в «индекс». Почему-то не кажется, что значение session.isAuthenticated сразу перехватывается при переходе на 'индекс'. Я не знаю, почему это так, но это другой вопрос.

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

export default Ember.Component.extend({
    layout,
    session: Ember.inject.service(),
    currentUser: Ember.inject.service(),
    isAuthenticated: Ember.observer('session.isAuthenticated', function() {
        this.didReceiveAttrs();
    }),
    currentUserFullName: null,
    user: null,
    tagName: 'navbar-main',
    className: ['navbar', 'main'],
    didReceiveAttrs() {
        this._super(...arguments);

        if (this.get('session.isAuthenticated')) {
            this._setCurrentUser();
        }
    },
    _setCurrentUser() {
        this.get('currentUser').load().then(user => this.set('user', user));
    },
    onGetCurrentUser: Ember.observer('user', function() {
        this.getUser();
    }),
    getUser: function() {
        var user = this.get('user');

        if(user) {
            this.set('user', user);
            this.set('currentUserFullName', user.get('fullName'));
        } else {
            user = this.get('store').findRecord('user', 'me');
            this.set('user', user);
            this.set('currentUserFullName', user.get('fullName'));
        }
    }
});

Все, что я читаю, предлагает просто использовать didReceiveAttrs() вместо наблюдателя. Кроме того, в нем говорится, что вычисляемые свойства обычно являются тем, что вы хотите использовать вместо наблюдателей. Однако я не могу заставить что-либо работать без следующего:

isAuthenticated: Ember.observer('session.isAuthenticated', function() {
    this.didReceiveAttrs();
}),

Если я попытаюсь преобразовать его в вычисляемое свойство, оно, похоже, ничего не сделает с изменением свойства, если только я не обновлю страницу после того, как она уже перешла на маршрут «индекс». Если я попытаюсь использовать только метод didReceiveAttrs() с логикой this.get('session.isAuthenticated'), обновление страницы все равно потребуется.

Так что же я не понимаю в том, как использовать Ember.computedи didReceiveAttrs()? Может ли кто-нибудь привести мне пример? Или покажите мне более эффективный способ сделать то, что я пытаюсь сделать?

ОБНОВЛЕННЫЙ КОД:

// app/pods/components/navbar-main/component.js
import Ember from 'ember';
import layout from './template'; 

export default Ember.Component.extend({
    layout,
    session: Ember.inject.service(),
    currentUser: Ember.inject.service(),
    user: null,
    isAuthenticated: Ember.observer('session.isAuthenticated', function() {
        this.get('currentUser').load().then(user => this.set('user', user));
    }),
    currentUserFullName: Ember.computed.alias('user.fullName'),
    tagName: 'navbar-main',
    className: ['navbar', 'main'],
    didReceiveAttrs() {
        this._super(...arguments);

        if(this.get('session.isAuthenticated')) {
            this.isAuthenticated();
        }
    }
});

<!-- app/pods/components/navbar-main/template.hbs -->
{{#if session.isAuthenticated}}
    <div class="ui black user login label">
        <div class="ui grid">
            <div class="twelve wide column">
                <div class="detailed">{{currentUserFullName}}<br>Welcome</div>
            </div>
            <div class="four wide column" style="padding-top: 12px; margin-top: 0;">
                <img class="ui top aligned small avatar image" src="http://semantic-ui.com/images/avatar/small/christian.jpg">
                <!--<img class="ui top aligned tiny avatar image" src="{{currentUser.avatar}}">-->
            </div>
        </div>
    </div>
{{/if}}

ОБНОВЛЕНИЕ 2 (окончательный код с помощью @AdamCooper и @kumkanillam):

export default Ember.Component.extend({
    layout,
    session: Ember.inject.service(),
    currentUser: Ember.inject.service(),
    user: null,
    isAuthenticated: Ember.on('init', Ember.observer('session.isAuthenticated', function() {
        this.get('currentUser').load().then(user => this.set('user', user));
    })),
    currentUserFullName: Ember.computed.alias('user.fullName'),
    tagName: 'navbar-main',
    className: ['navbar', 'main']
});

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


person Charles Williams    schedule 23.08.2016    source источник


Ответы (3)


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

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

export default Ember.Component.extend({
    layout,
    session: Ember.inject.service(),
    currentUser: Ember.inject.service(),

    user: null,

    authenticatedObserver: Ember.observer('session.isAuthenticated', function() {
        // Load the user, this will cause the currentUserFullName to be 
        // updated. We're using an observer here because load() returns
        // a promise 
        this.get('currentUser').load().then(user => this.set('user', user));
    }),

    // This computed property will be re-calculated whenever user is modified
    currentUserFullName: Ember.computed.alias('user.fullName')  
});
person Adam Cooper    schedule 23.08.2016
comment
~ так что это в основном то же самое, что я уже слышал и читал о наблюдателях, но в вашем ответе все еще используется наблюдатель? ржу не могу. Могу ли я предположить, что в этой конкретной ситуации необходимо использовать наблюдателя? - person Charles Williams; 23.08.2016
comment
Я использую наблюдателя только там, где это необходимо. Вот где вы вызываете функцию загрузки - person Adam Cooper; 24.08.2016
comment
Так что это работает, если я все еще использую didReceiveAttrs() { this._super(...arguments); if(this.get('session.isAuthenticated')) { this.isAuthenticated(); } },, что определенно сокращает код, но я все еще не уверен, что на самом деле происходит, потому что если я вместо этого сделаю this.get('currentUserFullName')' or a this.get('user'), I get null values. I also have to change my template to {{#if session.isAuthenticated` из {#if currentUser, чтобы скрыть элемент, к которому это привязано. Это звучит правильно? - person Charles Williams; 25.08.2016
comment
Можете ли вы обновить свой вопрос с помощью кода, который у вас есть сейчас? - person Adam Cooper; 26.08.2016
comment
Хорошо, я добавил предложение от @kumkanillam, так что это было скорее совместным усилием между вами двумя, но этот ответ был самым близким. Мой обновленный код выше, который работает. - person Charles Williams; 26.08.2016

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

При начальном рендеринге — init,didReceiveAttrs,willRender,didInsertElement,didRender
При повторном рендеринге — didUpdateAttrs,didReceiveAttrs,willUpdate,willRender,didRender
При уничтожении компонента – willDestroyElement, willClearRender, didDestroyElement
(Ссылка – https://guides.emberjs.com/v2.7.0/components/the-component-lifecycle/)

Мы не должны вызывать методы ловушек жизненного цикла этих компонентов.

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

export default Ember.Component.extend({
    session: Ember.inject.service(),
    currentUser: Ember.inject.service(),
    currentUserFullName: Ember.computed.alias('currentUser.currentUserFullName'),
    user: Ember.computed.alias('currentUser.user'),

    tagName: 'navbar-main',
    className: ['navbar', 'main'],    

});

Инициализируйте currentUser и его данные, наблюдая за свойством session.isAuthenticated.

services\current-user.js

import Ember from 'ember';

export default Ember.Service.extend({
  session: Ember.inject.service(),
  currentUserFullName: null,
  user: null,

  loadCurrentUserDetails:Ember.observer('session.isAuthenticated', function(){
    //load async data and set user and currentUserFullName.
  })
});
person Ember Freak    schedule 23.08.2016
comment
~ Я думал об инициализации, но еще не касался инициализаторов. Я посмотрю на это и прокомментирую. - person Charles Williams; 23.08.2016
comment
На самом деле я использую пакет Ember Addon node с моим проектом, в котором уже есть служба для currentUser, поэтому у меня это не сработало. Я не хочу менять службу, к которой это привязано, но эта служба использует вычисляемое свойство: currentUserId: Ember.computed('session.data.authenticated', function() {var session = this.get('session'); if (session.get('isAuthenticated')) {return session.get('data.authenticated.id'); } else {return null;}}), - person Charles Williams; 25.08.2016
comment
И затем: load() {return new Ember.RSVP.Promise((resolve, reject) => {var currentUserId = this.get('currentUserId');if (currentUserId) {var currentUser = this.get('store').peekRecord('user', currentUserId);if (currentUser) {resolve(currentUser);} else {this.get('store').findRecord('user', currentUserId).then((user) => resolve(user), reject);}} else {reject();}});}, поэтому, используя этот уже существующий сервис, я воспользовался предложением @AdamCooper выше и все же добавил функцию didReceiveAttrs(), чтобы заставить его работать (объяснено выше). Это лучший способ? - person Charles Williams; 25.08.2016
comment
Нет, не стоит звонить didReceiveAttrs() ИМХО. Наблюдатели никогда не срабатывают, пока не завершится инициализация объекта. Справочник . Вы можете попробовать это .. isAuthenticated: Ember.on('init',Ember.observer('session.isAuthenticated', function() { this.get('currentUser').load().then(user => this.set('user', user)); })) - person Ember Freak; 25.08.2016
comment
~ Большое спасибо! Это помогло. Я обновил свой пост. - person Charles Williams; 26.08.2016

Вот что у меня получилось, что сработало для моей ситуации:

export default Ember.Component.extend({
    layout,
    session: Ember.inject.service(),
    currentUser: Ember.inject.service(),
    user: null,
    isAuthenticated: Ember.on('init', Ember.observer('session.isAuthenticated', function() {
        this.get('currentUser').load().then(user => this.set('user', user));
    })),
    currentUserFullName: Ember.computed.alias('user.fullName'),
    tagName: 'navbar-main',
    className: ['navbar', 'main']
});

Здесь используется комбинация Ember Freak и Адам Купер отвечает на этот вопрос.

person Charles Williams    schedule 27.09.2018