翻译|Using normalizr.js in a Redux store – Farmdrop – Medium


title: 翻译|Using normalizr.js in a Redux store – Farmdrop – Medium
date: 2017-04-12 20:19:25
categories: 翻译
tags: Redux


原文请参见,程序设计我理解简单一句话就是处理数据和展现数据.数组,对象和数据库都是展现数据的不同形式.自从意识到这一点以后,就突然冒出一个念头,React中引入的Redux就可以作为一个数据库来思考啊,至少是能使用已经学过的开发web模式,框架+数据库的概念来辅助理解.但是Redux中的state还显的很简单,所以有程序员就引入了normalizr.js来继续扩展state的能力,最终这篇文章就也提出了简单数据库的概念.我对这个问题还不是太清楚,前面看文档的时候并没有意识到他的价值,现在返回头来看,发现这个可能还挺有用的.后面会继续学习这部分的内容.翻译部分完成,但是有些内容不理解.所以内容以原文为准

在Redux store中使用normalizr.js

最近我们在app中开始使用React,但是我们意识到当app规模扩大的时候,单凭Props来传递数据是不能适应app的大规模结构需求.单纯使用React,要维护和添加额外的功能或者重构(refactor,是这么翻译的吗?)都非常的困难.因此我们决定在app中整合使用Redux,我们花费了很多精力来重构代码,由此我觉得可以分享一下我学到的东西.第一件事就是怎么在Redux store中组织数据.

提出问题

我们的app是e-commerce,所以里面有订单和商品的概念,把订单和商品联系起来的是每一条 Line Items(订单项).一个 Line Item连接一个产品到订单,可以储存客户订单中每种产品的数量.
为了确保我们的app的性能,我想确认在app运行中没有实体(entities)在内存中被复制.在我们以前的Angular app中,我们在内从中复制产品类似下面
(译注:这里的复制的意思是有没有存在重复的问题)

 // BAD - notice how the 'Carrots' object is 
 duplicated.
 //不好的方法-注意Carrots对象被复制
{
 order: {
   id: 10,
   line_items: [
     { 
       id: 483,
       quantity: 2,
       product: {//这个产品和下面的产品是重复的,有内存浪费
         id: 924,
         name: 'Carrots',
         price: '1.50',
       }
     }
   ]
 },
 products: [ //这里的产品和上面的是一摸一样的,用一个引用就可以
   {
     id: 924,
     name: 'Carrots',
     price: '1.50'
   }
 ]
}

但是我们使用Redux的时候也应该这么做吗?reducers在这种情况下应该是什么样子?(上面的两个产品的重复,怎么在reducer中来减少重复)

应对对象冗余复制的解决办法

在看了这篇伟大的文章后,我意识到normalizr.js应该非常符合我的要求.简明扼要,normalizr接收一个巢式(nested) javascirpt对象(类似上面的订单),然后输出扁平化的对象.在Github repo中查看它的具体工作原理.

为了让normalizr正常工作,必须为需要储存在store中的entity创建不同的图式(schema).

 // schemas.js
import { Schema, arrayOf } from 'normalizr';
const orderSchema     = Schema('orders');
const lineItemSchema  = Schema('lineItems');
const productSchema   = Schema('products');
// An Order has an array of line items
//订单含有line items的数组
orderSchema.define({
  lineItems: arrayOf(lineItemSchema)
});
// A Line Item has one product attached
//每个line item有一个产品附着到上面
lineItems.define({
  product: productSchema
});
export { orderSchema, lineItemSchema, productSchema };

接着我们需要配置一个简单的action,目的是去序列化我们的订单,输入到redux store中.

 // shop_actions.js
module.exports = {
  deserializeOrder: (order) => {
    return {
      type: 'DESERIALIZE_ORDER',
      order: order    
    }
  }
};

一旦你已经有了schema和actions,reducer会变得出奇的简单

 // shop_reducer.js
import { normalize } from 'normalizr';
import { orderSchema } from './schemas';
// We use seamless-immutable but thats for another time.
//还要两个比较好的immutable.js的实现方法,可以在github中看看
import Immutable from 'seamless-immutable';
const defaultState = Immutable({
  order: [],
  product: [],
  lineItem: []
});
export default function shopReducer(state = defaultState, action) {
  switch (action.type) {
    case 'DESERIALIZE_ORDER':
      // This is the magic part - the normalize method will flatten 
      // my deeply nested order according to my schemas defined
      // above.
      //巢式结构被扁平化
      var normalizedOrder = normalize(action, { 
        order: orderSchema 
      });
      // Due to using seamless-immutable we have to merge the new
      // entities into the state.
      return state.merge(normalizedOrder.entities);
    default:
      return state;
  }
}

现在我们也可以很容易的在actions和reducers一起工作时进行测试工作.

import reducer from './path/to/reducer';
import actions from './path/to/actions';
const fakeOrder = {
  id: 10,
  lineItems: [
    {
      id: 483,
      quantity: 2,
      product: {
        id: 924,
        name: 'Carrots',
        price: 1.50
      }
    }
  ]
};
describe('shopReducer', () => {
  describe('DESERIALIZE_ORDER', () => {
    let state;
    beforeEach(() => {
      state = reducer(
        undefined, 
        actions.deserializeOrder(fakeOrder)
      );
    });    
    
    it('should deserialize the order correctly', () => {
      expect(state.orders[10]).toEqual({
        id: 10,
        lineItems: [ 483]
      });
    });
    
    it('should deserialize the lineItems correctly', () => {
      expect(state.lineItems[483]).toEqual({
        id: 483,
        quantity: 2,
        product: 924
      });
    });
  
    it('should desialize the product correctly', () => {
      expect(state.products[924]).toEqual({
        id: 924,
        name: 'Carrots',
        price: 1.50
      });
    });
  });
});

所以,现在如果你在订单中传递文章开头的数据,去序列化,redux store看起来像这样


{  
  orders: {
    10: {
      id: 10,
      line_items: [ 483 ]//关联到lineItems的483
    }
  },
  lineItems: {
    483: {
      id: 483,
      quantity: 2,
      product: 924 //关联到product 924
    }
  },
  products: {
    924: {
      id: 924,
      name: 'Carrots',
      price: 1.50
    }

上面所有在javascript store中的结果表现的像是一个简单的数据库.现在只要我们知道每一个想更新的entity的id,我们就可以在store中发现他.例如,在新store中,如果我们想更新line item的数量,我们不在需要知道和他相关联的订单id.

这一切都非常的精彩,但是如果我想在store之外重构一个订单?我们的解决办法是创建一些助手 class(helper class).下面的例子相当的简单,但是这些classes可以帮助改进一下复杂的方法.

export default class orderHelper {
  constructor(store) {
    this.store = store;
    // We store the current order id on the store too
    //在store中储存当前订单id   
    this.currentOrder = store.orders[store.currentOrderId];
  }
  currentLineItems() {
    return this.currentOrder.lineItems.map(lineItemId =>
      return (this.store.lineItems[liId])
    );
  }
}

让我们看看,如果我们构建一个组件来显示items的总数量,你可以这样配置

import React       from 'react';
import { connect } from 'react-redux';
import OrderHelper from './path/to/orderHelper';
// First of all we create the React Component
//创建React 组件
class OrderCount extends React.Component {
  static propTypes = {
    lineItems: React.PropTypes.array.isRequired,
  }
  totalQuantity() {
    return this.props.lineItems.reduce((total, lineItem) => {
      return (total + lineItem.quantity);
    }, 0); 
  }
  render() {
    // We're using the jsx format
    return (
      <div>{ this.totalQuantity() }</div>
  }
}
// We now bind the component to the redux store
//连接组件到redux的store中
export default connect((state) => {
  const helper = new OrderHelper(state);
  return {
    lineItems: helper.currentLineItems(),
  }
})(OrderCount);

如果其他的组件更新了联系到这个store的line items时,这个组件将会更新.

到目前为止,我们已经发现这种形式使用起来非常的简单.我们的reducers仅仅关注store中的结构数据,非常容易测试,因为他们是vanilla.js,不和Dom或其他内容交互.我们可以把React变为其他的框架,仍然使用相同的reducers和actions

结论

在app中改用Redux,使我们思考app的不同结构方式.从app其他部分把Redux(还有 reducers,actions)分离出来,这个过程给了我们继续构建的信心,即使不使用React,我们构建的单元测试让人感觉很舒服,使用normalizr.js改进了我们组织数据的方式.
虽然这个项目目前还在继续进展中,但是现在我们做的已经够的上精彩了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容