Нажми пробел чтобы продолжить
Стрелка вправо — для быстрой промотки
function* runAndWait() {
console.log('Готов работать. Жду подтверждения')
let startCommand = yield
console.log('Начинаю работать работу...')
// TODO: сделать что-то мега полезное
console.log('Выполнено. Жду подтверждения...')
let nextCommand = yield workResult
// TODO: продолжить делать что-то полезное
// ...
}
отобразить прелоадер
загрузить заказ с сервера
показать форму редактирования заказа
дождаться ввода пользователя
отправить данные на сервер
отобразить ошибки
repeat until
// 1. отобразим прелоадер
riot.mount('#container', 'preloader-page', {
msg: 'Загружаем заказ'
})
// 2. загрузим заказ с сервера
let response = yield fetch(`/order/${id}`)
let order = yield response.json()
// создадим канал для событий - действий пользователя
let pageChannel = channel()
let store = createStore(reduceFormErrors)
// 3. отобразим страницу с заказом
riot.mount('#container', 'order-page', {
pageChannel,
order
})
<!-- Тег Riot.js, живет в отдельном файле -->
<order-page>
<h1>Заказ #{opts.order.id}</h1>
<form onsubmit={onSubmit}>
<!-- TODO здесь разместить поля HTML формы -->
<button type="submit">Отправить</button>
</form>
</order-page>
<script type="text/javascript">
this.onSubmit = event => {
let formData = jQuery(event.target).serializeArray()
opts.pageChannel.put(formData)
}
</script>
while(true){
// 4. дождемся ввода пользователя
let formData = yield take(pageChannel)
// 5. отправим данные на сервер
let response = yield fetch(`/order/${id}`, {
method: 'POST',
body: JSON.stringify(formData),
})
let errors = yield response.json()
// если все ОК - переправим на новую страницу
if (!errors.length)
return window.location = `/order/${id}/success`
// 6. отобразим ошибки
store.dispatch({
type: 'ERRORS_OCCURRED',
errors
})
}
45 разных страниц
Многовато... React нас спасёт!
Будет жестокая битва за конверсию
Придется строить
много велосипедов...
Еще тут сложная бизнес-логика
What?!
И нужно сделать несколько интерфейсов
А после возьмемся за приложение для мастеров
И оно в 3 раза больше
View НЕ знает о store.dispatch
View НЕ знает о сагах
View читает из store и пускает события в channel
Один глобальный store
Локальный store для каждой страницы
ошибки заполнения форм
прелоадеры
...
OrderNotFoundError
Redirect2URL
RequestToAPIError
ServerError
....
import runOrderPage from './pages/order'
// ... TODO: импортировать другие страницы/саги
// URL схема приложения
const routes = [
['/order/*', runOrderPage],
// ... TODO: дописать другие URLs и саги для них
]
function* router(routes){
// запускается единожды при инициализации приложения
let routerChannel = channel()
// настраиваем riot.router
function registerRoute([urlPattern, saga]){
riot.route(urlPattern, ()=>routerChannel.put(saga))
}
_.forEach(routes, registerRoute)
// при каждом событии в routerChannel
// останавливаем предыдущую сагу и запускаем новую
yield* takeLatest(routerChannel, function*(saga){
yield* saga()
})
}
// останавливаем предыдущую сагу и запускаем новую
yield* takeLatest(routerChannel, function*(saga){
try {
yield* saga()
} catch (error){
// проглатываем исключение чтобы
// не порушить все приложение
Raven.captureException(error)
console.error(error)
}
})
const PRELOADER_INITIAL_STATE = {
isRunning: false,
}
function reduce(state=PRELOADER_INITIAL_STATE, action){
switch (action.type) {
case 'TASK_STOPPED':
return { isRunning: false }
default:
return state
}
}
case 'TASK_STARTED':
return { isRunning: true }
function* runOrderPage(store){
riot.mount('#container', 'order-page')
store.dispatch({type: 'TASK_STARTED'})
let response = yield fetch('/api/order/${id}')
// TODO: обработать ответ сервера и возможные ошибки
store.dispatch({type: 'TASK_STOPPED'})
}
// ...
try {
let response = yield fetch('/api/order/${id}')
// TODO: обработать ответ сервера и возможные ошибки
} finally {
store.dispatch({type: 'TASK_STOPPED'})
}
// ...
const PreloaderWrapper = store => saga => function*(...args){
store.dispatch({type: 'TASK_STARTED'})
try {
return yield* saga(...args)
finally {
store.dispatch({type: 'TASK_STOPPED'})
}
}
function* runOrderPage(store){
riot.mount('#container', 'order-page')
yield* PreloaderWrapper(store)(function*(){
let response = yield fetch('/api/order/${id}')
// TODO: обработать ответ сервера и возможные ошибки
})()
}
const fetchOrder = decorate(
PreloaderWrapper(store),
function*(id){
let response = yield fetch('/api/order/${id}')
// TODO: обработать ответ сервера и возможные ошибки
return responseData
})
)
function* runOrderPage(store){
riot.mount('#container', 'order-page')
let order = yield* fetchOrder(id)
}
const routesMiddlewares = [
showPlugIfFailure,
// перехватывает все ошибки,
// отображает страницу заглушку и стучит в Sentry.io
redirectsHandler.handler,
// ловит RedirectException, меняет window.location
handler404.handler,
// ловит Order404Exception и пр.
// отображает страницу заглушку
handleAPIAuthErrors.handler,
// перенаправляет на /signin
]
App structure
Model
reducer
selectors
View
React.js
Riot.js
Vue.js
Controller:
decorators
middlewares
page saga
Apps
forms
config
preloader
tracker
auth
orders
...
CTO веб-сервиса МойМеханик.рф
email pelid80@gmail.com
skype evseev.evgeny
Код из доклада есть в репозитории на GitHub