美文网首页Web前端之路让前端飞移动开发
翻译|Using normalizr.js in a Redux

翻译|Using normalizr.js in a Redux

作者: smartphp | 来源:发表于2017-04-14 06:11 被阅读1018次

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改进了我们组织数据的方式.
虽然这个项目目前还在继续进展中,但是现在我们做的已经够的上精彩了.

相关文章

网友评论

    本文标题:翻译|Using normalizr.js in a Redux

    本文链接:https://www.haomeiwen.com/subject/kaaiattx.html