JSON:API, React и Redux
Кухта Александр, ведущий веб-технолог
Что такое SuperJob ?
1 млн посетителей в день
30 млн резюме
2 млн приглашений на собеседования в месяц
Что такое SuperJob Frontend ?
Что такое SuperJob Frontend ?
300 тыс строк кода
1200 компонентов
1000 из них подключены к стору
2700 компонентов на странице поиска вакансий
Никогда не было, и вот опять
...ваших программистов надо уволить, переход на поиск с главной занимает 4 секунды!
Вбиваю запрос в поиск, а подсказки тормозят!
ЛАГАЕТ
В чем проблема?
Store
comment
comment
comment
author
author
author
likesCounter
likesCounter
likesCounter
Selector
mapStateToProps
Component
⚛️ Comment
💎
У нас сущность - это объект:
id - ID сущности
type - тип сущности
attributes - произвольные поля
relationships - связанные сущности
Entity
comment
author
likesCounter
Store
comment
comment
comment
comment
author
author
author
author
likesCounter
likesCounter
likesCounter
likesCounter
В сторе,
нормализованное представление
{
"comment" : {
"1" : {
"id" : "1" ,
"type" : "comment" ,
"attributes" : {
"body" : "diffraction never killed nobody"
} ,
"relationships" : {
"author" : {
"data" : { "type" : "author" , "id" : "1" }
}
}
}
}
}
Где-то в mapStateToProps компонента,
После денормализации
const comment = getEntityById( state, { id, type: 'comment' } ) ;
console.log (
comment.author .name ,
comment.body ,
comment.likes .count ,
) ;
// "Fraunhoffer" "diffraction never killed nobody" 7
Редьюсер
export function entities( state = { } , action) {
const { id, type, ...entity } = action.payload ;
switch ( action.type ) {
case 'ADD_ENTITY' : return {
...state ,
[ type] : {
...state [ type] ,
[ id] : { ...entity , id, type } ,
} ,
}
}
}
Селектор
const getEntityById = createSelector(
( { entities } ) => entities,
( _, { type } ) => type,
( _, { id } ) => id,
( entities, type, id) => denormalizeEntity( entities, type, id) ,
) ;
Задача
Объяснить селектору, что сущность ,
а так же ее зависимости , не изменились
comments
authors
likesCounters
comment
comment
comment
comment
author
author
author
author
likesCounter
likesCounter
likesCounter
likesCounter
Entity
comment
author
likesCounter
prev
comment
author
likesCounter
===
===
===
next
comment
author
likesCounter
MAIN THREAD
~10%
времени уходит только на сборку мусора
во время обновления сущности в сторе
А потом еще разок 😞
Сейчас
Определяем неизменность сущности и ее зависимостей во время чтения
Ключи кэширования
Добавляем/изменяем сущность в сторе —
генерируем UUID и пишем в поле $$cacheKey
{
"id" : "1" ,
"type" : "likesCounter" ,
"$$cacheKey" : genUuid( )
}
Но есть одно но — инвалидация
resume
portfolio
experience
education
photo
video
university
company
⚛️ Resume
resume
portfolio
experience
education
photo
video
university
company
⚛️ Resume
???
Нужно обновить $$cacheKey
у всех зависимых сущностей
$$dependants
Храним указатели на зависимые сущности
{
"id" : "1" ,
"type" : "likesCounter" ,
"$$cacheKey" : "6fbeb9e1-5ef4-4a0c-8acc-fa8f4a843adf" ,
"$$dependants" : [ "comment@1" ]
}
resume
portfolio
experience
education
photo
video
university
company
⚛️ Resume
Вот теперь все круто и быстро 🎉
Обратная сторона простоты redux
const mapStateToProps = ( state, { authorId } ) => {
const comments = getAllEntitiesOfType( state, { type: 'comment' } ) ;
const authorComments = comments.filter ( ( { author } ) => {
return author.id === authorId;
} ) ;
return { authorComments } ;
} ;
MobX
MobX
Следит за тем, какие данные использует компонент
Обновляет компоненты только если эти данные поменялись
mobx@3.x.x не работает в IE11 😔