Spec-Zone.ru › Redux 3

Нормализация формы состояния

Многие приложения работают с данными, которые имеют вложенную или реляционную структуру. Например, редактор блога может содержать множество записей (Posts), каждая запись может иметь множество комментариев (Comments), и записи, и комментарии будут написаны пользователем (User). Данные для такого приложения могут выглядеть так:

const blogPosts = [
    {
        id : "post1",
        author : {username : "user1", name : "User 1"},
        body : "......",
        comments : [
            {
                id : "comment1",
                author : {username : "user2", name : "User 2"},
                comment : ".....",
            },
            {
                id : "comment2",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            }
        ]    
    },
    {
        id : "post2",
        author : {username : "user2", name : "User 2"},
        body : "......",
        comments : [
            {
                id : "comment3",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            },
            {
                id : "comment4",
                author : {username : "user1", name : "User 1"},
                comment : ".....",
            },
            {
                id : "comment5",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            }
        ]    
    }
    // and repeat many times
]

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

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

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

Проектирование нормализованного состояния

Основные концепции нормализации данных:

  • Каждый тип данных получает свою собственную «таблицу» в состоянии.
  • Каждая «таблица данных» должна хранить отдельные элементы в объекте, где ключами являются идентификаторы элементов, а значениями — сами элементы.
  • Любые ссылки на отдельные элементы должны осуществляться с помощью хранения идентификатора элемента.
  • Для указания порядка должны использоваться массивы идентификаторов.

Пример нормализованной структуры состояния для примера блога выше может выглядеть так:

{
    posts : {
        byId : {
            "post1" : {
                id : "post1",
                author : "user1",
                body : "......",
                comments : ["comment1", "comment2"]    
            },
            "post2" : {
                id : "post2",
                author : "user2",
                body : "......",
                comments : ["comment3", "comment4", "comment5"]    
            }
        }
        allIds : ["post1", "post2"]
    },
    comments : {
        byId : {
            "comment1" : {
                id : "comment1",
                author : "user2",
                comment : ".....",
            },
            "comment2" : {
                id : "comment2",
                author : "user3",
                comment : ".....",
            },
            "comment3" : {
                id : "comment3",
                author : "user3",
                comment : ".....",
            },
            "comment4" : {
                id : "comment4",
                author : "user1",
                comment : ".....",
            },
            "comment5" : {
                id : "comment5",
                author : "user3",
                comment : ".....",
            },
        },
        allIds : ["comment1", "comment2", "comment3", "commment4", "comment5"]
    },
    users : {
        byId : {
            "user1" : {
                username : "user1",
                name : "User 1",
            }
            "user2" : {
                username : "user2",
                name : "User 2",
            }
            "user3" : {
                username : "user3",
                name : "User 3",
            }
        },
        allIds : ["user1", "user2", "user3"]
    }
}

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

  • Поскольку каждый элемент определен только в одном месте, нам не нужно пытаться вносить изменения в нескольких местах, если этот элемент обновлен.
  • Логика редуктора не должна обрабатывать глубокие уровни вложенности, поэтому, вероятно, она будет намного проще.
  • Логика получения или обновления заданного элемента теперь достаточно проста и согласованна. Зная тип элемента и его идентификатор, мы можем непосредственно найти его за несколько простых шагов, не проходя через другие объекты в поисках.
  • Поскольку каждый тип данных отделен, обновление, например, изменения текста комментария, потребовало бы только новых копий части дерева «комментарии > по идентификатору > комментарий». Это, как правило, означает, что меньше частей пользовательского интерфейса нуждаются в обновлении, потому что их данные изменились. В отличие от этого, обновление комментария в исходной вложенной форме потребовало бы обновления объекта комментария, родительского объекта записи, массива всех объектов записей и, вероятно, привело бы к повторной отрисовке всех компонентов Post и Comment в пользовательском интерфейсе.

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

Организация нормализованных данных в состоянии

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

{
    simpleDomainData1: {....},
    simpleDomainData2: {....}
    entities : {
        entityType1 : {....},
        entityType2 : {....}
    }
    ui : {
        uiSection1 : {....},
        uiSection2 : {....}
    }
}

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

Связи и таблицы

Поскольку мы рассматриваем часть нашего хранилища Redux как «базу данных», многие принципы проектирования баз данных также применимы здесь. Например, если у нас есть связь «многие ко многим», мы можем смоделировать ее с помощью промежуточной таблицы, которая хранит идентификаторы соответствующих элементов (часто называемой «таблицей соединения» или «ассоциативной таблицей»). Для согласованности мы, вероятно, также захотим использовать тот же byId и allIds подход, что и для самих таблиц элементов, например, так:

{
    entities: {
        authors : { byId : {}, allIds : [] },
        books : { byId : {}, allIds : [] },
        authorBook : {
            byId : {
                1 : {
                    id : 1,
                    authorId : 5,
                    bookId : 22
                },
                2 : {
                    id : 2,
                    authorId : 5,
                    bookId : 15,
                }
                3 : {
                    id : 3,
                    authorId : 42,
                    bookId : 12
                }
            },
            allIds : [1, 2, 3]

        }
    }
}

Операции, такие как «Поиск всех книг данного автора», затем могут быть выполнены с помощью одного цикла по таблице соединения. Учитывая типичные объемы данных в приложении на стороне клиента и скорость JavaScript-движков, этот тип операции, скорее всего, будет иметь достаточно высокую производительность для большинства случаев использования.

Нормализация вложенных данных

Поскольку API часто возвращают данные во вложенной форме, эти данные необходимо преобразовать в нормализованную форму перед включением в дерево состояния. Для этой задачи обычно используется библиотека Normalizr. Вы можете определить типы схем и связи, передать схему и данные ответа в Normalizr, и она выведет нормализованное преобразование ответа. Этот результат затем можно включить в действие и использовать для обновления хранилища. Дополнительные сведения об использовании см. в документации Normalizr.

© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API