Чему можно научиться у Dart:
переносим подходы из Dart и Angular2 в SPA на JavaScript

Алексей Золотых

Чему можно научиться у Dart: переносим подходы из Dart и Angular2 в SPA на JavaScript Алексей Золотых https://github.com/zolotyh

О Wrike

О докладе

История 1

История 2

Zone.js

При помощи зон вы можете:

Начинаем разбираться

Что будет в консоли?

var myObject = {
  method: function(){
    console.log(this);
  }
};

myObject.method();
		

Что будет в консоли?

var myObject = {
  method: function(){
    setTimeout(function(){
      console.log(this);
    },0);
  }
};

myObject.method();
		

Думаю, что все ответили правильно

  1. Ссылка на myObject
  2. Ссылка на window

Почему так происходит?

— теряется контекст

Схема работы

Как можно исправить?

Представьте себе, что мы хотим контролировать контекст выполнения чужого кода

А что будет, если перехватывать все обращения в Web Api и передавать контекст через замыкания?

Что нужно заменить?

— Да, но это же манки патчинг

— Именно, посмотрите, как это круто!

Пример

var main = function(){
  console.log(Zone.current.name); // 'my-context'
  setTimeout(function(){
   console.log(Zone.current.name); // 'my-context'
  },0);
};

Zone.current.fork({
  name: 'my-context'
}).run(main);
		

Контекст сохраняется

А что будет, если вместо setTimeout поставить вызов или цепочку вызовов стороннего кода?

Или независимый модуль из своего кода?

var main = function(){
  thirdPartyCode(function(){
    throw 'error';
  },0);
};

Zone.current.fork({
  name: 'my-context',
  onHandleError: function(){
    console.log('error');
    return false;
  }
}).run(main);

Так можно ставить задачи в райке или в джире

Перехватываем

Колбеки

Применение

Есть javascript и typescript версия

здесь

Победа

История 1

История 2

Уроки велосипедостроения

Простое приложение

Простой код:

//Класс, для всего приложения
AppView = Backbone.View.extend({
// флаги, описание событий, методы
});

//  Класс для задачи в листе
TodoView = Backbone.View.extend({
// флаги, описание событий, методы
});

Ты хорошо поработал, но мы хотим чуть-чуть расширить функциональность

Повтореяем задачу

//  Класс для задачи в листе
TodoView = Backbone.View.extend({
	isRepeat: true, // флаг
	// Логика для повторяющихся событий
	// ...
});

Потом напишем еще немного кода...

	isRepeat: true,
	clickHandler: function(){
		//...
		if(this.isRepeat){
			this.showRepeatModal();
		}
		// ... код для других флагов
	}

Победа!

А потом, спустя несколько месяцев

//  Класс для задачи в листе
TodoView = Backbone.View.extend({
	isRepeat: true,
	importedFromWrike,
	hasReaded,
	hasPermissions,
	changed,
	synchronized,
	//  еще 500 строк кода для  обработчиков и различных методов
});

Давнокод!

Такой код характерен для многих фреймоворков.

Совпадение?

Выход есть

Наследование

TodoView = Backbone.View.extend({
	//  базовая логика
});
TodoRepeatView = Backbone.View.extend(TodoView,{
	// реализация логики для повторяющихся событий
});

А потом мы узнаём про миксины...

MySuperView = Backbone.View.extend(
	TodoRepeatView,
	MyFeature1View,
	MyFeature2View,
	MyFeature3View,
	MyFeature4View,
	TodoView,{
});

Пример родительского миксина

MyFeature1View = Backbone.View.extend({
	myFeature1method: function(arg){
		console.log(arg.foo);
    }
});

А что случится, если нужно поменять родительское api

MyFeature1View = Backbone.View.extend({
	myFeature1method: function(arg){
		arg.newFoo.call(this);
    }
});

Выводы

Возможно, стоит использовать композицию?

MySuperView = Backbone.View.extend({
	repeater: new RepeatBehavior(),
	render: function(){
	//...
		repeater.myFeature1method(arg);
	//...
	}
});

А что будет, если способ инициализации зависимости поменяется?

MySuperView = Backbone.View.extend({
	repeater: new RepeatBehavior(),
	render: function(){
	//...
		repeater.myFeature1method(arg);
	//...
	}
});
MySuperView = Backbone.View.extend({
	repeater: new RepeatBehavior(newParams),
	render: function(){
	//...
		repeater.myFeature1method(arg);
	//...
	}
});

Мы правда хотим менять во всех местаx где инициализировался класс?

grep -inIEr –color=ALWAYS –include="*.js"
"View.*=" source/code/directory/

Конечно, можно использовать ctrl/CMD + F, но сути это не меняет

Инверсия управления (Inversion of Control, IoC)

Способы реализации

  1. Factory pattern
  2. Service locator
  3. Dependency injection

Service locator

window.loc = new Locator(),
    helloService = {
        say : function (name) { return 'Hello ' + (name || 'World') }
    }
loc.register('hello', helloService);

function SomeClass(locator) {
    var hello = locator.resolve('hello');

    this.say = function () {
        var name = this.constructor.name;
        return hello.say(name);
    };
};
		

Ничего не напоминает?

angular.module('myModule', [])
.factory('serviceId', ['depService', function(depService) {
  // ...
}])
		

AngularJS первой версии использует паттерн сервис локатор для внедрения зависимостей

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

Хватит это терпеть!

У нас есть нормальный DI https://github.com/angular/di.js

@Inject(Grinder, Pump, Heater)
export class CoffeeMaker {
	constructor(grinder, pump, heater) {
	// ...
	}
	brew() {
		console.log('Brewing a coffee...');
	}
}
@Inject(Grinder, Pump, Heater)
export class CoffeeMaker {
	constructor(grinder, pump, heater) {
	// ...
	}
	brew() {
		console.log('Brewing a coffee...');
	}
}
@Inject(Grinder, Pump, Heater)
export class CoffeeMaker {
	constructor(grinder, pump, heater) {
	// ...
	}
	brew() {
		console.log('Brewing a coffee...');
	}
}

Появляются типы

@Injectable()
@Inject(Logger)
export class HeroService {
	constructor(private _logger: Logger) {  }
	getHeroes() {
		this._logger.log('Getting heroes ...')
		return HEROES;
	}
}
var injectorClass = Injector.resolveAndCreate([
	HeroService,
	new Provider(Logger, { useClass: ServerLogger })
]);
expect(injectorClass.get(Logger) instanceof ServerLogger).toBe(true);

		

Если вы используете Angular2, у вас уже есть DI.

Также можно подключить DI отдельно, есть версия для JS, Dart и для Typescript.

Спасибо за внимание!

Вопросы?