NgRx 选择器和柯里化

huangapple go评论64阅读模式
英文:

NgRx selectors and currying

问题

我已经使用 NgRx 有一段时间了,试图理解在 这个 NgRx 文档 页面描述的用于创建选择器的函数式语言特性或设计模式。作为一个面向对象编程背景的人,看到大量函数而不是简单的调用,这让我感到困惑。

用于创建 NgRx 选择器的函数式语言特性或设计模式是什么?

英文:

I have been using NgRx for a while and trying to understand the functional language feature or design pattern being used to create selectors as described on this NgRx documentation page. Coming from a Object Oriented Programming background, seeing numerous functions instead of a simple call which takes a bunch of attributes has confused me.

What functional language feature or design pattern being used to create NgRx selectors?

答案1

得分: 1

我猜最好的解释是,这个解决方案的目的是强制执行一件事:单一数据源

// 选择用户并且不提供任何选项来修改它所取的状态片段
export const selectUser = (state: AppState) => state.selectedUser;
// 同样的情况适用于这里
export const selectAllBooks = (state: AppState) => state.allBooks;
 
// 即使是组合选择器也只能使用当前状态。
// 是的,内部有一些逻辑,但是提供相同的输入,你将得到相同的结果。
export const selectVisibleBooks = createSelector(
  selectUser,
  selectAllBooks,
  (selectedUser: User, allBooks: Book[]) => {
    if (selectedUser && allBooks) {
      return allBooks.filter((book: Book) => book.userId === selectedUser.id);
    } else {
      return allBooks;
    }
  }
);

如果你有可能向选择器提供一个 state 或在组合选择器的情况下提供 partial selectors,你如何确保在选择之前没有对它们进行修改呢?

想象这样的情况:

// 当前状态:
{
  user: {
    id: 1,
    name: 'John Doe'
  }
  books: [
    { id: 100, userId: 1, name: 'Exiting Vim - eventually' },
    { id: 200, userId: 2, name: 'Z-index: 10000000; - Real world CSS' }
  ]
}

// 组件 A

// 使用选择器像这样,你可以确保以可预测的方式获取当前用户的可见书籍。
this.result = this.store.select(selectVisibleBooks);

// 组件 B

// 现在想象一下,你有能力提供用户;
const user = this.store.select(selectUser);
this.result = this.store.select(selectVisibleBooks(user));

// 什么会阻止你这样做呢?
const oldUser = this.store.select(selectUser);
const user = { ...oldUser, id: 3 };
this.result = this.store.select(selectVisibleBooks(user));

// 组件 C
// 同样的事情,但是想象一下你向选择器提供了自己版本的状态
const oldState = this.store.select(all); // 假设,想象你选择了所有
const state = { ...oldState, user: { id: 4, name: 'Jane Doe' }}
this.result = this.store.select(state, selectVisibleBooks);

// "相同的选择器" 会根据参数返回不同的结果,破坏了 Redux/NgRx 的“可预测性”原则。

正如我所说,我一直认为这是一种强制执行单一数据源的方式。这是通过柯里化选择器来实现的,不允许你在选择所需的状态片段时提供修改后的状态或片段。

如果你考虑带有 props 的选择器 - 如今的 [Factory Selectors][1],它们仍然只允许你发送一个参数来修改选择,但它们仍然依赖于柯里化函数,不允许你提供状态本身。


  [1]: https://timdeschryver.dev/blog/parameterized-selectors


<details>
<summary>英文:</summary>

I guess the best explanation would be, that this solution is there to enforce one thing: **Single source of truth**.

```lang-typescript
// Selects user and provides no option to somehow modify the state from which it takes the slice
export const selectUser = (state: AppState) =&gt; state.selectedUser;
// Same here
export const selectAllBooks = (state: AppState) =&gt; state.allBooks;
 
// Even combined selectors work only with current state. 
// Yes, there is some logic inside, but provide same inputs and you&#39;ll get same results.
export const selectVisibleBooks = createSelector(
  selectUser,
  selectAllBooks,
  (selectedUser: User, allBooks: Book[]) =&gt; {
    if (selectedUser &amp;&amp; allBooks) {
      return allBooks.filter((book: Book) =&gt; book.userId === selectedUser.id);
    } else {
      return allBooks;
    }
  }
);

If you had the possibility to provide a state to the selector, or to provide partial selectors in case of the combined ones, how would you be sure that none of them were modified prior to selecting?

Imagine a situation like this:

//current state:
{
  user: {
    id: 1,
    name: &#39;John Doe&#39;
  }
  books: [
    { id: 100, userId: 1, name: &#39;Exiting Vim - eventually&#39; },
    { id: 200, userId: 2, name: &#39;Z-index: 10000000; - Real world CSS&#39; }
  ]
}

// Component A

// Using selector like this, you are sure, that you&#39;ll get visible books
// for current user. In a predictable manner
this.result = this.store.select(selectVisibleBooks);

// Component B

// Now imagine you had the ability to provide user;
const user = this.store.select(selectUser);
this.result = this.store.select(selectVisibleBooks(user));

// What would stop you from doing this?
const oldUser = this.store.select(selectUser);
const user = { ...oldUser, id: 3 };
this.result = this.store.select(selectVisibleBooks(user));

// Component C
// Same thing, but imagine you provided your own version of state to the selector
const oldState = this.store.select(all); // Fake, imagine you&#39;re selecting everything
const state = { ...oldState, user: { id: 4, name: &#39;Jane Doe&#39; }}
this.result = this.store.select(state, selectVisibleBooks);

// The &quot;same selector&quot; would return different things, depending on parameters, 
// breaking the &#39;Predictability&#39; principle of the Redux/NgRx

As I said, I always took it as a form of enforcing the single source of truth. This is achieved by currying the selectors, not giving you the ability to provide modified state or slice while selecting desired slice of the state.

And if you think of selectors with props - nowadays Factory Selectors, they still only allow you to send a parameter to modify the selection, but they still rely on curried functions, not allowing you to provide the state itself.

huangapple
  • 本文由 发表于 2023年7月4日 21:54:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76613369.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定