Простое ToDo приложение на Ember

AdreS AdreS 18 Октября
Поделиться в соцсетях
Пожаловаться модератору

Старт проекта

Откроем терминал

npm install -g ember-cli
ember new todo-app
cd todo-app
ember serve

Первое, на что следует обратить внимание, — это структура папок и файлов в Ember по сравнению с React.
Самое простое приложение в React будет иметь

index.html 
style.css
index.js

Вы можете закинуть все в index.js (функции, вызовы API и т. д.), не трогая файлы HTML и CSS, и это будет работать.

В Ember каждое новое приложение будет иметь:

App
    Components
    Controllers
    Helpers
    Models
    Routes
Styles
Templates
    Application.hbs
App.js
Index.html
Router.js

Чтобы напечатать «Hello World» на экране, перейдите в application.hbs, удалите
{{outlet}}
и вставьте
<h1>Hello World</h1>
теперь для нашего приложения измените его обратно на
{{outlet}}
Все, что попадет в один компонент в React, будет разбросано между Route, Component и Template в Ember.

Template — это ваш html. Hbs означает handlebar. Главное, что нужно знать, — это то, что Handlebar — это нелогика, поэтому в вашем html нет сопоставления или фильтрации.

Route… лучше всего думать об этом так: Route — это ваш компонент, который будет монтироваться (на самом деле это не так, но для практических целей выполнения чего-то думайте об этом именно так). Данные, которые вы хотите видеть на экране при загрузке страницы, извлекаются/аксиируются/запрашиваются в Route.

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

Helpers — это то место, куда помещаются небольшие вспомогательные функции многократного использования. Например, если вы конвертируете градусы Фаренгейта в градусы Цельсия, то вот куда идет функция, которая это делает.

Что касается контроллеров, то процитирую вопрос, заданный в Ember Guide: «Стоит ли использовать контроллеры в моем приложении? Я слышал, что они уходят!»

Шаг 1 — Создаем route

В терминале пишем
ember g route todo
Вывод в вашем терминале будет следующим:

installing route
  create app/routes/todo.js
  create app/templates/todo.hbs
updating router
  add route todo
installing route-test
  create tests/unit/routes/todo-test.js

Шаг 2 — Отображение списка дел в консоли

Давайте начнем с добавления некоторых существующих дел.
Перейдите в app/routes/todo.js, шаблон должен выглядеть примерно так:

import Route from '@ember/routing/route';

export default class TodoRoute extends Route {
}

Чтобы добавить набор данных todo, добавьте модель к маршруту:

import Route from "@ember/routing/route";

export default class TodoRoute extends Route {
  model() {
    return [
      {
        id: 1,
        todo: "todo 1"
      },
      {
        id: 2,
        todo: "todo 2"
      },
      {
        id: 3,
        todo: "todo 3"
      }
    ];
  }
}

Теперь перейдите в app/templates/todo.hbs, удалите все, что там есть, и добавьте:

<h1>TODO app</h1>
{{log this.model}}

В терминале запустите ember serve
Откройте веб-браузер, перейдите по адресу http://localhost:4200/todo на странице должно появиться приложение TODO. Откройте Inspector -> Console. В консоли вы должны увидеть массив вашей модели.

Шаг 3 — Отображение списка дел на экране

Итак, я принимаю решение, что построю все приложение в одном компоненте. Можете свободно реорганизовать его, чтобы он был в отдельных компонентах. Я бы сказал, что «список дел» и «кнопка добавления нового дела» должны быть двумя отдельными компонентами, но я предоставлю вам возможность разобраться, как это реорганизовать.

Часть 3.1

В Терминале выполните:
ember g component todo-app
В вашем терминале вы увидите следующее:

installing component
  create app/components/todo-app.hbs
  skip app/components/todo-app.js
  tip to add a class, run `ember generate component-class todo-app`
installing component-test

Продолжайте следовать «совету» и выполните команду ember generate component-class todo-app .
Теперь, если вы перейдете в app/components, вы найдете todo-app.hbs и todo-app.js .
Todo-app.hbs — это ваш HTML, а todo-app.js — это ваша логика и часть обработки действий.
Давайте перейдем к todo-app.hbs, удалим все, что там есть, и добавим
<p>sanity check</p>
Если вы сейчас перейдете по адресу http://localhost:4200/todo или http://localhost:4200 вы не увидите проверку работоспособности на экране.
Чтобы отобразить todo-app.hbs на экране, перейдите в todo.hbs и добавьтев файл, чтобы у вас было

<h1>TODO app</h1>
<TodoApp />
{{log this.model}}

Теперь перейдите по адресу http://localhost:4200/todo — вуаля! Проверка работоспособности отображается.

Часть 3.2

Итак, если вы зайдете на todo.hbs и возьмете
{{log this.model}}
и зайдите в todo-app.hbs и добавьте его туда

<p>sanity check</p>
{{log this.model}}

Теперь вы получите undefined в консоли, а не в модели.
Давайте передадим модель из todo в компонент todo-app, изменив
<TodoApp />
к
<TodoApp @model={{this.model}}/>
Несмотря на это изменение, вы все равно получите undefined , поскольку this.model был передан компоненту как @model .
Так что давайте изменим
{{log this.model}}
к
{{log @model}}
Ура! Вы вернулись к исходной точке 0 с моделью, отображаемой в консоли. Теперь давайте ее отобразим.

Часть 3.3

В React самым простым решением для отображения 3 задач будет следующее:

<ul>
    <li>{this.state.model[0].todo}</li>
    <li>{this.state.model[1].todo}</li>
    <li>{this.state.model[2].todo}</li>
</ul>

Вы можете попробовать написать что-то в этом роде в todo-app.hbs, но это не сработает. Поэтому другой вариант отображения этого в React — использовать .map.
Итак, что-то вроде этого:

<ul>
  {this.state.model.map(todo => {
    return <li key={todo.id}>{todo.todo}</li>;
  })}
</ul>

Шаблоны лишены логики, и это означает, что javascript .map не будет работать. Однако у шаблонов есть помощники, которые могут привнести некоторую логику в шаблон.
Мы сделаем что-то похожее на .map, используя вспомогательный метод «each» .
Итак, давайте перейдем в todo-app.hbs и добавим:

<ul>
  {{#each @model as |item|}}
  <li>{{item.todo}}</li>
  {{/each}}
</ul>

Отлично! Список дел отображается.

Шаг 4 — Добавление действий

Теперь давайте добавим текстовое поле и кнопку, чтобы вы могли добавлять новые задачи в список.
На стороне html теги будут идентичны тем, которые вы бы использовали в компоненте React. Итак, давайте добавим это в todo-app.hbs :

<ul>
  {{#each @model as |item|}}
  <li>{{item.todo}}</li>
  {{/each}}
</ul>
<form>
  <input placeholder='Add todo' type='text' />
  <button type='submit'>Add</button>
</form>

Это отобразит поле ввода и кнопку, но, конечно, это ничего не сделает, так что пришло время наконец взглянуть на todo-app.js . Но прежде чем мы это сделаем, давайте посмотрим, как это выглядело бы в React.

Реагировать вид

import ReactDOM from "react-dom";
import React, { Component } from "react";

class Todo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      model: [
        {
          id: 1,
          todo: "todo 1"
        },
        {
          id: 2,
          todo: "todo 2"
        },
        {
          id: 3,
          todo: "todo 3"
        }
      ],
      text: ""
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();
    const i = this.state.model[this.state.model.length - 1].id + 1;
    let newTodo = {
      todo: this.state.text,
      id: i
    };
    this.setState(prevState => ({
      model: prevState.model.concat(newTodo),
      text: ""
    }));
  }

  handleChange(e) {
    this.setState({
      text: e.target.value
    });
  }

  render() {
    return (
      <div>
        <h1>TODO LIST</h1>
        <ul>
          {this.state.model.map(todo => {
            return <li key={todo.id}>{todo.todo}</li>;
          })}
        </ul>
        <form onSubmit={this.handleSubmit}>
          <input value={this.state.text} onChange={e => this.handleChange(e)} />
          <button>Add</button>
        </form>
      </div>
    );
  }
}

ReactDOM.render(<Todo />, document.getElementById("root"));

Вернуться к Эмберу

Теперь давайте напишем handleChange и handleSubmit в Ember.
Реакция

  handleChange(e) {
    this.setState({
      text: e.target.value
    });
  }

почти не меняется, поскольку становится:

  @tracked
  text = "";

  @action
  onChange(e) {
    this.text = e.target.value;
  }

@tracked — это ваше состояние, но лучше всего прочитать о @tracker и @action в руководстве Ember .
А handleSubmit идет от:

  handleSubmit(e) {
    e.preventDefault();
    const i = this.state.model[this.state.model.length - 1].id + 1;
    let newTodo = {
      todo: this.state.text,
      id: i
    };
    this.setState(prevState => ({
      model: prevState.model.concat(newTodo),
      text: ""
    }));
  }

к:

  @action
  submit(model, e) {
    e.preventDefault();
    const i = model[model.length - 1].id + 1;
    const newTodo = {
      id: i,
      todo: this.text
    };
    model.pushObject(newTodo);
  }

Итак, todo-app.js в конечном итоге выглядит так:

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";

export default class TodoAppComponent extends Component {
  @tracked
  text = "";

  @action
  submit(model, event) {
    event.preventDefault();
    const i = model[model.length - 1].id + 1;
    const newTodo = {
      id: i,
      todo: this.text
    };
    model.pushObject(newTodo);
    console.log("add", model);
  }

  @action
  onChange(e) {
    this.text = e.target.value;
  }
}

Шаг 5. Соединение .js с .hbs

Рендеринг React выглядит так:

        <form onSubmit={this.handleSubmit}>
          <input value={this.state.text} onChange={e => 
            this.handleChange(e)} />
          <button>Add</button>
        </form>

в Ember ваш todo-app.hbs должен выглядеть так:

<form onsubmit={{fn this.submit @model}}>
  <input placeholder='Add todo' type='text' value={{this.text.todo}} 
    onchange={{fn this.onChange}} />
  <button type='submit'>Add</button>
</form>

Итак, полный todo-app.hbs выглядит так:

<ul>
  {{#each @model as |item|}}
  <li>{{item.todo}}</li>
  {{/each}}
</ul>
<form onsubmit={{fn this.submit @model}}>
  <input placeholder='Add todo' type='text' value={{this.text.todo}} onchange={{fn this.onChange}} />
  <button type='submit'>Add</button>
</form>

Шаг 6. Добавление функции «Удалить»

Я должен признать, что удаление не будет выглядеть красиво в React или Ember, потому что мы имеем дело с массивом объектов. Если бы это был массив строк, жизнь была бы проще.
В React это выглядит так:

  removeItem(id) {
    let model = this.state.model;
    const index = model
    .map((file, index) => {
      if (file.id === id) {
        return index;
      } else return undefined
    })
    .filter(id => id !== undefined);
    model.splice(index, 1);
    this.setState([...model]);
  }

В какой-то степени Ember следует той же общей идее. Вы вычисляете индекс, затем сращиваете массив.
Есть одно но. HTML-сторона не будет перерисовываться, если вы удалите «todo» из массива. Поэтому мой обходной путь: заменить элемент, который должен быть удален, пустым объектом и добавить в шаблон «if empty don't show».
todo-app.js

  @action
  delete(model, item) {
    const index = model
      .map((file, index) => {
        if (file.id === item.id) {
          set(item, "id", null);
          set(item, "todo", null);
          return index;
        }
      })
      .filter(id => id !== undefined);
    model.splice(index[0], 1);
  }

и todo-hbs.js

<ul>
  {{#each @model as |item|}}
  {{#if item.id}}
  <li>{{item.todo}}</li>
  <button onclick={{fn this.delete @model item}} type='button'>delete</button>
  {{/if}}
  {{/each}}
</ul>

Приложение Todo готово.

Для ответа вы можете авторизоваться