рдЙрджрд╛рд╣рд░рдг рдХрд╛рд░реНрдп рд╕реВрдЪреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкреНрд░рднрд╛рд╡рдХрд╛рд░-рдбреЛрдо рдХрд╛ рдкрд░рд┐рдЪрдп

рдХрдИ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдкреНрд░рднрд╛рд╡рд╢реАрд▓ рд░рд╛рдЬреНрдп рдкреНрд░рдмрдВрдзрдХ рдХреЛ рдЬрд╛рдирддреЗ рд╣реИрдВ , рдХрд┐рд╕реА рдиреЗ рди рдХреЗрд╡рд▓ рдЗрд╕реЗ рджреЗрдЦрд╛, рдмрд▓реНрдХрд┐ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдареЗрд╕ рдореЗрдВ рднреА рдХрд┐рдпрд╛ред рд╢рд░рдж рдЛрддреБ рдХреА рд╕рдорд╛рдкреНрддрд┐ рдХреЗ рдмрд╛рдж рд╕реЗ, рдЗрд╕рдХрд╛ рд▓реЗрдЦрдХ рдПрдХ рдкреНрд░рднрд╛рд╡рдХрд╛рд░ рдХреЗ рд▓рд┐рдП рд╕рдХреНрд░рд┐рдп рд░реВрдк рд╕реЗ рдбреЗрд╡рдЯреВрд▓реНрд╕ рд╡рд┐рдХрд╕рд┐рдд рдХрд░ рд░рд╣рд╛ рд╣реИ, рдФрд░ рдЗрд╕ рдХрд╛рдо рдХреА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдореЗрдВ рд╡рд╣ рдПрдХ рдЕрдиреБрдкреНрд░рдпреЛрдЧ - рдкреНрд░рднрд╛рд╡рдХрд╛рд░-рдбреЛрдо рдХреЗ рдкреНрд░рддрд┐рдкрд╛рджрди рдХреЗ рд▓рд┐рдП рдПрдХ рдмрд╣реБрдд рд╣реА рджрд┐рд▓рдЪрд╕реНрдк рдкреБрд╕реНрддрдХрд╛рд▓рдп рд▓рд┐рдЦрдиреЗ рдореЗрдВ рдХрд╛рдордпрд╛рдм рд░рд╣рд╛ ред


рд╣рдо рдЗрд╕ рд░реЗрдВрдбрд░ рдХреЛ рдЬрд╛рди рдкрд╛рдПрдВрдЧреЗ - рдЗрд╕ рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдореЗрдВ рд╣рдо рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЯреЛрдбреЛ рдПрдкреНрд▓реАрдХреЗрд╢рди рдмрдирд╛рдПрдВрдЧреЗред



рддрд░реНрдХ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЕрдиреБрдкреНрд░рдпреЛрдЧ- рдкреНрд░рднрд╛рд╡рдХрд╛рд░-рдбреЛрдо рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдкреНрд░рднрд╛рд╡рдХрд╛рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ ред


рджреГрд╢реНрдп рднрд╛рдЧ рдХреЗ рд▓рд┐рдП, рдХреЗ рдкрд╣рд▓реЗ рд╕реЗ рддреИрдпрд╛рд░ рдХрд░рддреЗ рд╣реИрдВ todomvc рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рдЯреЗрдореНрдкрд▓реЗрдЯ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЗ рд╕рд╛рде todomvc рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рд╕реАрдПрд╕рдПрд╕ рджреНрд╡рд╛рд░рд╛ рд╢реИрд▓рд┐рдпреЛрдВ tastejs рдХреЗ рд░реВрдк рдореЗрдВ рдЖрдзрд╛рд░ ред


1. рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреА рддреИрдпрд╛рд░реА


рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЖрдк рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╡реЗрдмрдкреИрдХ рдФрд░ npm рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдо npm рдХреЛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ, рд╡реЗрдмрдкреИрдХ рдХреЗ рд╕рд╛рде рдПрдХ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдмрдирд╛рдиреЗ рдФрд░ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рд▓реЙрдиреНрдЪ рдХрд░рдиреЗ рдХреЗ рдЪрд░рдг рдХреЛ рдЫреЛрдбрд╝ рджреЗрддреЗ рд╣реИрдВ (рдпрджрд┐ рд╣рдо рдкрд░рд┐рдЪрд┐рдд рдирд╣реАрдВ рд╣реИрдВ, рддреЛ google webpack boilerplate)ред


рд▓реЗрдЦрди рдХреЗ рд╕рдордп рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрд╕реНрдХрд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рдЖрд╡рд╢реНрдпрдХ рдкреИрдХреЗрдЬ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:
npm install effector@20.11.5 effector-dom@0.0.10 todomvc-app-css


2. рдЪрд▓рд┐рдП рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ


рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЖрдЗрдП рдЖрд╡реЗрджрди рдХреА рд╕рдВрд░рдЪрдирд╛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░реЗрдВред


, , , - best practices .


, .


:


srs/
  view/
    app.js //   
    title.js // 
    footer.js //  ,  ,   
    header.js //   ,   
    main.js //  
    todoItem.js //  ,   
  model.js //  
  index.js //   

3.


, : , , .


( undefined) Store, тАФ Event.


combine, .


// src/model.js
import {createStore, createEvent, combine} from 'effector';

// 

//  
export const $todos = createStore([]); 

//  ,    null/true/false
export const $activeFilter = createStore(null); 

//  
export const $filteredTodos = combine(
  $todos,
  $activeFilter,
  (todos, filter) => filter === null
    ? todos
    : todos.filter(todo => todo.completed === filter)
);

// 

//   
export const appended = createEvent(); 

// /  
export const toggled = createEvent();

//  
export const removed = createEvent();

//    
export const allCompleted = createEvent();

//   
export const completedRemoved = createEvent();

//  
export const filtered = createEvent();

.
, store.on


// src/model.js
...

$todos
  //   
  .on(appended, (state, title) => [...state, {title, completed: false}])
  //  .     title
  .on(removed, (state, title) => state.filter(item => item.title !== title)) 
  // / 
  .on(toggled, (state, title) => state.map(item => item.title === title 
    ? ({...item, completed: !item.completed})
    : item))
  //   
  .on(allCompleted, state => state.map(item => item.completed
    ? item
    : ({...item, completed: true})))
  //   
  .on(completedRemoved, state => state.filter(item => !item.completed));

$activeFilter
  // 
  .on(filtered, (_, filter) => filter);

. .


4. view


view effector-dom. , SwiftUI .


, , . dom- .


effector-dom dom , . using, dom- , :


// src/index.js
import {using} from 'effector-dom';
import {App} from './view/app';

using(document.body, () => {
  App();
});

h, dom- , .


dom- spec, :


// src/view/app.js
import {h, spec} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {Header} from './header';
import {Main} from './main';
import {Footer} from './footer';

export const App = () => {
  //  section 
  h('section', () => {
    //    
    spec({attr: {class: classes.todoapp}});

    //     
    Header();
    Main();
    Footer();
  });
};

.


5.


h1 .


dom- , , . spec , :


// src/view/title.js
import {h} from 'effector-dom';

export const Title = () => {
  h('h1', {text: 'todos'});
};

6.


, effector-dom effector , , ..


, header. dom- spec ( ), handler.


, input , $value input.


sample . api sample тАФ , тАФ , : event = sample($store, triggerEvent).


// src/view/header.js
import {h, spec} from 'effector-dom';
import {createEvent, createStore, forward, sample} from 'effector';
import classes from 'todomvc-app-css/index.css';
import {Title} from './title';
import {appended} from '../model';

export const Header = () => {
  h('header', () => {
    Title();

    h('input', () => {
      const keypress = createEvent();
      const input = createEvent();

      //   ,
      const submit = keypress.filter({fn: e => e.key === 'Enter'});

      //     
      const $value = createStore('')
        .on(input, (_, e) => e.target.value)
        .reset(appended); //    

      //         forward({from, to})
      forward({ 
        //    $value   submit,
        //       
        from: sample($value, submit).filter({fn: Boolean}), 
        to: appended,
      });

      spec({
        attr: {
          class: classes["new-todo"],
          placeholder: 'What needs to be done?',
          value: $value
        },
        handler: {keypress, input},
      })
    });
  });
};

7.


, effector-dom list.


тАФ , , . list($store, itemCallback) .


.


, todomvc-app-css - , . .


// src/view/main.js
import {h, spec, list} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {TodoItem} from './todoItem';
import {$filteredTodos, allCompleted} from '../model';

export const Main = () => {
  h('section', () => {
    spec({attr: {class: classes.main}});

    //   
    h('input', {
      attr: {id: 'toggle-all', class: classes['toggle-all'], type: 'checkbox'}
    });
    h('label', {attr: {for: 'toggle-all'}, handler: {click: allCompleted}});

    //  
    h('ul', () => {
      spec({attr: {class: classes["todo-list"]}});
      list({
        source: $filteredTodos,
        key: 'title',
        fields: ['title', 'completed']
        //  fields     
      }, ({fields: [title, completed], key}) => TodoItem({title, completed, key})); 
    });
  });
};

8.


, effector-dom - , ..


, toggled removed - тАФ .


effector тАФ event.prepend.


store.map


// src/view/todoItem.js
import {h, spec} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {toggled, removed} from '../model';

// title  completed -    
export const TodoItem = ({title, completed, key}) => {
  h('li', () => {
    //       
    spec({attr: {class: completed.map(flag => flag ? classes.completed : false)}});

    h('div', () => {
      spec({attr: {class: classes.view}});

      h('input', {
        attr: {class: classes.toggle, type: 'checkbox', checked: completed},
        //     
        handler: {click: toggled.prepend(() => key)},
      });

      h('label', {text: title});

      h('button', {
        attr: {class: classes.destroy},
        //     
        handler: {click: removed.prepend(() => key)},
      });
    });
  });
};

9.


,


// src/view/footer.js
import {h, spec} from 'effector-dom';
import classes from 'todomvc-app-css/index.css';
import {$todos, $activeFilter, filtered, completedRemoved} from '../model';

export const Footer = () => {
  h('footer', () => {
    spec({attr: {class: classes['footer']}});

    h('span', () => { //   
      spec({attr: {class: classes['todo-count']}});

      const $activeCount = $todos.map(
        todos => todos.filter(todo => !todo.completed).length
      );

      h('strong', {text: $activeCount});
      h('span', {text: $activeCount.map(count => count === 1
        ? ' item left'
        : ' items left'
      )});
    });

    h('ul', () => { //  ,  
      spec({attr: {class: classes.filters}});

      h('li', () => {
        h('a', {
          attr: {class: $activeFilter.map(active => active === null
            ? classes.selected
            : false
          )},
          text: 'All',
          handler: {click: filtered.prepend(() => null)},
        });
      });

      h('li', () => {
        h('a', {
          attr: {class: $activeFilter.map(completed => completed === false
            ? classes.selected
            : false
          )},
          text: 'Active',
          handler: {click: filtered.prepend(() => false)},  
        });
      });

      h('li', () => {
        h('a', {
          attr: {class: $activeFilter.map(completed => completed === true
            ? classes.selected
            : false
          )},
          text: 'Completed',
          handler: {click: filtered.prepend(() => true)},
        });
      });
    });

    h('button', {
      attr: {class: classes['clear-completed']},
      text: 'Clear completed',
      handler: {click: completedRemoved},
    });
  });
};

10.


рдЗрд╕ рддрд░рд╣ рдХреЗ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЖрд╡реЗрджрди рдирд┐рдХрд▓рд╛, рдЗрд╕реЗ рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдпрдерд╛рд╕рдВрднрд╡ рд╕рд░рд▓ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рдерд╛, рдЬрд┐рд╕рдореЗрдВ рдЕрд▓рдЧ-рдЕрд▓рдЧ рд╕рдВрд╕реНрдерд╛рдУрдВ рдореЗрдВ рдиреНрдпреВрдирддрдо рдЕрд▓рдЧрд╛рд╡ рдерд╛ред


рдмреЗрд╢рдХ, рдпрджрд┐ рд╡рд╛рдВрдЫрд┐рдд рд╣реИ, рддреЛ рдПрдХ рд╣реА рдлрд╝рд┐рд▓реНрдЯрд░ рдмрдЯрди рдХреЛ рдПрдХ рдЕрд▓рдЧ рдЗрдХрд╛рдИ рдореЗрдВ рдирд┐рдХрд╛рд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдлрд╝рд┐рд▓реНрдЯрд░ рдкреНрд░рдХрд╛рд░ рдФрд░ рдирд╛рдо рдХреЛ рд╡реНрдпрдХреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред


const FilterButton = ({filter, text}) => {
  h('li', () => {
    h('a', {
      attr: {class: $activeFilter.map(completed => completed === filter
        ? classes.selected
        : false
      )},
      text: text,
      handler: {click: filtered.prepend(() => filter)},  
    });
  });
};

h('ul', () => { 
  spec({attr: {class: classes.filters}});

  FilterButton({filter: null, text: 'All'});
  FilterButton({filter: false, text: 'Active'});
  FilterButton({filter: true, text: 'Completed'});
});

рдЙрд╕реА рддрд░рд╣, рдЗрд╕рдХреЗ рд╕реНрдЯреИрдХ-рдЖрдзрд╛рд░рд┐рдд рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж, рдкреНрд░рднрд╛рд╡рдХрд╛рд░-рдбреЛрдо рдЖрдкрдХреЛ рд╕реНрд╡рддрдВрддреНрд░ рд░реВрдк рд╕реЗ рди рдХреЗрд╡рд▓ рд╡реНрдпрдХреНрддрд┐рдЧрдд рддрддреНрд╡реЛрдВ рдХреЛ рдкреНрд░рд╕реНрддреБрдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рдмрд▓реНрдХрд┐ рд╕рд╛рдорд╛рдиреНрдп рд╡реНрдпрд╡рд╣рд╛рд░ рднреА рдХрд░рддрд╛ рд╣реИред


рдкреНрд░рд╕реНрддреБрдд рдХреЛрдб рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдЖрд╡рд╢реНрдпрдХ рддрддреНрд╡реЛрдВ рдХреЗ рд▓рд┐рдП рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП:


const WithFocus = () => {
  const focus = createEvent();
  focus.watch(() => console.log('focused'));

  spec({handler: {focus}});
};

h('input', () => {
  ...
  WithFocus();
  ...
});

11. рдХреБрд▓


рдореИрдВ рд╡реНрдпрдХреНрддрд┐рдЧрдд рд░реВрдк рд╕реЗ рдирдП рд░реЗрдВрдбрд░ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреА рд╕рд╛рджрдЧреА рд╕реЗ рдкреНрд░рднрд╛рд╡рд┐рдд рдерд╛, рдЦрд╛рд╕рдХрд░ рдЬрдм рд╕реЗ рдореБрдЭреЗ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рдмрдбрд╝реЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рдкреНрд░рднрд╛рд╡рдХрд╛рд░реА рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХрд╛ рдЕрдиреБрднрд╡ рдерд╛ред


рдореИрдВ рдПрдХ рд╕реНрдерд┐рд░ рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП рддрддреНрдкрд░ рд╣реВрдВ, рдареЗрд╕ рдореЗрдВ рд░реЗрдВрдбрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рдЕрд╡рд╕рд░ рдХреЗ рд▓рд┐рдП рдХрд┐рд╕реА рдкреНрд░рдХрд╛рд░ рдХрд╛ рд▓реЛрдХрдкреНрд░рд┐рдпрдХрд░рдгред

Source: https://habr.com/ru/post/undefined/


All Articles