Skip to content

Instantly share code, notes, and snippets.

@SerafimArts
Created September 29, 2015 12:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SerafimArts/650b43d830d021ae7c2e to your computer and use it in GitHub Desktop.
Save SerafimArts/650b43d830d021ae7c2e to your computer and use it in GitHub Desktop.
ES Example (ES6 Symbol + ES7 Decorators + ES7 Object properties)
import Observable from "Observable";
class AuthController {
@Observable
user = null;
@Observable
isAuth = false;
constructor() {
// Update isAuth if user variable exists
this.user.after(value => this.isAuth = value instanceof User);
// Remove user if isAuth == false
this.isAuth.after(value => {
if (!value) { this.user = null; }
});
}
loginAs(user) {
this.user = user;
}
logout() {
this.isAuth = false;
}
}
/**
* ObservablePrimitive instance
*/
class ObservablePrimitive {
/**
* @constructor
* @param value
*/
constructor(value = null) {
this.events = {
before: [],
after: [],
all: []
};
Object.defineProperty(this, '$value', {
enumerable: false,
value: value
});
}
/**
* @param value
* @return void
*/
set value(value) {
var exists = typeof value !== "undefined" && value !== null;
var oldValue = this.$value;
if (exists) {
this.events.before.forEach((c) => c(oldValue));
this.$value = value;
this.events.all.forEach((c) => c(oldValue, value));
this.events.after.forEach((c) => c(value));
}
}
/**
* Return value
* @return {*}
*/
get value() {
return this.$value;
}
/**
* @param callback
* @returns {ObservablePrimitive}
*/
after(callback) {
this.events.after.push(callback);
return this;
}
/**
* @param callback
* @returns {ObservablePrimitive}
*/
before(callback) {
this.events.before.push(callback);
return this;
}
/**
* @param callback
* @returns {ObservablePrimitive}
*/
subscribe(callback) {
this.events.all.push(callback);
return this;
}
/**
* @returns {*}
*/
toString() {
return this.value;
}
/**
* @returns {*}
*/
[Symbol.toPrimitive]() {
return this.value;
}
}
/**
* Observable decorator
*
* @param target
* @param key
* @param descriptor
* @returns {{enumerable, get, set}}
* @constructor
*/
export default function Observable(target, key, descriptor) {
return (function (descriptor) {
var value = descriptor.initializer();
var property = new ObservablePrimitive(value);
return {
enumerable: true,
get: () => property,
set: (value) => property.value = value
};
})(descriptor);
}
@trikadin
Copy link

А ты запускать-то пробовал?) И зачем вообще это всё нужно?)

@SerafimArts
Copy link
Author

@trikadin Запускается и работает как надо из под бабела с флагами es7.decorators + es7.objectProperties. =) А зачем нужно... Ну по-моему намного круче писать a = 23, вместо, например a = ko.observable(23) (в случае knockout обсерваблов). Можно сократить и подчистить код в разы.

@trikadin
Copy link

Скопировал в песочницу бабеля, что-то не взлетело.

@SerafimArts
Copy link
Author

@trikadin у тебя какой-то не такой бабель, вот собрал: http://pastebin.com/mpGait4R

@trikadin
Copy link

Лол, на оф. сайте бабеля какой-то не такой бабель. Ну ладно.

Зачем такое замыкание внутри декоратора Observable?

@trikadin
Copy link

Поигрался я ночью с этой штукой (точнее, написал свою). Пока могу сказать, что полноценно сделать именно так, как ты хочешь, не получится -- во-первых, то, что значение -- это всё-таки объект, постоянно откуда-нибудь вылезает. Во-вторых -- бабель очень по-дурацки генерит декораторы для свойств, и просто взять и сделать свойство геттером не получится -- ты, видимо, не проверял, но те геттер и сеттер, что ты возвращаешь из декоратора, падают не на инстанс объекта, а на прототип ObservablePrimitive. Обойти это, увы, можно только грязным хаком. Вот тут можно посмотреть, что я нахимичил, результатом не удовлетворён, но пока с этим ничего не поделать. Думаю, у бабеля ещё будет меняться поведение.

Про твоё решение могу сказать, что у тебя косяк с наследованием (если у свойства нету инициализирующего значения, то оно должно браться из цепочки прототипов).

@SerafimArts
Copy link
Author

@trikadin я с офф сайта и собирал, а не отдельно.

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

Я проверял, но общую работоспособность, а не конкретно этот пример с AuthController. Попробую запилить более приближённый к реальности и выложить куда-нибудь на jsbin.

@SerafimArts
Copy link
Author

P.S. Не выкладывай ссылки на babel, они обрезаются, т.к. в get нельзя иметь строку такой длины, как размер кода у тебя.

@trikadin
Copy link

trikadin commented Oct 3, 2015

Не знаю, у тебя не открывается?

@trikadin
Copy link

trikadin commented Oct 4, 2015

А вот так красиво будет в будущем.

Смотреть в лисе, если что, в консоль.

Из плюшек -- возможность отменить присваивание (валидация), возможность сделать один универсальный обработчик хоть для всех инстансов (потому что обработчик получает всю инфу), минимальное потребление памяти.

В общем, прокси классная штука)

@SerafimArts
Copy link
Author

@trikadin, это конечно круто, но код по-моему нафига не читаем. Используй нормальное ооп, раз боженька (мозилла и ко) в ES6 его дал =)

@trikadin
Copy link

trikadin commented Oct 6, 2015

А что не читаемо-то? О_о

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment