Не надо заставлять

В эфире рубрика «Лайфаки от капитана Очевидности». Рассказываю, чё делать, если не получается себя заставить чё‑то делать.

— Когда не получается заставить себя чё‑то сделать, не надо заставлять себя это делать.
— Спасибо, Кэп!

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

На примере, пожалуйста

Кулстори из прошлого. Когда‑то давно, ещё до того, как я придумал себе нормальную систему деления рабочего времени, я не мог заставить себя делать перерывы.

Пробовал помидорки, но они не работали. Я откладывал отдых, забивал на перерывы и обнаруживал себя проработавшим 14 часов подряд. А потом пытался понять, чо ж было не так.

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

Появилась идея, что неплохо бы отдать контроль за выполнением уговора куда‑то вовне. Поставил себе программу, которая блокирует компьютер через определённые интервалы времени. Она предупреждала за 5 минут, а потом блокировала всё нафиг: останавливалась музыка, гас монитор, компьютер переставал реагировать на клавиатуру и мышь.

Мне ничего больше не оставалось, как встать из‑за стола и поделать что‑то ещё, потому что иначе — скучно. То есть я не мог не выполнить уговор с собой, потому что не было возможности не выполнить его :–)

Радикально как‑то

Ну да! Но такой способ даёт понять, действительно ли мне так важна эта хрень, которую я не могу заставить себя сделать. Если нет, то ну и зачем она мне?

Если всё же важна, то есть возможность подумать, какой приоритет у неё относительно других задач. Оборванный скайп, например, мог бы заставить меня задуматься, действительно ли профиты от регулярных перерывов выше, чем минусы от резкой блокировки компьютера.

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

Другие о том же

От Редакса к хукам

В Реакте в версии 16.7.0 появились Hooks (дальше по тексту — хуки). Это API, которое позволяет использовать локальный стейт без использования классов. И среди них есть один, который, как мне кажется, может заменить собой Редакс.

В этой статье я предполагаю, что вы знаете разницу между функциональными компонентами и классами, в курсе о локальном стейте и жизненном цикле компонентов и о том, как работает Редакс. Без этого вникнуть будет трудно, но всё в одну статью я бы не уместил, так что вот ¯\_(ツ)_/¯

Что за хуки?

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

Например, здесь мы используем хук useState, чтобы создать и использовать переменную counter:

import React, {useState} from 'react'
const SimpleComponent = () => {
  const [counter, setCounter] = useState(0)
  // в первый раз значение counter будет равно тому, что мы передаём в useState
  // затем — тому, что мы установим через setCounter
  return <div>{counter}</div>
}

useState возвращает кортеж из значения и функции, которая будет это значение обновлять.

Из‑за того, что в одном компоненте можно использовать несколько хуков по несколько раз, у них есть ограничения и правила. Подробнее об этом написано в документации и рассказано в докладе на Реакт‑конфе.

useReducer

useReducer — это хук, который по принципу работы схож с редьюсерами из Редакса.

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState)
  // initialState — начальное состояние
  // reducer — функция, которая принимает state и action 
  //           и обрабатывает изменение состояния
  //
  // state — текущее состояние
  // dispatch — функция, которая будет дёргать экшены, 
  //            чтобы обновить состояние

  return <div>Hello world</div>
}

По принципу работы это и есть Редакс. Проблема только в том, что переменные state и dispatch находятся внутри области видимости функции App, а значит использовать этот редьюсер в других компонентах у нас не получится.

…Если только мы не используем контекст.

Context API

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

Это работает, но если дерево большое, то и пробрасывать пропсы приходится через каждый уровень вложенности. Возникает так называемый prop drilling. Схематично это выглядит, как на картинке слева:

Редакс помимо работы с состоянием решал и эту проблему тоже. С ним мы создаём лишь одно хранилище, к которому может обращаться любой компонент. Таким образом нам не требуется пробрасывать свойства через всё дерево.

Ту же проблему решает и контекст в Реакте. Мы создаём контекст, через провайдер указываем, что именно нужно хранить и передавать, а через консьюмер — в каком компоненте эти значения забирать и использовать:

import {createContext} from 'react'
// создаём контекст
const StoreContext = createContext()

const App = () => (
  // через провайдер в свойстве value указываем значение,
  // которое нам надо хранить и как-то использовать в других компонентах
  <StoreContext.Provider value={{meaningOfLife: 42}}>
    <OtherComponent />
  </StoreContext.Provider>
)

const OtherComponent = () => {
  <StoreContext.Consumer>
    // через консьюмер получаем доступ к значению
    {({meaningOfLife}) => (
      <div>{meaningOfLife}</div>
    )}
  </StoreContext.Consumer>
}

Вызывать консьюмер можно где угодно, и это позволяет делить состояние между компонентами. И тут возникает мысль, нельзя ли заменить Редакс на смесь хуков и контекста. Ну и эт, вроде, можно.

Пример

Я написал простенькое приложение с использованием Редакса и с использованием контекста и хуков. Это счётчик, значение которого можно менять кнопками, либо меняя значение в инпуте, а также обнулять его нажатием на кнопку из другого компонента.

По структуре оно будет состоять из корневого компонента App, компонента формы Form и ещё одного компонента с кнопкой внизу Display. Схематично можно изобразить так:

Компоненты Form и Display зависят от хранилища, в котором содержится состояние приложения. Все события кнопок и инпута вызывают экшены, которые будут обновлять хранилище.

С использованием Редакса

Вначале создадим хранилище и редьюсер, который будет заниматься его обновлением:

// reducers.js
import {combineReducers} from 'redux'

const app = (state, action) => {
  switch(action.type) {
    case 'PLUS':
      return {...state, counter: state.counter + 1}

    case 'MINUS':
      return {...state, counter: state.counter - 1}

    case 'MAGIC':
      return {...state, counter: Math.floor(Math.random() * 100)}

    case 'CHANGE':
      return {...state, counter: +action.value}

    case 'RESET':
      return {...initialState}

    default:
      return state
  }
}

export default combineReducers({ app })

// index.js
import {createStore} from 'redux'
import rootReducer from './reducers'
import App from './App'

// создаём хранилище
const store = createStore(rootReducer)

// используем через провайдер
render(
  <Provider store={store}><App /></Provider>,
  document.getElementById('app'))

Дальше создадим экшены, которые будут вызываться событиями кнопок и инпута:

// actions.js
export const plus = () => ({ type: 'PLUS' })
export const minus = () => ({ type: 'MINUS' })
export const magic = () => ({ type: 'MAGIC' })
export const reset = () => ({ type: 'RESET' })
export const change = e => ({ 
  value: e.target.value,
  type: 'CHANGE', 
})

Чтобы привязать какой‑то компонент к хранилищу, используем connect:

import {connect} from 'react-redux'
import {reset} from './actions'

// app — часть хранилища;
// reset — экшен;
// всё это мы привязали через connect перед экспортом ниже
const Display = ({app, reset}) => {
  const {counter} = app

  return <footer>
    <p>Another component knows that counter equals to {counter} as well!</p>
    <p>
      It even can 
      <button onClick={reset}>reset the coutner</button>
    </p>
  </footer>
}

// мапим свойства из хранилища и экшены
// на пропсы компонента
export default connect(
  state => ({ app: state.app }),
  {reset}
)(Display)

В результате приложение будет работать так.

Контекст + хуки

Теперь напишем то же самое без использования Редакса. Используем useReducer:

// store.js
export const initialState = {counter: 0}

// редьюсер точно такой же, как в прошлый раз 
export const reducer = (state, action) => {
  switch(action.type) {
    // ...
  }
}

// index.js
import {reducer, initialState} from './store'

const App = () => {
  // создаём в корневом компоненте хранилище
  // и функцию для его обновления
  const [state, dispatch] = useReducer(reducer, initialState)
  return <div></div>
}

Чтобы пробросить значения из хранилища компонентам, воспользуемся контекстом:

// context.js
import {createContext} from 'react'
export const StoreContext = createContext()

// index.js
import {reducer, initialState} from './store'
// импортируем созданный контекст
import {StoreContext} from './context'

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  // используем провайдер, чтобы передать в контекст 
  // хранилище и функцию для обновления
  return (
    <StoreContext.Provider value={{dispatch, state}}>
      <Form />
      <Display />
    </StoreContext.Provider>
  )
}

export default App

Чтобы привязать какой‑то компонент, используем консьюмер:

import React from 'react'
// импортируем контекст 
import {StoreContext} from './context'
// экшены точно такие же, как в прошлый раз
import {reset} from './actions'

const Display = () => (
  // получаем доступ к тому, что хранится в контексте
  <StoreContext.Consumer>
    // в нашем случае — state и dispatch
    {({state, dispatch}) => (
      <footer>
        // используем state, чтобы вывести значение счётчика
        <p>{state.counter}</p>
        // используем dispatch, чтобы дёрнуть экшен
        <button onClick={() => dispatch(reset())}>reset</button>
      </footer>
    )}
  </StoreContext.Consumer>
)

export default Display

А ещё можно сделать код чище, заменив консьюмер на useContext:

import React, {useContext} from 'react'
import {StoreContext} from './context'
import {reset} from './actions'

const Display = () => {
  // вызываем useContext, передавая аргументом нужный контекст
  const {state, dispatch} = useContext(StoreContext)

  return (
    // убираем консьюмер
    <footer>
      <p>{state.counter}</p>
      <button onClick={() => dispatch(reset())}>reset</button>
    </footer>
  )
}

export default Display

И работает оно точно так же.

А чо по весу и перформансу?

Я не удивился, когда бандл ужался на 12 кБ: с Редаксом — 166, без него — 154. Это логично, меньше зависимостей — меньше вес.

А вот прирост в скорости обработки экшенов и отрисовки меня слегка удивил. Я проводил измерения с помощью console.time и performance.measure. Средние значения за 100 итераций вышли такими:

 console.timeperformance.measure
Redux12 мс13 мс
Context + hooks9 мс8 мс

Минусы

Вызов экшенов стал чуть более многословным из‑за прямого использования dispatch. И если работать с контекстом без useContext, придётся использовать паттерн render‑prop, из‑за чего может подняться щит‑сторм ¯\_(ツ)_/¯

Но если серьёзно, хуки — пока что в стадии RFC, и возможно многое поменяется. Поэтому переписывать свои приложения на них не советует даже Дэн Абрамов. То есть это экспериментальная фигня.

Хотя выглядит всё равно заманчиво :–)

Ссылки

Документация

Доклады

Статьи со сравнениями

Измерения перформанса

Исходники и примеры

Новые правила деловой переписки. М. Ильяхов, Л. Сарычева

Эта книга не о словах и правилах, а об отношениях между людьми.

Да, в ней есть список фраз, которые в 2018 году режут людям слух, но нет рецептов типа «используй вот этот шаблон, и все клиенты твои». Ну ладно, по порядку.

Основная мысль

Если относиться к адресату уважительно, то и в ответ к вам будут относиться так же.

Книга не про слова, а про правильное отношение к людям

Переписка — это инструмент, им надо уметь пользоваться. Люди — не инструмент, ими пользоваться не надо, им надо помогать. Собеседник будет охотнее отвечать вам, если ему будет приятно читать письмо.

У нас есть цель: получить совет, назначить встречу, договориться о сотрудничестве. Добиться этой цели проще, если получателю будет приятно читать письмо

Так что можно сказать, что книга — о том, как сделать письмо приятным для читателя.

Об уважении к чужому времени

Целая глава посвящена письмам о срочных задачах, и том как о них пишут: «АСАП», «срочно», «нужно вчера» и вот это всё. Людям не нравится получать письма с такими пометками. Но дело не в самих словах, а в скрытом в них отношении к чужому времени.

Раздражает не слово «срочно», раздражает неуважение. …когда к твоему времени относятся как к расходному материалу. Раздражает, что менеджер может подгонять не из‑за срочности, а просто ради забавы… Всё это — неуважение, и всё это раздражает

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

Например, можно вынести ключевые данные из приложенного файла прямо в текст письма. Тогда, возможно, открывать файл уже не потребуется, человек ответит быстрее. Разделить текст на абзацы, выделить и пронумеровать вопросы, объяснить, зачем переходить по ссылкам — всё это сделает письмо более читаемым.

Чем больше работы мы проделаем за адресата, тем легче ему ответить, тем приятнее с нами сотрудничать

Не надо рассчитывать на почту, как на экстренный способ связи.

Всё, что в почте, по умолчанию не «горит». Если что‑то и правда «горит», оно не должно быть в почте

Об эмоциях и агрессии в переписке

Письма на эмоциях писать можно, нельзя — отправлять. Эмоциональное письмо может привести к необоснованному конфликту. Если вы сильно переживаете из‑за какой‑то проблемы, позвоните человеку или напишите в чат.

Как определить, что вы сейчас на эмоциях: если вам хочется показать, что вы спокойны и рассудительны — вы не спокойны и не рассудительны. Отправлять письмо в таком состоянии нельзя

В книге схемы «Когда писать письмо» нет, но её можно найти в лекции на ютубе: Когда писать и когда не писать письмо

В работе редко кто‑то вредит специально. Если кажется, что кто‑то ведет себя нехорошо, скорее всего…

Он не козёл, он просто не знает, как правильно

О праве на отказ

Да‑да, пошла песня про Кэмпа, но камон. Люди любят делать добрые дела и помогать другим людям. Мы социальны, и ощущение, что я помог кому‑то приносит удовольствие, а…

Когда мы даём человеку право на отказ, у него появляется больше поводов нам помочь

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

Об удобстве читателя

Быть вежливым — это не про слова, а про намерения. Сделать удобно получателю — значит проделать за него часть работы, снять часть нагрузки.

Это вообще общий принцип любой переписки: когда один человек думает об интересах и удобстве другого. Само по себе механическое повторение формул не помогает — нужно думать, будет ли это удобно нашему конкретному читателю

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

Принципы‑шмынципы. Ответ однозначный: надо делать так, чтобы было приятно и удобно читателю, научному руководителю. Нечего тут рассуждать

О внимательности, границах, переходе на «ты»

О небрежном отношении к имени знаю из опыта — меня постоянно называют Алексеем, не надо так.

Если бы мы могли оставить в этой книге только одно правило, мы бы оставили вот это: внимательно с именем

Переходить на «ты» лучше при личной встрече. Главное, чтобы в переходе не было напряжения, иначе переход не нужен.

При общении описывать свои эмоции и ощущения — нормально. Опасно — описывать других людей, давать непрошеные советы, оценивать работы.

Гораздо безопаснее писать о том, что мы сами думаем и чувствуем, — а не лезть со своими оценками в душу другому

О манипуляциях и ситуациях «из ряда вон»

Самые неприятные письма — лицемерные и манипулятивные. Люди всегда видят, когда их пытаются обмануть или пытаются ими манипулировать. Им это не нравится. Если ситуация нестандартная, то надо это признать. Свои косяки тоже надо уметь признавать.

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

Лучше прямо сказать: «Я понимаю, что ситуация дурная». И дальше строить аргументацию, исходя из этого

Манипуляция — это когда человек втайне хочет чего‑то одного, а давит на оппонента в другом месте, чтобы тот сам сделал нужное

Об этике переписки

Не писать ничего, что нельзя переслать. В претензиях не переходить на личности и оценивать не человека, а работу и результат.

Не писать ничего, что нельзя переслать. Рабочая переписка не подходит для обсуждений за глаза. Всегда есть риск, что письмо получит не тот, кому оно предназначалось

Все претензии должны быть сделаны без переходов на личности, а оценить можно только работу, а не человека или его профессиональные качества

Об откликах на вакансии

Здесь всё снова упирается в человеческие отношения. Если письмо шаблонное и пытается просто закрыть вакансию, то работодатель скорее всего в нём пользы не увидит и не ответит.

У сопроводительного письма нет обязательной формы. Нет требований по формату и объему; необязательно пересказывать биографию… говорить о своей стрессоустойчивости, коммуникабельности, самообучаемости. Относитесь к этому письму не как к священному ритуалу, а как к общению человека с человеком. У одного проблема, у другого — решение. Они хотят договориться

Об ответах на хамские письма

Тут так же: быть на равных, не переходить на личности, не нарушать границ, проявлять искреннюю заботу.

Главный секрет хорошего ответа на хамскую и неоправданную претензию — это уважение. Оно подразумевает не делать трех вещей: не хамить в ответ, не учить жизни и не нянчиться

Иногда принципы вашей компании могут не подходить кому‑то, это нормально. Это нужно объяснить, но не учить жизни или пытаться переубедить кого‑то. Это гиблое дело, ни к чему хорошему не приведёт.

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

О шаге навстречу

Книга предлагает в любой ситуации делать шаг навстречу человеку, с которым вы переписываетесь. По‑моему, это вообще универсальный принцип.

Вы наверняка заметили: почти в любом конфликте побеждает тот, кто первым делает шаг навстречу. Если человек на нас сердит, мы должны быть первыми, кто скажет ему: «Дружище, мы тебе не враги, а друзья. Чем тебе помочь? Как сделать тебе хорошо?» Это работает везде, во всех отношениях — хоть в семье, хоть на работе, хоть с боссом, хоть с клиентом. Побеждает тот, кто делает первый шаг навстречу

Ссылки

К этой книге:

Мимо пролетало:

«Кликни меня!» на RxJS

На прошлых выходных мне надо было поковырять RxJS, и я попробовал написать игру с кнопкой, которая убегает из‑под курсора. В этом посте расскажу, что и как именно я использовал при написании и разберу по шагам процесс.

Если вы уже работали с этой технологией, скорее всего, ничего нового не узнаете. Пост ориентирован на таких как я — которым из RxJS знаком только JS.

Игру будем писать на Тайпскрипте. Отличий от обычного JS будет немного, но всё же стоит глянуть документацию по нему, чтобы знать, как объявляются типы переменных и возвращаемых из функции значений.

Почему Тайпскрипт

Мне давно хотелось его распробовать, а RxJS написан как раз на нём. Я подумал, почему бы не добавить себе ограничений и головной боли, ну и вот ¯\_(ツ)_/¯

Что такое RxJS

RxJS — это имплементация ReactiveX для JS.
ReactiveX в свою очередь, если верить главной их сайта, — это АПИ для асинхронной работы с наблюдаемыми потоками. Я тож не сразу въехал, будем разбираться по порядку.

Если выражаться примитивно, поток — это последовательность чего‑то: событий, данных, преобразований и т. д. Представьте себе чатик, в котором вы с кем‑то переписываетесь. Последовательность сообщений в нём — это и есть поток.

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

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

Наблюдатель и наблюдаемый

ReactiveX в основе использует шаблон «Наблюдатель». Два основных понятия, которые нам понадобятся — это observer (наблюдатель) и observable (наблюдаемый).

Наблюдаемым будет поток: он отправляет элементы из какого‑то источника по одному за раз. Это можно представить как ручей, по которому плывут кораблики‑элементы.

Наблюдатель — это объект, который знает, что с элементами из потока надо делать и как их обработать. Это можно представить как ребёнка, который хочет кораблики собрать и унести домой.

Наблюдатель связан с наблюдаемым через подписку (subscribe) — функцию, которая передаёт элементы от наблюдаемого наблюдателю. Это похоже на сетку, которая ловит проплывающие кораблики. Когда новый кораблик попадает в сетку, ребёнок его замечает и может забрать себе.

Наблюдаемый поток знает, как сообщить:

  • что появился новый элемент;
  • произошла ошибка;
  • элементы закончились.

На всё это наблюдатель может как‑то реагировать.

Диаграммы

Чтобы понять концепцию потоков лучше, документация к RxJS предлагает так называемые marble diagrams. На них изображены шарики, которые как бы нанизаны на нитку.

Эти шарики — это элементы в потоке. Нитка — это линия времени, направленная слева направо. Если элемент стоит левее, значит он появился раньше.

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

Применимо к игре

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

Мы будем отслеживать движение мыши и проверять, где находится курсор. Если он находится в пределах 15 пикселей от кнопки, то будем перерисовывать кнопку.

Если чуть ближе к коду, то у нас будет поток из событий перемещения мыши. Мы их будем чистить и оставлять только координаты {x, y}. Затем будем фильтровать координаты, проверяя находится ли курсор достаточно близко от кнопки:

Начинаем пилить

В RxJS поток можно сделать из чего угодно: из массива, промиса, событий в браузере. Например, из массива его можно сделать с помощью оператора from:

import {from} from 'rxjs'

// выталкивает по одному элементу из массива, пока они не закончатся
const arraySource = from([1, 2, 3, 4, 5])

Источником для нашего потока будет событие движения мыши по экрану. Чтобы создать источник из браузерного события, мы будем использовать fromEvent:

import {fromEvent} from 'rxjs/observable/fromEvent'

const source = fromEvent(document, 'mousemove')

Теперь браузерное событие mousemove будет отслеживаться в пределах document, и на каждое перемещение будет появляться новый элемент в source.

Эти элементы мы будем преобразовывать и фильтровать. После каждого преобразования мы будем получать новый observable с элементами, с которыми опять сможем как‑то взаимодействовать.

Операторы

Операторы — это функции, которыми можно преобразовывать элементы после того, как observable их отправил.

Чтобы применить несколько преобразований по очереди, нам потребуется pipe. Это метод, который занимается композицией операторов, то есть применяет их по порядку.

import {map, filter} from 'rxjs/operators'

// ...

const observable = source.pipe(
  map(...),
  filter(...)
)

Оператор map нам нужен, чтобы применить к каждому элементу какую‑то функцию.

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

map((event: MouseEvent): MouseCoords => ({ x: event.x, y: event.y }))

MouseCoords — это тип данных, который мы создадим для работы с координатами. Он будет представлять из себя объект с полями {x, y}. Создавать новый тип необязательно, но так понятнее, с чем мы работаем.

type MouseCoords = {
  x: number,
  y: number,
}

// ...

map((event: MouseEvent): MouseCoords => ({ x: event.x, y: event.y }))

Оператор filter будет выбирать события, которые нам подходят.

Событие нам подходит, если курсор находится в пределах 15 пикселей от кнопки по обеим осям.

const shouldUpdateApp = ({x, y}: MouseCoords): boolean => {
  const {top, left, widthRange, heightRange} = state.get()
  const padding = 15

  return inRange(x, left - padding, widthRange + padding) 
      && inRange(y, top - padding, heightRange + padding)
}

// ...

filter(shouldUpdateApp)

И тогда код observable будет выглядеть так:

const source = fromEvent(document, 'mousemove')

const observable = source.pipe(
  map((event: MouseEvent): MouseCoords => 
    ({ x: event.x, y: event.y })),
  filter(shouldUpdateApp))

Подписка на события

Каждый элемент в потоке стремится попасть в subscribe, где он будет как‑то обработан.

Метод subscribe принимает три функции‑аргумента. Первая функция обрабатывает новые элементы, вторая — ошибку, если она возникнет, третья — окончание потока:

observable.subscribe(
  // onNext, вызывается при появлении новых элементов, el — новый элемент
  (el) => {},

  // onError, вызывается, если произошла ошибка, er — объект ошибки
  (er) => {},

  // onCompleted, вызывается, когда поток завершён
  () => {},
)

При появлении нового элемента мы будем вызывать функцию updateApp, которая будет генерировать случайные координаты для кнопки, обновлять состояние приложения и перерисовывать кнопку:

observable.subscribe(() => updateApp())

const updateApp = () => {
  const {left, top} = getNewPosition()
  state.update({ left, top })

  applyStyle(button, {
    left: `${left}px`,
    top: `${top}px`,
  })
}

Результаты

Я не буду подробно останавливаться на классе, который управляет состоянием приложения, и функциях‑хелперах. Исходный код всего‑всего можно посмотреть на Гитхабе.

Сама игрушка получилась очень простой, хотя для знакомства с RxJS вполне ок.

Конечно, там ещё куча всякого, о чём я не рассказал: создание потоков из промисов, Subject, Scheduler, куча операторов, работу которых иногда без специального сервиса не разберёшь. Но для начала — сойдёт.

Ссылки

Сделяль

Шаблон «Наблюдатель» и ФРП

Документация RxJS

Операторы

Книги и сервисы

Об эффективном делении рабочего времени

Последние пару месяцев я перестраиваю свои рабочие процессы, чтобы сделать их менее напряжными. Одна из штук, которые помогают мне меньше уставать, — разделение рабочего времени на куски.

Зачем делить день на куски

У меня большая часть проблем с концентрацией и вниманием возникает, когда я занимаюсь каким‑то делом несколько часов подряд без отдыха.

Внимание — ценный ресурс, который необходимо восстанавливать. Я привык думать о внимании, как о ведре с дождевой водой, которое постепенно наполняется. Из него можно зачерпывать воду, но в какой‑то момент вода заканчивается, и сколько ни пытайся, больше воды оттуда зачерпнуть не получится. Надо ждать, пока ведро наполнится.

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

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

Как бы научная гипотеза, почему так

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

В университетах на БЖД учат что работоспособность зависит от длительности работы. Как видно из графика, работоспособность со временем падает, а после отдыха слегка возрастает.

Во время отдыха работоспособность восстанавливается. На этом основан метод помидорок: в нём работа делится на ритмичные отрезки, которые чередуются с отдыхом.

Также по закону Стивенса интенсивность ощущения зависит от интенсивности раздражителя. Если за раздражитель принять монотонность работы, то чем длиннее рабочий отрезок, тем интенсивнее раздражитель. Следовательно его воздействие растёт по степенной функции — быстро то есть.

Из всего этого я сделал дерзкий (ненаучный!) вывод, что если давать организму часто отдыхать и восстанавливаться, то интенсивность ощущения от монотонности — усталость — должна снизиться.

Подтверждения я нашёл не только в лекциях по БЖД, но ещё и в рассылке Найсэндизи. Меня убедило.

Как делю день я

Рабочий день я делю на блоки по 3–4 часа. Блоков может быть 2, 3 или больше — зависит от загрузки. Если их больше 2, то я снижаю продолжительность последних блоков на час‑полтора.

Между большими блоками я делаю длинные перерывы: ем, меняю место, откуда работаю, просто прогуливаюсь. Обычно хватает 30–60 минут.

Каждый блок я делю на периоды по часу. Периодами удобно измерять задачи и сверяться с планом. В одном блоке получается 2–4 периода, зависит от размера блока.

Каждый период я делю на отрезки по 20–30 минут. Отрезок включает в себя работу и перерыв. Например, отрезок в 30 минут я делю на 20–25 минут работы и 5–10 минут отдыха. Это похоже на помидорки, только я таймер не ставлю.

Иногда я всё‑таки забываюсь, и несколько отрезков сливаются в один без отдыха, тогда в конце периода я отдыхаю дольше.

Минусы

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

Иногда нужно намеренно останавливать работу. Бывает, вхожу в раж и фигачу код как не в себя. Блок заканчивается, а я «хм, может продолжить? прёт же». В это время я себя останавливаю, потому что:

  • я всё равно уже устал, хотя могу этого не замечать;
  • надо освежить голову, потому что решение может оказаться чересчур сложным или запутанным, а для замыленного взгляда оно будет казаться нормальным.

Ещё одно ограничение в копилку, оно может как помогать, так и расстраивать. Например, расстраивает, если задача сложная, и её не получается решить за отведённое мной же самим время.

Плюсы

Всегда свежий взгляд на задачу. Мышление не скатывается в привычные шаблоны из‑за усталости; становится видно больше скрытых связей между компонентами системы, которую пишу.

Ведро концентрации наполняется быстрее. Меньше отвлекаюсь во время работы на твитер, чувствую себя менее уставшим после рабочего дня.

Удобно делить день и измерять время, потраченное на задачи. А если запомнить, какая задача сколько заняла времени, получается точнее прогнозировать сроки.

Пропадает ощущение, что надо сделать слишком много. Я знаю, что блок закончится через 4 часа, и я пойду гулять. Да, потом возможно будет ещё один блок, но это всё равно не так страшно, как фигачить 12 часов подряд.

Ссылки и материалы

Книги:

Лекции и Википедия:

Рассылки, каналы, инструменты:

Раньше ↓