Matreshka.js 2: От простого к простому

  • Tutorial

image


Документация на русском
Github репозиторий


Всем привет! В этой статье я расскажу, как пользоваться Matreshka.js на трех несложных примерах. Мы рассмотрим базовые возможности фреймворка, познакомимся с тем, как работать с данными и разберем коллекции.


Пост является краткой компиляцией переводов из этого репозитория.



1. Hello World!


Для начала стоит ознакомиться с приложением Hello World, которое вынесено из поста на сайт.


// ...
// связываем свойство x и текстовое поле
this.bindNode('x', '.my-input');

// связываем свойство x и блок с классом my-output
this.bindNode('x', '.my-output', htmlBinder());

// слушаем изменения свойства x
this.on('change:x', () =>
            console.log(`x изменен на "${this.x}"`));
// ...

При обновлении свойства x произойдет три вещи:


  • Обновится значение поля ввода
  • Обновится HTML содержимое блока
  • В консоль будет выведена информация о том, что x поменяли

При вводе текста в текстовое поле:


  • Обновится свойство x
  • Обновится HTML содержимое блока
  • В консоль будет выведена информация о том, что x поменяли

Как видите, не нужно вручную отлавливать событие ввода в поле текста; при изменении значения свойства не нужно вручную устанавливать значения HTML узлам; не нужно объявлять дескриптор самостоятельно.


Демо


2. Форма авторизации. Знакомимся с Matreshka.Object


Следующий пример — реализация формы авторизации на сайте. У нас есть два текстовых поля: логин и пароль. Есть два чекбокса: «показать пароль» и «запомнить меня». Есть одна кнопка: «войти», которая активна только тогда, когда форма валидна. Скажем, что валидация формы пройдена если длина логина не меньше 4 символов, а длина пароля не меньше 5 символов.


image

Немного теории: Matreshka.Object играет роль класса, создающего объекты типа ключ-значение. В каждом экземпляре класса можно отделить свойства, отвечающие за данные (то что будет передано не сервер, например) от других свойств (то, что серверу не нужно, но определяет поведение приложения). В данном случае, логин, пароль и “запомнить меня” являются данными, которые мы отправляем на сервер, а свойство, говорящее о том, валидна ли форма — нет.


Подробная и актуальная информация об этом классе находится в документации.


Итак, создадим класс, который наследуется от Matreshka.Object.


class LoginForm extends Matreshka.Object {
    constructor () {
        // ...
    }
}

Так как “приложение” очень небольшое, всю логику можно разместить в конструкторе класса.


Перво-наперво, объявим данные по умолчанию.


super();

this.setData({
    userName: '',
    password: '',
    rememberMe: true
})

Метод setData не только устанавливает значения, но и объявляет свойства, отвечающие за данные. Т. е. userName, password и rememberMe должны быть переданы на сервер (в этом примере просто выведем JSON на экран).


Так как при разработке приложений, рекомендуется ипользовать всю мощь ECMAScript 2015, и, так как конструктор Matreshka.Object вызвает setData с переданным в него аргументом, мы инициализируем дефольные данные с помощью единственного вызова super (который сделал бы то же семое, что и Matreshka.Object.call(this, { ... })) для того, чтоб код стал симпатичнее. Код ниже делает то же самое, что и предыдущий:


super({
    userName: '',
    password: '',
    rememberMe: true
})

Объявляем свойство, isValid, которое зависит от свойств userName и password. При изменении любого из этих свойств (из кода, консоли или с помощью привязанного элемента), свойство isValid тоже изменится.


.calc('isValid', ['userName', 'password'], (userName, password) => {
    return userName.length >= 4 && password.length >= 5;
})

isValid будет равно true, если длина имени пользователя не меньше четырех, а длина пароля — не меньше пяти. Метод calc — это еще одна крутая возможность фреймворка. Одни свойства могут зависеть от других, другие от третьих, в третьи вообще от свойств другого объекта. При этом, вы защищены от цикличных ссылок. Метод прекращает работу если встречается с опасными зависимостями.


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


// альтернативный синтаксис метода позволяет передать объект ключ-элемент в качестве первого аргумента,
// что несколько уменьшает количество кода
.bindNode({
    sandbox: '.login-form',
    userName: ':sandbox .user-name',
    password: ':sandbox .password',
    showPassword: ':sandbox .show-password',
    rememberMe: ':sandbox .remember-me'
})

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


Затем, связываем кнопку, отвечающую за отправку формы, и свойство isValid. Когда isValid равно true, добавляем элементу класс "disabled", когда false — убираем. Это пример одностороннего связывания, а точнее, значение свойства объекта влияет на состояние HTML элемента, но не наоборот.


.bindNode("isValid", ":sandbox .submit", {
    setValue(v) {
        this.classList.toggle("disabled", !v);
    }
})

Вместо такой записи можно использовать более краткую:


.bindNode('isValid', ':sandbox .submit',
    Matreshka.binders.className('disabled', false))

См. документацию к объекту binders.


Связываем поле с паролем и свойство showPassword (“показать пароль”) и меняем тип инпута в зависимости от значения свойства (:bound(KEY) — это последний нестендартный селектор).


.bindNode("showPassword", ":bound(password)", {
    getValue: null,
    setValue(v) {
        this.type = v ? "text" : "password";
    }
})

getValue: null означает то, что мы переопределяем стандартное поведение фреймворка при привязке элементов формы.


Добавляем событие отправки формы.


.on("submit::sandbox", evt => {
    this.login();
    evt.preventDefault();
})

submit — обычное, произвольное DOM или jQuery событие, sandbox — наша форма (.login-form). Такое событие и ключ должны быть разделены двоеточием. Это синтаксический сахар DOM событий, т. е. событие можно навешать любым другим способом, в том числе, и используя addEventListener:


this.nodes.sandbox.addEventListener("submit", evt => { ... });

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


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


login() {
    if(this.isValid) {
        alert(JSON.stringify(this));
    }

    return this;
}

В самом конце создаём экземпляр класса.


const loginForm = new LoginForm();

Можете снова открыть консоль и изменить свойства вручную:


loginForm.userName = "Chuck Norris";
loginForm.password = "roundhouse_kick";
loginForm.showPassword = true;

Демо


3. Список пользователей. Разбираемся с коллекциями (Matreshka.Array)


С данными вида ключ-значения разобрались. Рассмотрим коллекции. Скажем, задача звучит так: вывести список неких людей в виде таблицы.



Чтобы не усложнять пример, поместим заранее подготовленные данные в переменную data.


const data = [{
        name: 'Ida T. Heath',
        email: 'ida@dayrep.com',
        phone: '507-879-9766'
    }, {
        name: 'Robert C. Burkhardt',
        email: 'rburkhardt@teleworm.us',
        phone: '321-252-5698'
    }, {
        name: 'Gerald S. Reaves',
        email: 'gsr@rhyta.com',
        phone: '765-431-5347'
}];

(имена и телефоны получены с помощью генератора случайных данных)


Для начала, как обычно, создаём HTML разметку.


<table class="users">
    <thead>
        <th>Name</th>
        <th>Email</th>
        <th>Phone</th>
    </thead>
    <tbody><!-- здесь будет список пользователей --></tbody>
</table>

Объявим коллекцию Users, которая наследуется от Matreshka.Array.


class Users extends Matreshka.Array {

}

Укажем свойство itemRenderer, которое отвечает за то как элементы массива будут рендериться на странице.


get itemRenderer() {
    return '#user_template';
}

В данном случае, указан селектор в качестве значения, ссылающийся на шаблон в HTML коде.


<script type="text/html" id="user_template">
    <tr>
        <td class="name"></td>
        <td class="email"></td>
        <td class="phone"></td>
    </tr>
</script>

Свойство itemRenderer может принимать и другие значения, в том числе, функцию или HTML строку.


И укажем значение свойства Model, определяя класс элементов, содержащихся в коллекции.


get Model() {
    return User;
}

Класс User мы создадим немного позже, для начала определим конструктор новосозданного класса коллекции.


constructor(data) {
    super();
    this
        .bindNode("sandbox", ".users")
        .bindNode("container", ":sandbox tbody")
        .recreate(data);
}

При создании экземпляра класса


  • Связываются свойство sandbox и элемент '.users' создавая песочницу (границы влияния класса на HTML).
  • Связываются свойство container и элемент ':sandbox tbody', определяя HTML узел, куда будут вставляться отрисованные элементы массива.
  • Добавляем переданные данные в массив методом recreate.

Отлично. Но мы собираемся использовать как можно больше возможностей ECMAScript 2015, улучшающих код. Поэтому, мы будем использовать вызов super для заполнения массива.


constructor(data) {
    super(...data)
        .bindNode('sandbox', '.users')
        .bindNode('container', ':sandbox tbody')
        .rerender();
}

  • Добавляются айтемы в коллекуию с помощью вызова super (который делает то же самое, что и вызов Matreshka.Array.apply(this, data)).
  • Связываются свойство sandbox и элемент '.users'
  • Связываются свойство container и элемент ':sandbox tbody'
  • Вызывается метод rerender котрый рендерит коллекцию (мы должны его вызвать, так как привязали container после того, как добавили данные).

Теперь объявляем “Модель”: класс User, который наследуется от уже знакомого нам Matreshka.Object.


class User extends Matreshka.Object {
    constructor(data) { ... }
}

Устанавливаем данные, переданные в конструктор методом setData, или, как обычно, вызываем super.


super(data);

Затем, дожидаемся события render, которое срабатывает тогда, когда соответствующий HTML элемент был создан, но еще не вставлен на страницу. В обработчике привязываем соответствующие свойства соответствующим HTML элементам. Когда значение свойства изменится, innerHTML заданного элемента тоже поменяется.


this.on( 'render', function() {
    this
        .bindNode({
            name: ':sandbox .name',
            email: ':sandbox .email',
            phone: ':sandbox .phone'
        }, Matreshka.binders.html())
    ;
})

Есть возможность заменить прослушиваение события "render" созданием специального метода onRender (см. доку), но, чтоб не усложнять этот пример, оставим так.


В конце создадим экземпляр класса Users, передав данные в качестве аргумента


const users = new Users(data);

Всё. При обновлении страницы вы увидите таблицу со списком юзеров.


Демо


Теперь откройте консоль и напишите:


users.push({
    name: 'Gene L. Bailey',
    email: 'bailey@rhyta.com',
    phone: '562-657-0985'
});

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


users.reverse();

Или любой другой метод массива (sort, splice, pop...). Matreshka.Array, кроме собственных методов, содержит все без исключения методы стандартного JavaScript массива. Затем,


users[0].name = 'Vasily Pupkin';
users[1].email = 'mail@example.com'

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


Не забывайте, что Matreshka.Array поддерживает собственный набор событий. Вы можете отлавливать любое изменение в коллекции: добавление, удаление, пересортировку элементов методом on.


users.on("addone", evt => {
    console.log(evt.addedItem.name);
});
users.push({
    name: "Clint A. Barnes"
});

(выведет в консоль имя добавленного пользователя)




Как говорится в документации к itemRenderer можно определять рендерер на уровне класса Model. Это ответ на частозадаваемый вопрос: почему я должен определять рендерер на уровне коллекции. Вместо определения itemRenderer для класса Users можно определить свойство renderer для класса User.


class User extends Matreshka.Object {
    get renderer() {
        return '#user_template';
    }
    constructor(data) { ... }
}

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


class Users extends Matreshka.Array {
    get itemRenderer() {
        return '#user_template';
    }
    constructor(data) {
        super(...data)
            .bindNode('sandbox', '.users')
            .bindNode('container', ':sandbox tbody')
            .rerender();
    }
    onItemRender(item) {
        // item - это обычный объект, а не экзепляр Matreshka, поэтомы мы воспользуемся
        // статичной версией метода bindNode
        Matreshka.bindNode(item, {
            name: ':sandbox .name',
            email: ':sandbox .email',
            phone: ':sandbox .phone'
        }, Matreshka.binders.html());
    }
}

Так же, можно использовать парсер байндингов, который по умолчанию используется классом Matreshka.Array, определив рендерер прямо в классе и не добавляя ничего в HTML.


class Users extends Matreshka.Array {
    get itemRenderer() {
        return `
        <tr>
          <td class="name">{{name}}</td>
          <td class="email">{{email}}</td>
          <td class="phone">{{phone}}</td>
        </tr>`;
    }
    constructor(data) {
        super(...data)
            .bindNode('sandbox', '.users')
            .bindNode('container', ':sandbox tbody')
            .rerender();
    }
}

Спасибо всем тем, кто сообщал об опечатках на сайте. Всем добра!

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

Выглядит просто?
Собираетесь ли попробовать Matreshka.js в следующем проекте?
Дайте оценку тексту документации к Matreshka.js
Matreshka.js 23,94
JavaScript фреймворк для новичков
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 34
  • +6
  • +1
    не надо было брать название которое прочно ассоциируется с другим опен-сурс проектом
    • +2
      Тот — Matroska, а тут есть balalaika (DOM) уже. Осталось Event driven движок сделать (vodka, ushanka?).
      • 0
        Ещё пара фреймворков и следующий можно называть Boyan
    • +1
      Вам бы примеров побольше, например
      — простенькое дерево категорий (на генерацию хтмля)
      — взаимодействие со сторонними библиотеками (например с картами, добавляем десяток точек они появляются на карте, связываем клик по маркеру и содержимое попапа с моделью)
      — работа с hash частью url'a и историей (я понимаю что это немного за рамками библиотеки, но всеже)
      • +1
        Спасибо, записал в список.
      • 0
        Давно присматривался и уже забыл, а тут ваша статья.

        Ни капли злой иронии, но почему я должен посмотреть Matreshka.js если мне безумно крутым и легким показался Mithril.js?

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

        Теперь классы — а почему бы не ES6 синтаксис сразу в документации, прям жирным шрифтом? IE8 идет лесом как мне кажется, уже давно и навсегда.

        А вообще побольше примеров и, желательно, посложнее и более приближенных к реальным задачам (например гриды, формы и так далее).
        • 0
          но почему я должен посмотреть Matreshka.js если мне безумно крутым и легким показался Mithril.js?

          Все фреймворки хороши по-своему. Вы мне ничего не должны :)

          Теперь классы — а почему бы не ES6 синтаксис сразу в документации, прям жирным шрифтом?

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

          IE8 идет лесом как мне кажется, уже давно и навсегда.

          К сожалению, это не так. Еще как минимум год разработчикам прийдется мучиться. Посмотрите любую статистику.

          А вообще побольше примеров и, желательно, посложнее и более приближенных к реальным задачам (например гриды, формы и так далее).

          Полностью согласен. Но очень сложно соблюсти баланс между максимальной понятностью и функционалом примера.

          • 0
            Но вы же хотите привлечь внимание, назовите причины по которым это стоит сделать с оглядкой на упомянутый крутой фрэймворк.
            Неужели вам не хочется понять в сравнении преимущества своего детища? Я же не издеваюсь ни капли.

            Да уж я с вами не соглашусь, про ES6 знают и его хотят использовать. Многие ждали и верили, дождались и используют.

            Я уже год назад отказался от поддержки IE < 8 и не собираюсь возвращаться и извращаться. Того же и всем желаю.

            Так для новичков будут простые примеры, а из реальной жизни — для тех кто поигрался и захотел на продакшне.
      • 0
        Но вы же хотите привлечь внимание, назовите причины по которым это стоит сделать с оглядкой на упомянутый крутой фрэймворк.
        Неужели вам не хочется понять в сравнении преимущества своего детища? Я же не издеваюсь ни капли.

        Я вряд ли когда-то буду говорить, что такой-то фреймворк плохой, а этот лучше всех. Почему стоит использовать или не использовать Матрешку, решаете вы сами. Главная задача разработчика любого свободного инструмента — дать возможность определиться, нужно ли его использовать или нет. Мне кажется, здесь с этим всё в порядке: есть сайт, есть «рекламирующие» тексты, есть документация, есть примеры (в том числе и средних размеров: TodoMVC).

        Я уже год назад отказался от поддержки IE < 8 и не собираюсь возвращаться и извращаться.

        Я и не против, но клиенты требуют.
        • 0
          Зачем же вы добавляете в ваше решение излишний (на мой взгляд) функционал? Нет желания пользоваться AngularJS только от того, что мне не нужны их фильтры да сервисы, а вот связывание хочется.

          Думаю перейти на «матрешку», но можно ли уменьшить ее вес, выпилив все, кроме связывания?
          • 0
            Связывание тут реализуется достаточно просто. Любой программист со средним опытом может реализовать standalone функцию bindNode с помощью Object.defineProperty и addEventListener.
            • 0
              Решил для небольшой тренеровки после обеда написать отдельную функцию bindNode.

              Вот, если вам интересно:
              window.bindNode = function bindNode( object, key, node, binder ) {
              	var value = object[ key ];
              	Object.defineProperty( object, key, {
              		get: function() {
              			return value;
              		},
              		set: function( v ) {
              			binder.setValue.call( node, v );
              		}
              	});
              	
              	node.addEventListener( binder.on, function() {
              		value = binder.getValue.call( node );
              	});
              };
              


              Функция поддерживает только один набор аргументов, аргумент node должен быть DOM нодой, нет стандартных байндеров и пр., но вы можете сами дописать, это не трудно.

              jsbin.com/mabetap/2/edit?html,js,console,output
              Введите data.x = 'Hello world' в консоли.
              • 0
                Ну и «многие ко многим» не поддерживаются. Но это всё детали.
                • 0
                  Спасибо. Возможно было бы очень удобно, если бы вы разделили фреймворк на части, чтобы можно было наращивать функционал постепенно, по мере надобности.

                  Использую ваш фреймворк в связке с React. Думаю об использовании балалайки )
                  • 0
                    Возможно было бы очень удобно, если бы вы разделили фреймворк на части, чтобы можно было наращивать функционал постепенно, по мере надобности.

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

                    Использую ваш фреймворк в связке с React

                    Буду благодарен, если поделитесь опытом (здесь или в личке). Очень интересно.

                    • 0
                      Буду благодарен, если поделитесь опытом (здесь или в личке). Очень интересно.


                      Постараюсь что нибудь написать специально для вас (распространять имеющиеся исходники не имею права)
                      • 0
                        Идея в том, чтобы позволить программисту рендерить структуру виджета с помощью React, но обновлять конкретные значения виджета через связывание.
                        Пример использования
                        define('components/Timer', ['react', 'matreshka'], function (React, Matreshka) {
                            return React.createClass({
                                displayName: 'Timer',
                        
                                // Увеличиваем значение таймера используя модель
                                tick: function () {
                                    this.model.value = Number(this.model.value) + 1;
                                },
                        
                                // Обнуляем таймер используя модель
                                clean: function(){
                                    this.model.value = 0;
                                },
                        
                                // Создаем модель с использованием Matreshka
                                initModel: function(){
                                    this.model = new (Matreshka.Class({
                                        'extends': Matreshka.Object,
                                        constructor: function (node, value) {
                                            this.value = value;
                        
                                            this.bindNode('value', node, {
                                                setValue: function (v) {
                                                    this.innerHTML = v;
                                                }
                                            });
                                        }
                                    }))(this.getDOMNode().lastChild, this.props.value);
                                },
                        
                                // Вешаем обработчики на виджет
                                componentDidMount: function () {
                                    this.initModel();
                        
                                    this.timer = setInterval(this.tick, 500);
                        
                                    this.getDOMNode().lastChild.addEventListener('click', this.clean);
                                },
                        
                                // Удаляем обработчики с виджета
                                componentDidUnmount: function () {
                                    clearInterval(this.timer);
                        
                                    this.getDOMNode().lastChild.removeEventListener('click', this.clean);
                                },
                        
                                // рендерим виджет
                                render: function () {
                                    return React.createElement('div', null, [
                                        React.createElement('span', null, 'Timer: '),
                                        React.createElement('span', {title: 'Click to reset'})
                                    ]);
                                }
                            });
                        });
                        

                        • 0
                          Более сложный пример. Головной элемент состоит из подэлемента, который способен изменять свою HTML структуру (благодаря React). Тот, в свою очередь, состоит из еще более мелких элементов, которые могут изменять свое значение (благодаря Matreshka).

                          Пример использования
                          var todo = React.createClass({
                                  inc: function(){
                                      this.model.value = Number(this.model.value) + 1;
                                  },
                          
                                  componentDidMount: function(){
                                      this.model = new (Matreshka.Class({
                                          'extends': Matreshka.Object,
                                          constructor: function (node, value) {
                                              this.value = value;
                          
                                              this.bindNode('value', node, {
                                                  setValue: function (v) {
                                                      this.innerHTML = v;
                                                  }
                                              });
                                          }
                                      }))(React.findDOMNode(this.refs.data), this.props.value);
                          
                                      React.findDOMNode(this.refs.button).addEventListener('click', this.inc);
                                  },
                          
                                  render: function(){
                                      return React.createElement('li', null, [
                                          React.createElement('span', {ref: 'data'}, this.props.value),
                                          React.createElement('input', {type: 'button', value: 'inc', ref: 'button'})
                                      ]);
                                  }
                              });
                          
                              var todoList = React.createClass({
                                  getInitialState: function(){
                                    return {items: this.props.items};
                                  },
                          
                                  addLi: function(text){
                                      var newState = this.state.items;
                                      newState.push(React.createElement(todo, {value: text}));
                                      this.setState(newState);
                                  },
                          
                                  render: function(){
                                      return React.createElement('ul', null, this.state.items)
                                  }
                              });
                          
                              var todoApp = React.createClass({
                                  addTodo: function(){
                                      this.refs.todoList.addLi(React.findDOMNode(this.refs.text).value);
                                      React.findDOMNode(this.refs.text).value = '';
                                  },
                          
                                  componentDidMount: function(){
                                      React.findDOMNode(this.refs.button).addEventListener('click', this.addTodo);
                                  },
                          
                                  componentDidUnmount: function(){},
                          
                                  render: function(){
                                      return React.createElement('div', null, [
                                          React.createElement(todoList, {ref: 'todoList', items: []}),
                                          React.createElement('input', {type: 'text', ref: 'text'}),
                                          React.createElement('input', {type: 'button', value: 'Add', ref: 'button'})
                                      ]);
                                  }
                              });
                          

                          • 0
                            Круто, спасибо. Небольшое замечание по Матрешке: если вам нужно свойство с определенным типом, юзайте mediate.

                            this.mediate('value', Number);
                            

                            Тогда вместо

                            this.model.value = Number(this.model.value) + 1;
                            

                            Можно писать

                            this.model.value++;
                            
                  • 0
                    Создание сервера сборки, к сожалению, штука очень трудозатратная. Если еще интересуетесь фреймворком, я могу предложить вот эту штуку. Вся функциональность, отвеающая за классы там отсутствует (хотя, много лишнего может быть для вас).
                  • 0
                    Где-то видел сравнение производительности матрешки и прочих популярных фреймворков.
                    Подскажите, где посмотреть?
                    Как раз решил обкатать матреху, до сих пор пользовался только jquery, да все велосипеды изобретал )))
                      • 0
                        Самый крутой ember, получается… даже не слышал.
                        Ладно, опробую матреху чисто из-за названия.
                        • 0
                          Как подметили в комментариях ниже, эти тесты не совсем корректны, и Ангуляр, при определенных условиях, может работать быстрее всех (хотя не могу судить).
                          • 0
                            Хорошо бы поиск нормальный по документации сделать.
                            Вот я новичек, не знаю и не помню методов матрешки, а хочу найти «взаимодействие с DOM», например.
                            Гуглить не получается, тк сайт на скриптах и индексируется плохо.
                            • 0
                              Это проблема… Подумаю, как решить.
                              • 0
                                И спасибо за статью.
                                • 0
                                  Готовлю еще)
                                  • 0
                                    Ок. Дайте знать, а то не всегда успеваю читать Хабру.

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

                      Самое читаемое