Angular 中充满空值的 Redux 初始状态
Redux initial state full of null values in Angular
我已经在一个项目上工作了大约 10 个月,它基于 Angular 4+ 和 Redux(通过 angular-redux/store)。这个项目基本上是成功的,它从一月份开始投入生产,没有遇到任何严重的问题。
但是...我对 Redux 逐渐感到失望的一个问题我认为至少很烦人,但可能不利于维护:初始状态 和 null
价值观,所以,我想找出我做错了什么 - 或者如果这是 Redux 设计的问题 本身 .
如何复制它
这是一个简单而好的例子:https://github.com/marinho/example-app
您只需克隆,npm install
它,然后 运行 npm run ng serve
并在浏览器上加载 localhost:4200。然后转到 Console 并过滤“111”以查看两行:
11111 undefined
11112 null
这是由两个可观察订阅打印的,它们分别在 myUndefined
和 myObjects.country
上观察值。第一个是无效密钥(未在全局状态上初始化),第二个是使用 null
.
初始化的
这些订阅的更精确位置在这里:https://github.com/marinho/example-app/blob/master/src/app/component.ts#L15
为什么会这样
众所周知,在 Redux 上,必须 configureStore
有一个初始状态。这样的初始状态是一棵大树,其中包含所有可能的键,由它们的缩减器初始化。到初始化时,没有任何操作是 运行,reducer 都没有任何有效载荷要解析,因此在大多数情况下它们默认为 null
。
到这里为止,标准 Redux/Angular,据我所知。
现在,我的两个订阅(myUndefined$
和myObjects$
)观察到AnonymousSubject
个实例,它们是angular-redux/store[=80提供的通道=] 发出在关键路径上更新的新值(即 @select(['myObjects', 'city'])
表示 <AppState>.myObjects.city
)。
问题是,随着初始状态的初始化,所有已知状态键上都有新值,但几乎所有的都被分配了null
。
解决方案还是破解?
一旦有订阅监听这些键中的任何一个,这样的 null
将作为第一个值发出,这使我需要在太多地方进行空检查,例如 .filter(x => x !== null)
或类似的。真丑。
另一种方法是使用 Epics(来自 redux-observables ),但它们必须经过精心设计,并且结果闻起来很复杂脆弱的堆栈不那么可靠。
我的印象是,这不仅是 angular-redux/store 本身的问题,而且实际上是具有全局状态的副作用。
一个简单的解决方案可能是将 angular-redux/store 修复为仅在密钥不再 undefined
时才开始发出值,因此,避免初始化密钥使用 null
并且只是更喜欢根本不初始化它们。但同样,如果这是 Redux 的问题,我不确定这是否是正确的方法。
那么,有没有人有同样的经历或者有一个优雅的解决方案?
在我看来,这是 Redux 固有的。从概念上讲,Redux 是一种架构,它鼓励您将 UI 视为在任何给定时间对整个状态的纯粹计算。您的整个应用程序是一个大型状态机。因此,如果您设置 null
的初始值,则该选择的第一个值将为 null
。如果您根本不为字段设置初始值,则相应选择的第一个值将是 undefined
.
这样,Redux 在您的应用程序中有点 'flattens time' 并将其分解。只有当前状态和下一步操作。这个基本假设使调试工具如此强大。
我们可以让我们的 Observables 'wait until the first non-nil value' 直到触发,但这正是我个人使用 Redux 来避免的那种时间依赖性。我的感觉是,在 Angular-Redux/store 库中进行此更改可能不是正确的选择,因为其他人可能没有预料到它。我也不确定它会对时间旅行和其他调试工具产生什么影响。
那么在您的情况下,有哪些更简洁的方法来处理这些情况?
一般我会考虑以下其中一项:
1) 确保你的减速器总是有一个初始状态。
这适用于您确实具有合理的密钥前期值的情况。所以对于任何减速器控制state.myUndefined
,你做
const INITIAL_VALUE = 'some-reasonable-value';
const myUndefinedReducer = (state = 'some-reasonable-value', action) => {
// etc.
};
通过这种方式,您可以在 reducer 本身中对默认值进行编码。
2) 选择时处理
这适用于不直接绑定到商店的选择 属性(例如,它们是某些 RxJS 计算的结果)。它也适用于密钥初始值是动态的情况,例如一个 undefined
的值,直到 API 调用将其填入或类似。
正如您所指出的,这基本上归结为在多个地方进行过滤,这可能会很冗长。但是在您的示例中,您可以使用 select$
and a Transformer
:
更干净地实现它
一些实用程序文件:
export const skipNil = (obs$: Observable<any>): Observable<any> =>
obs$.filter(v => v !== null && v !== undefined);
Component.ts:
@select$('myUndefined', skipNil) myUndefined$: Observable<any>;
@select$(['myObjects', 'country'], skipNil) country$: Observable<any>;
我已经在一个项目上工作了大约 10 个月,它基于 Angular 4+ 和 Redux(通过 angular-redux/store)。这个项目基本上是成功的,它从一月份开始投入生产,没有遇到任何严重的问题。
但是...我对 Redux 逐渐感到失望的一个问题我认为至少很烦人,但可能不利于维护:初始状态 和 null
价值观,所以,我想找出我做错了什么 - 或者如果这是 Redux 设计的问题 本身 .
如何复制它
这是一个简单而好的例子:https://github.com/marinho/example-app
您只需克隆,npm install
它,然后 运行 npm run ng serve
并在浏览器上加载 localhost:4200。然后转到 Console 并过滤“111”以查看两行:
11111 undefined
11112 null
这是由两个可观察订阅打印的,它们分别在 myUndefined
和 myObjects.country
上观察值。第一个是无效密钥(未在全局状态上初始化),第二个是使用 null
.
这些订阅的更精确位置在这里:https://github.com/marinho/example-app/blob/master/src/app/component.ts#L15
为什么会这样
众所周知,在 Redux 上,必须 configureStore
有一个初始状态。这样的初始状态是一棵大树,其中包含所有可能的键,由它们的缩减器初始化。到初始化时,没有任何操作是 运行,reducer 都没有任何有效载荷要解析,因此在大多数情况下它们默认为 null
。
到这里为止,标准 Redux/Angular,据我所知。
现在,我的两个订阅(myUndefined$
和myObjects$
)观察到AnonymousSubject
个实例,它们是angular-redux/store[=80提供的通道=] 发出在关键路径上更新的新值(即 @select(['myObjects', 'city'])
表示 <AppState>.myObjects.city
)。
问题是,随着初始状态的初始化,所有已知状态键上都有新值,但几乎所有的都被分配了null
。
解决方案还是破解?
一旦有订阅监听这些键中的任何一个,这样的 null
将作为第一个值发出,这使我需要在太多地方进行空检查,例如 .filter(x => x !== null)
或类似的。真丑。
另一种方法是使用 Epics(来自 redux-observables ),但它们必须经过精心设计,并且结果闻起来很复杂脆弱的堆栈不那么可靠。
我的印象是,这不仅是 angular-redux/store 本身的问题,而且实际上是具有全局状态的副作用。
一个简单的解决方案可能是将 angular-redux/store 修复为仅在密钥不再 undefined
时才开始发出值,因此,避免初始化密钥使用 null
并且只是更喜欢根本不初始化它们。但同样,如果这是 Redux 的问题,我不确定这是否是正确的方法。
那么,有没有人有同样的经历或者有一个优雅的解决方案?
在我看来,这是 Redux 固有的。从概念上讲,Redux 是一种架构,它鼓励您将 UI 视为在任何给定时间对整个状态的纯粹计算。您的整个应用程序是一个大型状态机。因此,如果您设置 null
的初始值,则该选择的第一个值将为 null
。如果您根本不为字段设置初始值,则相应选择的第一个值将是 undefined
.
这样,Redux 在您的应用程序中有点 'flattens time' 并将其分解。只有当前状态和下一步操作。这个基本假设使调试工具如此强大。
我们可以让我们的 Observables 'wait until the first non-nil value' 直到触发,但这正是我个人使用 Redux 来避免的那种时间依赖性。我的感觉是,在 Angular-Redux/store 库中进行此更改可能不是正确的选择,因为其他人可能没有预料到它。我也不确定它会对时间旅行和其他调试工具产生什么影响。
那么在您的情况下,有哪些更简洁的方法来处理这些情况?
一般我会考虑以下其中一项:
1) 确保你的减速器总是有一个初始状态。
这适用于您确实具有合理的密钥前期值的情况。所以对于任何减速器控制state.myUndefined
,你做
const INITIAL_VALUE = 'some-reasonable-value';
const myUndefinedReducer = (state = 'some-reasonable-value', action) => {
// etc.
};
通过这种方式,您可以在 reducer 本身中对默认值进行编码。
2) 选择时处理
这适用于不直接绑定到商店的选择 属性(例如,它们是某些 RxJS 计算的结果)。它也适用于密钥初始值是动态的情况,例如一个 undefined
的值,直到 API 调用将其填入或类似。
正如您所指出的,这基本上归结为在多个地方进行过滤,这可能会很冗长。但是在您的示例中,您可以使用 select$
and a Transformer
:
一些实用程序文件:
export const skipNil = (obs$: Observable<any>): Observable<any> =>
obs$.filter(v => v !== null && v !== undefined);
Component.ts:
@select$('myUndefined', skipNil) myUndefined$: Observable<any>;
@select$(['myObjects', 'country'], skipNil) country$: Observable<any>;