
Meu nome é Vitaliy Rizo, sou desenvolvedor sênior de front-end na Amplifer. Compartilharei como usamos o Logux em um aplicativo da Web: organizamos trocas de dados em tempo real, notificações de erro sem recarregar a página, comunicação entre as guias do navegador e integração com o Redux.
O Amplifer é um serviço para publicação em redes sociais. Era necessário notificar os usuários de maneira rápida e confiável, sem recarregar a página: se de repente não fosse possível processar a imagem, a API do VKontakte caiu ou o Facebook decidiu novamente não publicar. Olhando para o futuro, direi que também planejamos usar o Logux para discutir publicações e reescrever o pop-up de postagem cruzada do RSS. Mas primeiro, por que não ficamos satisfeitos com as soluções disponíveis.
Soluções possíveis: WebSocket, Firebase e Swarm.js
Normalmente, o WebSocket é usado para implementar informações de atualização automática. Com isso, você não precisa enviar solicitações de informações a cada segundo, como faria com as solicitações HTTP tradicionais, e o tamanho do cabeçalho será pequeno. No entanto, o WebSocket tem desvantagens:
- . , , , . , : , ;
- , . ;
- , , ;
- , (CRDT), .
Firebase, , — . Firebase CRDT, Redux . CRDT Swarm.js, Redux , .
Logux
, — Logux. , Redux, CRDT . - , API-: , .
, , « ». :
imports-api.js
update (project, id, data) {
return put(project, `settings/imports/${ id }`, convert(data))
}
imports.js
onUpdate (projectId, importId, changed) {
return dispatch({
projectId,
importId,
import: changed,
type: 'amplifr/imports/update'
})
}
:
imports.js
let dispatch = useDispatch()
let onUpdate = (projectId, importId, changed) => dispatch.sync({
projectId,
importId,
import: changed,
type: 'amplifr/imports/update'
}, { channels: ['imports/' + projectId] })
sync
dispatch
, , , . , Logux .
Logux

— . , . , .
, Logux-. .
Logux Redux. Redux createStore
. Logux, «» createLoguxCreator
:
import { createLoguxCreator } from '@logux/redux'
let createStore = createLoguxCreator({
credentials: loguxToken,
subprotocol: '0.6.5',
userId,
server: 'wss://logux.amplifr.com'
})
let store = createStore(reducers)
store.client.start()
createLoguxCreator
:
- . , . gon;
- Logux-;
- . , , .
Logux- , . , - posts/1
, . , WebSocket.
? — , , posts/1
. subscribe
— :
import useSubscription from '@logux/redux/use-subscription'
let Notices = props => {
let isSubscribing = useSubscription([`projects/${ props.id }`])
if (isSubscribing) {
return <Loader />
} else {
}
}
— , HTTP- Redux- Logux, Logux- :
def schedule_logux
LoguxNotificationWorker.perform_async(
{ type: 'amplifr/notices/add', notice: Logux::NoticePresenter.new(notice).to_json },
channels: ["projects/#{project.id}"]
)
end
, . - , , . , :
import { useDispatch } from 'react-redux'
let dispatch = useDispatch()
let onNoticeHide = noticeId => dispatch.sync({
type: 'amplifr/notices/hide',
noticeId
}, {
channels: [`projects/${ projectId }`]
})
, , Logux:

-
Logux , . , - , . , . , , , .
Logux . «» , , localStorage. , Firefox Safari localStorage! Logux , , . , .
, , Logux , , . , , .
, Logux, .
Logux

, Logux, . — , :
let PostEditor = { isApprovable, postId, … } => {
let isSubscribing = useSubscription(isApprovable ? [`posts/${ postId }`] : [])
if (isSubscribing) {
return <Loader />
} else {
}
}
notes
:
import { useDispatch, useSelector } from 'react-redux'
let dispatch = useDispatch()
let notes = useSelector(notes.filter(note => note.postId === postId))
let onNoteAdd = note => dispatch.sync({
type: 'amplifr/notes/add',
note
}, {
channels: [`posts/${ postId }`]
})
. , :
export default function notes (state = [], action) {
if (action.type === 'amplifr/notes/add') {
return state.concat([action.note])
} else if (action.type === 'amplifr/posts/close') {
return state.filter(i => i.postId !== action.postId)
} else {
return state
}
}
. Logux, .

, @subscribe
. , isSubscribing: true
. , , . .
, . , squid 3, WebSocket ( Logux WebSocket). , , . — .
, Logux AJAX-. , «» «» Firefox.
AJAX Logux RSS. RSS- . RSS-, .

. :
import { useDispatch } from 'react-redux'
let dispatch = useDispatch()
let onCreate = (projectId, importId, import) => {
return dispatch.sync({
importId,
import,
type: 'amplifr/imports/add'
}, { channels: ['imports/' + projectId] })
}
let onUpdate = (projectId, importId, changed) => {
return dispatch.sync({
importId,
changed,
type: 'amplifr/imports/update'
}, { channels: ['imports/' + projectId] })
}
, , , Logux Optimitstic UI — . , , . - , .

«» .
«», , RSS- . , . Logux (dispatch.sync(…).catch(…)
), . -, , .
: catch()
JSON, try { JSON.parse(…) } catch { … }
. .
Logux ?
Logux WebSocket, , SPA . , . , , :
import status from '@logux/client/status'
let connected = false
status(store.client, state => {
if (state === 'synchronized') connected = true
}
setTimeout(() => {
if (!connected) {
sentry.track(new Error('No Logux connection for 10 minutes'))
}
}, 60 * 1000)
100 , . , - , :

, WebSocket: -, , , , , AdBlocker Kaspersky Protection. , , , Logux, .
Logux ,
Logux , . , , . RSS-, , . - , , , , .
, . — Logux. -. , , , , :
import log from '@logux/client/log'
let store = createStore(reducers)
log(store.client)
window.store = store
:
- RSS- ;
- , ;
- RSS-, ;
- ;
- !
:
window.store.client.log.store.created.filter(i => i[0].type === 'amplifr/popups/set')
, - : meta.tab
undefined
. , . , client.id
client.tabId
@logux/redux
id
tabId
. , , Logux , , .
, - «» Logux, :
Logux : «? , , ?». , — , , Redux. « », , . Logux .

:
- Logux, Redux, CRDT ;
- C Logux , ;
- Logux , ;
- O Logux tem suas desvantagens: o sistema não é simples, ainda existem bugs e as soluções nem sempre são encontradas rapidamente;
- No Amplifer, as vantagens do Logux superam os contras. Planejamos continuar a usá-lo na implementação de projetos adequados.

⌘⌘⌘
Espero que o Logux encontre uso no seu projeto. Se você tiver alguma dúvida, escreva-me no Twitter ou por correio .
Agradecemos a Alexander Marfitsin e Andrei Sitnik por sua ajuda na preparação deste artigo.