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