Оригинальный текст в моем блоге:исходный адресЕсли статья была вам полезна, ваша звездочка — лучшее поощрение для меня~
В случае сложной вложенности объектов JSON сложные вложенные объекты могут быть преобразованы в нормализованные данные. Например, объект json, возвращаемый бэкендом, относительно сложен, а интерфейс должен извлекать данные из сложного объекта json, а затем отображать их на странице.Сложное вложение json делает логику отображения интерфейса запутанной.
В частности, если мы используем flux или redux в качестве внешнего менеджера состояния (объекта состояния), контролируя изменения объекта состояния, мы можем представить отображение различных слоев представления.Если мы находимся в управлении состоянием, объект состояния Нормализация может уменьшить сложность операций с объектами состояния, чтобы процесс обновления представления можно было четко отобразить.
- Что такое нормализация и денормализация данных
- Реализация нормализации данных
- Написание простых модульных тестов в шутку
Адрес источника этой статьи:Адрес источника
1. Что такое нормализация данных
(1) Определение нормализации данных
В этой статье не будет конкретно вводиться определение нормальной формы в базе данных.В широком смысле нормализация данных означает, что в дополнение к самому внешнему атрибуту внешние ключи ссылаются на другие связанные атрибуты.
Преимущества нормализации данных: она может уменьшить избыточность данных.
(2) Примеры нормализации данных
Например, объект человека выглядит так:
{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':[{
id:30,
desp:'足球'
},{
id:40,
desp:'篮球'
},{
id:50,
desp:'羽毛球'
}]
}
В приведенных выше объектах хобби является вложенным. Мы используем другие невложенные атрибуты perosn в качестве основного атрибута, а атрибут хобби представляет атрибуты, на которые должны ссылаться внешние ключи. Мы используем id в качестве имени внешнего ключа, и приведенный выше вложенный объект можно нормализовать, чтобы получить:
{
person:{
'1':{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':['30','40','50']
}
},
hobby:{
'30':{
id:'30',
desp:'足球'
},
'40':{
id:'40',
desp:'篮球',
},
'50':{
id:'50',
desp:'羽毛球'
}
}
}
Приведенный выше объект является результатом нормализации.Мы обнаружили, что атрибут хобби в основном объекте person теперь стал массивом чисел идентификатора, и идентификатор используется в качестве внешнего ключа для индексации определенного значения в другом объекте хобби.
(3) Преимущества нормализации данных
Так какая польза от этого?
Например, теперь мы добавляем человека с идентификатором 2:
{
'id':2,
'name':'xiaoyu',
'age':20,
'hobby':[{
id:30,
desp:'足球'
}]
}
К счастью, его интересы также включают футбол, поэтому, если есть форма сложных вложенных объектов, объект становится следующей формы:
[
{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':[{
id:30,
desp:'足球'
},{
id:40,
desp:'篮球'
},{
id:50,
desp:'羽毛球'
}]
},
{
'id':2,
'name':'xiaoyu',
'age':20,
'hobby':[{
id:30,
desp:'足球'
}]
}
]
Вышеупомянутый объект глубоко вложен, например, сейчас мы обнаруживаем, что описание футбола в хобби изменилось, например:
desp: 'футбол' --> desp: 'футбол'
Если он напрямую изменен в приведенном выше вложенном объекте, нам нужно изменить два места: одно — это хобби с идентификатором 30 у человека с идентификатором 1, а другое — лицо с идентификатором 2, чей идентификатор равен 30. хобби в презрении.
Это все еще тот случай, когда экземпляров человека всего 2. Если экземпляров человека больше, то при изменении только одного хобби необходимо изменить несколько местоположений. Это тоже кажется излишним.
Если вы используете нормализацию данных, чтобы справиться с этим, как это работает? , нормализуя вышеуказанный объект, чтобы получить:
{
person:{
'1':{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':['30','40','50']
},
'2':{
'id':2,
'name':'xiaoyu',
'age':30,
'hobby':[30]
}
},
hobby:{
'30':{
id:'30',
desp:'足球'
},
'40':{
id:'40',
desp:'篮球',
},
'50':{
id:'50',
desp:'羽毛球'
}
}
}
В этот момент, если то же самое произойдет:
***desp:'足球'——> desp:'英式足球'***
Такие изменения нужно изменить только после сопоставления, и хобби запрашивается для объекта:
hobby:{
'30':{
id:'30',
desp:'英式足球'
},
......
}
Таким образом, независимо от того, сколько экземпляров ссылаются на это хобби с идентификатором 30, операция, вызванная нашей модификацией, должна выполняться только в одном месте.
(4) Недостатки нормализации данных
Итак, каковы недостатки нормализации данных?
Недостатки нормализации данных можно описать одним предложением: низкая производительность запросов.
Из приведенных выше нормированных данных видно, что:
person:{
'1':{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':['30','40','50']
},
'2':{
'id':2,
'name':'xiaoyu',
'age':30,
'hobby':[30]
}
}
В приведенных выше нормамизованных данных хобби представлен ID. Если вы хотите индексировать конкретное значение и объект каждого идентификатора, например, необходимо запрашивать объект «хобби» в верхнем слое. Оригинальный вложенный объект может отображаться интуитивно, какой объект хобби, соответствующий каждому идентификатору.
2. Реализация нормализации данных (этот раздел и последующее содержимое могут быть необязательными)
Попробуем написать функции нормализации и денормализации.
имя функции | Конкретное представление функции |
schema.Entity(name, [entityParams], [entityConfig]) | --name имя схемы --entityParams — необязательный параметр, Определите внешний ключ схемы, определенный внешний ключ может не существовать --entityConfig — необязательный параметр, в настоящее время поддерживается только один параметр Определяет первичный ключ этой сущности, значением по умолчанию является строка 'id' |
normalize(data, entity) | -- data Данные, которые необходимо нормализовать, это должен быть объект, соответствующий определению схемы, или массив объектов этого типа -- экземпляр объекта |
denormalize (normalizedData, entity, entities) |
-- normalizedData -- сущность - то же, что и выше -- сущности такие же, как и выше |
Реализация нормализации и денормализации данных, в основном трех вышеперечисленных функций, давайте проанализируем их одну за другой.
Исходные данные, которые необходимо нормализовать в этой статье:
const originalData = {
"id": "123",
"author": {
"uid": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": {
total: 100,
result: [{
"id": "324",
"commenter": {
"uid": "2",
"name": "Nicole"
}
}]
}
}
(1) схема.Сущность
Вложенные объекты должны быть обработаны перед нормализацией.В случае глубокой вложенности сущности необходимо деконструировать.Сначала необходимо определить самую глубокую сущность, а затем отделить слой за слоем от самого внешнего слоя.
Метод построения сущности принимает 3 параметра: имя первого параметра, которое представляет имя атрибута нормализованного объекта, и второй параметр entityParams, который представляет разницу между исходным вложенным объектом и определенной сущностью после того, как сущность Взаимооднозначное соответствие между , третий параметр представляет собой Первичный ключ, используемый для индексации вложенных объектов, по умолчанию мы используем идентификатор для индексации.
Материализация приведенного выше экземпляра:
const user = new schema.Entity('users', {}, {
idAttribute: 'uid'
})
const comment = new schema.Entity('comments', {
commenter: user
})
const article = new schema.Entity('articles', {
author: user,
comments: {
result: [ comment ]
}
});
Материализация по-прежнему идет от самого внутреннего слоя к самому внешнему слою. И третий параметр представляет собой первичный ключ индекса.
Как реализовать конструктор? Код реализации schema.Entity: сначала определите класс:
export default class EntitySchema {
constructor (name, entityParams = {}, entityConfig = {}) {
const idAttribute = entityConfig.idAttribute || 'id'
this.name = name
this.idAttribute = idAttribute
this.init(entityParams)
}
/**
* [获取当前schema的名字]
* @return {[type]} [description]
*/
getName () {
return this.name
}
getId (input) {
let key = this.idAttribute
return input[key]
}
/**
* [遍历当前schema中的entityParam,entityParam中可能存在schema]
* @param {[type]} entityParams [description]
* @return {[type]} [description]
*/
init (entityParams) {
if (!this.schema) {
this.schema = {}
}
for (let key in entityParams) {
if (entityParams.hasOwnProperty(key)) {
this.schema[key] = entityParams[key]
}
}
}
}
Определите класс EntitySchema.В методе построения, поскольку entityParams является вложенным, необходимо пройти свойство схемы в entityParams в методе инициализации. Кроме того, чтобы определить методы получения первичного ключа и имени, getName и getId.
(2) нормализовать (данные, сущность)
Выше приведена нормализованная функция, которая принимает два параметра, первый параметр — это исходные данные, которые необходимо нормализовать, а второй параметр — самая внешняя сущность. Также в приведенном выше примере необработанные данные нормализуются, что может быть достигнуто следующим образом:
normalize(originData,articles)
В приведенном выше примере самым внешним объектом являются статьи.
Итак, как реализовать эту нормализацию, сначала рассмотрим самый внешний объект, может быть вложенность, и значение атрибута объекта самого внешнего объекта не обязательно является объектом схемы, но также массивом и другими структурами, поэтому схема должна быть обработана отдельно. В случае сущностей и сущностей, не входящих в схему:
const flatten = (value, schema, addEntity) => {
if (typeof schema.getName === 'undefined') {
return noSchemaNormalize(schema, value, flatten, addEntity)
}
return schemaNormalize(schema, value, flatten, addEntity)
}
Если объект схемы передается:
const schemaNormalize = (schema, data, flatten, addEntity) => {
const processedEntity = {...data}
const currentSchema = schema
Object.keys(currentSchema.schema).forEach((key) => {
const schema = currentSchema.schema[key]
const temple = flatten(processedEntity[key], schema, addEntity)
// console.log(key,temple);
processedEntity[key] = temple
})
addEntity(currentSchema, processedEntity)
return currentSchema.getId(data)
}
Тогда дело в том, чтобы рекурсировать схему до рекурсии от самой внешней схемы к самой внутренней схеме.
Если не переданы объекты схемы:
const noSchemaNormalize = (schema, data, flatten, addEntity) => {
// 非schema实例要分别针对对象类型和数组类型做不同的处理
const object = { ...data }
const arr = []
let tag = schema instanceof Array
Object.keys(schema).forEach((key) => {
if (tag) {
const localSchema = schema[key]
const value = flatten(data[key], localSchema, addEntity)
arr.push(value)
} else {
const localSchema = schema[key]
const value = flatten(data[key], localSchema, addEntity)
object[key] = value
}
})
// 根据判别的结果,返回不同的值,可以是对象,也可以是数组
if (tag) {
return arr
} else {
return object
};
}
Если это не сущность, она делится на объект и массив, с которыми нужно работать отдельно.
Наконец, есть addEntity, которая рекурсивно обращается к внутреннему слою, а затем к внешнему слою, чтобы получить идентификатор, содержащийся в имени соответствующей схемы, и конкретный объект, на который указывает этот идентификатор.
const addEntities = (entities) => (schema, processedEntity) => {
const schemaKey = schema.getName()
const id = schema.getId(processedEntity)
if (!(schemaKey in entities)) {
entities[schemaKey] = {}
}
const existingEntity = entities[schemaKey][id]
if (existingEntity) {
entities[schemaKey][id] = Object.assgin(existingEntity, processedEntity)
} else {
entities[schemaKey][id] = processedEntity
}
}
Наконец, наш метод нормализации выглядит следующим образом:
const normalize = (data, schema) => {
const entities = {}
const addEntity = addEntities(entities)
const result = flatten(data, schema, addEntity)
return { entities, result }
}
(3) метод денормализации денормализации
Метод денормализации denormalize принимает 3 параметра, где normalizedData и сущности представляют свойства нормализованного объекта, а сущность представляет самую внешнюю сущность.
Способ вызова:
const normalizedData = normalize(originalData, article);
// 还原范式化数据
const {result, entities} = normalizedData
const denormalizedData = denormalize(result, article, entities)
Конкретный код денормализации похож на нормализацию, поэтому подробно объясняться не будет.Подробности см. в исходном коде.
3. простой модульный тест
Дайте простой код модульного теста напрямую:
//范式化数据用例,原始数据
const originalData = {
"id": "123",
"author": {
"uid": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": {
total: 100,
result: [{
"id": "324",
"commenter": {
"uid": "2",
"name": "Nicole"
}
}]
}
}
//范式化数据用例,范式化后的结果数据
const normalizedData={
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: {
total: 100,
result: [ "324" ]
}
}
},
"users": {
"1": { "uid": "1", "name": "Paul" },
"2": { "uid": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
//开始测试上述用例下的,范式化结果对比
test('test originalData to normalizedData', () => {
const user = new schema.Entity('users', {}, {
idAttribute: 'uid'
});
const comment = new schema.Entity('comments', {
commenter: user
});
const article = new schema.Entity('articles', {
author: user,
comments: {
result: [ comment ]
}
});
const data = normalize(originalData, article);
expect(data).toEqual(normalizedData);
});
//开始测试上述例子,反范式化的结果对比
test('test normalizedData to originalData',()=>{
const user = new schema.Entity('users', {}, {
idAttribute: 'uid'
});
// Define your comments schema
const comment = new schema.Entity('comments', {
commenter: user
});
// Define your article
const article = new schema.Entity('articles', {
author: user,
comments: {
result: [ comment ]
}
});
const data = normalize(originalData, article)
//还原范式化数据
const {result,entities}=data;
const denormalizedData=denormalize(result,article,entities);
expect(denormalizedData).toEqual(originalData)
})