
Nama saya Vitaliy Rizo, saya adalah pengembang senior front-end di Amplifer. Saya akan membagikan bagaimana kami menggunakan Logux dalam aplikasi web: kami mengatur pertukaran data real-time, pemberitahuan kesalahan tanpa memuat ulang halaman, komunikasi antara tab browser dan integrasi dengan Redux.
Amplifer adalah layanan untuk penerbitan di jejaring sosial. Perlu untuk memberi tahu pengguna tentang kesalahan dengan cepat dan andal tanpa memuat ulang laman: jika tiba-tiba tidak mungkin untuk memproses gambar, VKontakte API jatuh, atau Facebook memutuskan lagi untuk tidak mempublikasikan posting. Ke depan, saya akan mengatakan bahwa kami juga berencana untuk menggunakan Logux untuk membahas publikasi dan menulis ulang popup lintas-posting dari RSS. Tetapi pertama-tama, tentang mengapa kami tidak puas dengan solusi yang tersedia.
Solusi yang mungkin: WebSocket, Firebase dan Swarm.js
Biasanya, WebSocket digunakan untuk mengimplementasikan informasi pembaruan otomatis. Dengan itu, Anda tidak perlu mengirim permintaan informasi setiap detik, seperti yang harus Anda lakukan dengan permintaan HTTP tradisional, dan ukuran header akan kecil. Namun, WebSocket memiliki kelemahan:
- . , , , . , : , ;
- , . ;
- , , ;
- , (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 , ;
- Logux memiliki kekurangan: sistemnya tidak sederhana, masih ada bug dan solusi tidak selalu ditemukan dengan cepat;
- Dalam Amplifer, keunggulan Logux lebih besar daripada yang kontra. Kami berencana untuk terus menggunakannya ketika mengimplementasikan proyek yang sesuai.

⌘⌘⌘
Saya harap Logux digunakan dalam proyek Anda. Jika Anda memiliki pertanyaan, silakan menulis kepada saya di Twitter atau melalui surat .
Terima kasih kepada Alexander Marfitsin dan Andrei Sitnik atas bantuan mereka dalam mempersiapkan artikel ini.