原文在個人博客中:原文地址 若是文章對您有幫助,您的star是對我最好的鼓勵~前端
在json對象嵌套比較複雜的狀況下,能夠將複雜的嵌套對象轉化成範式化的數據。好比後端返回的json對象比較複雜,前端須要從複雜的json對象中提取數據而後呈如今頁面上,複雜的json嵌套,使得前端展現的邏輯比較混亂。git
特別的,若是咱們使用了flux或者redux等做爲咱們前端的狀態管理機(state對象),經過控制state對象的變化,從而呈現不一樣的視圖層的展現,若是咱們在狀態管理的時候,將state對象範式化,能夠減少state對象操做的複雜性,從而能夠清晰的展現視圖更新的過程。github
- 什麼是數據範式化和反範式化
- 數據範式化的實現
- jest編寫簡單的單元測試
本文的源碼地址爲:源碼地址數據庫
本文不會具體介紹在數據庫中關於範式的定義,廣義的數據範式化,就是除了最外層屬性以外,其餘關聯的屬性用外鍵來引用。json
數據範式化的好處有:能夠減小數據的冗餘redux
好比有一個person對象以下所示:後端
{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':[{
id:30,
desp:'足球'
},{
id:40,
desp:'籃球'
},{
id:50,
desp:'羽毛球'
}]
}
複製代碼
在上述的對象中,hobby存在嵌套,咱們將perosn的無嵌套的其餘屬性做爲主屬性,而hobby屬性表示的是須要外鍵來引用的屬性,咱們將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裏面的hobby屬性中,此時變成了id號組成的數組,經過id做爲外鍵來索引另外一個對象hobby中的具體值。函數
那麼這樣作到底有什麼好處呢?post
好比咱們如今新增了一我的id爲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:'足球'
}]
}
]
複製代碼
上述的這個對象嵌套層級就比較深,好比如今咱們發現hobby中的足球的描述發生了變化,好比:
desp:'足球'——> desp:'英式足球'
若是在上述的嵌套對象中直接改變,咱們須要改變兩處位置,其一是id爲1的person中的id爲30的hobby的desp,另外一處是id爲2處的person的id爲30處的hobby的desp.
這仍是person只有2個實例的狀況,若是person的實例更多,那麼,若是僅僅一個hobby改變,就須要改變多處位置。也就顯得操做比較冗餘。
若是用數據範式化來處理,效果如何呢?,將上述的對象範式化獲得:
{
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被查詢對象:
hobby:{
'30':{
id:'30',
desp:'英式足球'
},
......
}
複製代碼
這樣,不管有多少實例引用了id爲30的這個hobby,咱們修改所引發的操做只須要一處就能到位。
那麼數據範式化有什麼缺點呢?
一句話能夠歸納數據範式化的缺點:查詢性能低下
從上述範式化後的數據能夠看出:
person:{
'1':{
'id':1,
'name':'xiaoliang',
'age':20,
'hobby':['30','40','50']
},
'2':{
'id':2,
'name':'xiaoyu',
'age':30,
'hobby':[30]
}
}
複製代碼
在上述範式化的數據裏,hobby是經過id來表示,若是要索引每一個id的具體值和對象,好比要到上一層的「hobby」對象中去查詢。而原始的嵌套對象能夠很直觀的展現出來,每個id所對應的hobby對象是什麼。
下面咱們來嘗試編寫範式化(normalize)和反範式化的函數(denormalize).
函數名稱 | 函數的具體表示 |
schema.Entity(name, [entityParams], [entityConfig]) | --name爲該schema的名稱 --entityParams爲可選參數, 定義該schema的外鍵,定義的外鍵能夠不存在 --entityConfig爲可選參數,目前僅支持一個參數 定義該entity的主鍵,默認值爲字符串'id' |
normalize(data, entity) | -- data 須要範式化的數據,必須爲符合schema定義的對象或由該類對象組成的數組 -- entity實例 |
denormalize (normalizedData, entity, entities) | -- normalizedData -- entity -同上 -- 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"
}
}]
}
}
複製代碼
範式化以前必須對嵌套對象進行處理,深層嵌套的狀況下,須要用實體Entity進行解構,層級最深的實體須要首先被定義,而後一層層的解耦到最外層。
該實體的構造方法,接受3個參數,第一個參數name,表示範式化後的對象的屬性的名稱,第二個參數entityParams,表示實體化後,原始的嵌套對象和必定義的實體之間的一一對應關係,第三個參數表示的是 用來索引嵌套對象的主鍵,默認的狀況下,咱們用id來索引。
上述實例的實體化爲:
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存在嵌套的狀況,所以須要在init方法中遍歷entityParams中的schema屬性。此外爲了定義了獲取主鍵和name名的方法,getName和getId。
上述就是範式化的函數,接受兩個參數,第一個參數爲原始的須要被範式化的數據,第二個參數爲最外層的實體。一樣在上述例子原始數據被範式化,能夠經過以下方式來實現:
normalize(originData,articles)
複製代碼
上述的例子中,最外層的實體爲articles。
那麼如何實現該範式化,首先考慮到最外層的實體,可能存在嵌套,且最外層實體的對象的屬性值不必定是一個schema實體,也多是數組等結構,所以要分別處理schema實體和非schema實體的狀況:
const flatten = (value, schema, addEntity) => {
if (typeof schema.getName === 'undefined') {
return noSchemaNormalize(schema, value, flatten, addEntity)
}
return schemaNormalize(schema, value, flatten, addEntity)
}
複製代碼
若是傳入的是一個schema實體:
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)
}
複製代碼
那麼狀況爲遞歸該schema,直到從最外層的schema遞歸到最裏層的schema.
若是傳入的不是一個schema實體:
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,遞歸到裏層,再往外層,獲得對應的schema的name所包含的id,和此id所指向的具體對象。
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
}
}
複製代碼
最後咱們的normalize方法具體爲:
const normalize = (data, schema) => {
const entities = {}
const addEntity = addEntities(entities)
const result = flatten(data, schema, addEntity)
return { entities, result }
}
複製代碼
denormalize反範式化方法,接受3個參數,其中normalizedData 和entities表示範式化後的對象的屬性,而entity表示最外層的實體。
調用的方式爲:
const normalizedData = normalize(originalData, article);
// 還原範式化數據
const {result, entities} = normalizedData
const denormalizedData = denormalize(result, article, entities)
複製代碼
反範式化的具體代碼與範式化類似,就不具體說明,詳情請看源代碼。
直接給出簡單的單元測試代碼:
//範式化數據用例,原始數據
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)
})
複製代碼