本文介绍了React 和 Redux 架构问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

阅读前:

这不是非工作代码的问题,而是架构问题.此外,我目前没有使用 ReactRedux 库,因为我首先试图了解这些部分在这个测试应用程序中是如何独立工作的.它尽可能短,但不幸的是仍然很长,请耐心等待

简短介绍

我有一组 Bottle 模型.使用伪代码,瓶子定义如下:

I've got an array of Bottle models. Using pseudocode,a bottle is defined like so:

class Bottle{

//members
filledLiters
filledLitersCapacity
otherMember1
otherMember2

//functions
toPostableObject(){

//removes functions by running JSON.
var cloneObj = JSON.parse(JSON.stringify(this));
//removes all members we dont want to post
delete cloneObj["otherMember1"];
}

//other functions
}

我还有一个显示所有 Bottle 项目的 React 组件.该组件还需要存储所有 Bottle 项目的先前状态(用于动画,忽略此).

I've also got a React component that displays all Bottle items.The component needs to store the previous state of all Bottle items as well ( its for animating, disregard this ).

Redux 使用

我需要使用辅助类对某些 Bottle 项目执行复杂的操作,如下所示:

There are complex operations i need to perform on some of the Bottle items using a helper class like so:

var updated_bottles = BottleHandler.performOperationsOnBottles(bottleIds)
mainStore.dispatch({type:"UPDATED_BOTTLES",updated_bottles:updated_bottles})

我不想为每个操作更新商店,因为我希望商店在最后一次全部更新.因此我的 BottleReducer 看起来像这样:

I dont want to update the store for every operation as i would like the store to be updated all together at the end in one go. Therefore my BottleReducer looks something like this :

var nextState = Object.assign({}, currentState);
nextState.bottles = action.updated_bottles

其中 action.updated_bottles 是执行操作后瓶子的最终状态.

Where action.updated_bottles is the final state of bottles after having performed the operations.

问题

即使一切正常,我还是怀疑这是接近我的架构的错误的心态".原因之一是为了避免在执行操作时保持对瓶子对象的引用并改变状态,我必须做这件丑事:

Even though everything works, im suspicious that this is the "wrong mindset" for approaching my architecture. One of the reasons is that to avoid keeping the reference to the bottle objects and mutating the state as im performing the operations, i have to do this ugly thing:

var bottlesCloneArray = mainStore.getState().
bottleReducer.bottles.map(
a => {
var l = Object.assign({}, a);
 Object.setPrototypeOf( l, Character.prototype );
return l
}
);

这是因为我需要一个仍然保留其原始功能的对象克隆数组(意味着它们是类的实际实例克隆)

This is because i need a cloned array of objects that still retain their original functions ( meaning they're actual instance clones of the class )

如果您能指出我逻辑中的缺陷/缺陷,我将不胜感激.

If you can point out the flaw/flaws in my logic i'd be grateful.

PS:我需要保留类实例的深度克隆"的原因是,我可以在 React 组件中保留瓶子的先前状态,以便在更新时在两个状态之间进行动画处理渲染发生.

推荐答案

在处理 redux 架构时,将序列化和不变性放在每个决策的最前沿可能非常有用,这在开始时可能很困难,尤其是当您非常习惯OOP

When dealing with redux architecture it can be extremely useful to keep serialisation and immutability at the forefront of every decision, this can be difficult at first especially when you are very used to OOP

因为 store 的 state 只是一个 JS 对象,所以很容易用它来跟踪更复杂模型类的 JS 实例,但应该更像一个 DB,在那里你可以序列化你的模型的表示以不变的方式往返于它.

As the store's state is just a JS object it can be tempting to use it to keep track of JS instances of more complex model classes, but instead should be treated more like a DB, where you can serialise a representation of your model to and from it in an immutable manner.

以最原始的形式存储瓶子的数据表示使得诸如持久化到 localStorage 和存储的再水化等更高级的应用程序成为可能,然后允许服务器端渲染和离线使用,但更重要的是它使它变得更多您的应用程序中正在发生和发生的变化更加可预测和明显.

Storing the data representations of your bottles in its most primitive form makes things like persistance to localStorage and rehydration of the store possible for more advanced applications that can then allow server side rendering and maybe offline use, but more importantly it makes it much more predictable and obvious what is happening and changing in your application.

我见过的大多数 redux 应用程序(包括我的)都走上了完全取消模型类的功能路线,并简单地在 reducer 中直接对数据执行操作 - 可能在此过程中使用助手.这样做的一个缺点是它会导致缺乏一些上下文的大型复杂减速器.

Most redux apps i've seen (mine included) go down the functional route of doing away with model classes altogether and simply performing operations in the reducers directly upon the data - potentially using helpers along the way. A downside to this is that it makes for large complex reducers that lack some context.

但是,如果您希望将此类助手封装到 Bottle 类中,则有一个完全合理的中间立场,但您需要考虑 case 类,它可以是 从数据表单创建并序列化回数据表单,并且如果操作则不可变

However there is a middle ground that is perfectly reasonable if you prefer to have such helpers encapsulated into a Bottle class, but you need to think in terms of a case class, which can be created from and serialised back to the data form, and acts immutably if operated upon

让我们看看这对您的 Bottle 有何作用(打字稿注释以帮助显示正在发生的事情)

Lets look at how this might work for your Bottle (typescript annotated to help show whats happening)

瓶箱类

interface IBottle {
  name: string,
  filledLitres: number
  capacity: number
}

class Bottle implements IBottle {

  // deserialisable
  static fromJSON(json: IBottle): Bottle {
    return new Bottle(json.name, json.filledLitres, json.capacity)
  }

  constructor(public readonly name: string,
              public readonly filledLitres: number,
              public readonly capacity: number) {}

  // can still encapuslate computed properties so that is not needed to be done done manually in the views
  get nameAndSize() {
    return `${this.name}: ${this.capacity} Litres`
  }

  // note that operations are immutable, they return a new instance with the new state
  fill(litres: number): Bottle {
    return new Bottle(this.name, Math.min(this.filledLitres + litres, this.capacity), this.capacity)
  }

  drink(litres: number): Bottle {
    return new Bottle(this.name, Math.max(this.filledLitres - litres, 0), this.capacity)
  }

  // serialisable
  toJSON(): IBottle {
    return {
      name: this.name,
      filledLitres: this.filledLitres,
      capacity: this.capacity
    }
  }

  // instances can be considered equal if properties are the same, as all are immutable
  equals(bottle: Bottle): boolean {
    return bottle.name === this.name &&
           bottle.filledLitres === this.filledLitres &&
           bottle.capacity === this.capacity
  }

  // cloning is easy as it is immutable
  copy(): Bottle {
    return new Bottle(this.name, this.filledLitres, this.capacity)
  }

}

商店状态注意它包含一个数据表示的数组而不是类实例

Store stateNotice it contains an array of the data representation rather than the class instance

interface IBottleStore {
  bottles: Array<IBottle>
}

瓶子选择器在这里,我们使用选择器从存储中提取数据并转换为类实例,您可以将其作为 prop 传递给 React 组件.如果使用像 reselect 这样的库,这个结果将被记忆,所以你的实例引用将保持不变,直到它们在存储中的基础数据发生变化.这对于使用 PureComponent 优化 React 很重要,它仅通过引用来比较 props.

Bottles selectorHere we use a selector to extract data from the store and perform transformation into class instances that you can pass to your React component as a prop.If using a lib like reselect this result will be memoized, so your instance references will remain the same until their underlying data in the store has changed.This is important for optimising React using PureComponent, which only compares props by reference.

const bottlesSelector = (state: IBottleStore): Array<Bottle> => state.bottles.map(v => Bottle.fromJSON(v))

瓶子减速器在您的减速器中,您可以使用 Bottle 类作为执行操作的助手,而不是直接在减速器中对数据本身进行所有操作

Bottles reducerIn your reducers you can use the Bottle class as a helper to perform operations, rather than doing everything right here in the reducer directly on the data itself

interface IDrinkAction {
  type: 'drink'
  name: string
  litres: number
}

const bottlesReducer = (state: Array<IBottle>, action: IDrinkAction): Array<IBottle> => {
  switch(action.type) {
    case 'drink':
      // immutably create an array of class instances from current state
      return state.map(v => Bottle.fromJSON(v))
              // find the correct bottle and drink from it (drink returns a new instance of Bottle so is immutable)
              .map((b: Bottle): Bottle => b.name === action.name ? b.drink(action.litres) : b)
                // serialise back to date form to put back in the store
                .map((b: Bottle): IBottle => b.toJSON())
    default:
      return state
  }
}

虽然这个 drink/fill 示例相当简单,并且可以直接在 reducer 中的数据上直接在尽可能多的行中完成,但它说明了使用case 类仍然可以用更真实的术语来表示数据,并且可以使代码更容易理解并使代码更有条理,而不是在视图中使用巨大的减速器和手动计算属性,作为奖励,Bottle 类也很容易可测试.

While this drink/fill example is fairly simplistic, and could be just as easily done in as many lines directly on the data in the reducer, it illustrate's that using case class's to represent the data in more real world terms can still be done, and can make it easier to understand and keep code more organised than having a giant reducer and manually computing properties in views, and as a bonus the Bottle class is also easily testable.

通过始终不变的行为,如果设计正确,您的 React 类的先前状态将继续持有对先前瓶子的引用(在它们自己先前的状态中),因此无需以某种方式跟踪您自己的动画等

By acting immutably throughout, if designed correctly your React class's previous state will continue to hold a reference to your previous bottles (in their own previous state), so there is no need to somehow track that yourself for doing animations etc

这篇关于React 和 Redux 架构问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-18 10:06