【问题标题】:How to chain useState() method from React hook如何从 React 钩子链接 useState() 方法
【发布时间】:2020-07-20 14:48:05
【问题描述】:

是否可以链接 React 钩子?如果有,怎么做?

钩子的典型应用如下所示:

const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
b = a.filter(isDairy)
updateInventory(b)

我们也可以这样做,但不是链式的:

const [inv, updateInventory] = useState([])
a =  ["cheese", "bread", "apples"]
updateInventory(a.filter(isDairy))

我想要的是功能风格的链式钩子:

const [inv, updateInventory] = useState([])
a =  ["cheese", "bread", "apples"]
a.filter(isDairy).updateInventory()

可以修改挂钩以从this 获取状态吗?

【问题讨论】:

  • 您可以导入阶段 1 并使用管道运算符:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…,但我并没有真正看到这样做的价值。
  • 您可以通过修补原型向数组添加任意方法,但我不建议这样做 - 您有多少与数组值相关的钩子?您是否希望它们全部成为数组上的方法?
  • 请注意,如果您决定修补数组(请记住,updateInventory 将在 每个数组 上!)这将变为 stackoverflow.com/q/948358/3001761

标签: reactjs functional-programming react-hooks method-chaining


【解决方案1】:

正确的用法是:

updateInventory([...a, "cheddar"].quicksort().filter("cheese"))

但如果你真的想要这种链接,请查看how to edit the array prototype

确实不建议这样做,因为该方法随后将在 所有 数组上可用。

【讨论】:

  • 我在想[...a, "cheddar"].quicksort().filter("cheese").updateInventory()。基本上我要问的是可以修改钩子以使用this 并支持链接吗?假设.filter("cheese") 方法返回this,是否可以修改updateInventory() 以从this 获取状态?
  • .filter 方法返回一个数组,因此它的this 将是一个数组,因此您必须扩展array 的原型才能按照您的意愿进行链接
  • @kmiklas 正如我几小时前指出的那样,您必须将应用程序中的每个数组都修补为“以使用this 并支持链接”。这与 React 没有任何关系。
  • 这个扩展数组原型的业务太狭隘了;它没有回答这个问题。我可以轻松设置this.arrreturn this。有没有办法把钩子拴起来?
【解决方案2】:

我认为潜在的问题是您不清楚方法链和钩子可能实际发生了什么。具体问题:

可以修改挂钩以从 this 获取状态吗?

真的没有意义。因此,让我们分解为什么,然后在最后返回您如何 解决这个问题。


对于方法链接,让我们尝试一个简单的示例,使用两个方法,.filter.map,它们具有两个重要属性:

  1. 它们实际上返回数组(不像.push,它返回数组的新长度);和
  2. 它们实际上存在于数组中(与 .quicksort 不同,它既不存在于数组也不存在于您调用它的整数上)。
function isDairy(item) {
  return ["cheese", "milk"].includes(item);
}

function getPrice(item) {
  return { bread: 0.58, cheese: 0.80, apples: 0.47, milk: 1.01 }[item];
}

const inStock = ["bread", "cheese", "apples"];

inStock
  .filter(isDairy)
  .map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]

这里没有什么特别的事情发生,您调用的每个方法返回一个新数组,您还可以在该数组上调用数组具有的任何方法。您可以分配中间步骤并获得相同的结果:

const filteredStock = stock.filter(isDairy);
// => ["cheese"]
const pricedFilteredStock = filteredStock.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]

不是的情况是:

  • 这些是独立的函数(例如在 Python 中你 map(callable, iterable));或
  • item.name 语法除了访问item 上名为name 的属性外,还可以做任何事情。

如果我尝试将filter 方法用作独立函数:

filter(isDairy, inStock);

这将是一个 ReferenceError,或者如果我定义了另一个函数并尝试访问它,就好像它是一个数组上的一个道具:

function allUppercase() { 
  return this.map((item) => item.toUpperCase());
}

inStock.allUppercase();

这将是一个 TypeError(因为 isStock.allUppercaseundefined 并且 undefined 是不可调用的)。

请注意,您可以使用allUppercase.bind(inStock)()(或更简洁的allUppercase.call(inStock)); JavaScript 确实可以为函数设置this


当您使用useState 挂钩时,您正在调用一个函数,该函数返回一个包含两个对象的数组,并将该数组解构为两个局部变量:

const [thing, setThing] = useState(initialValue);

相当于:

const result = useState(initialValue);
const thing = result[0];
const setThing = result[1];

thingsetThing 命名只是一个约定;实际上,我们正在按位置访问这两个对象(当前值和 setter 函数)。他们没有自己的名字,您可以使用const [foo, bar] = useState("baz")(但...不要)。

由于 setter 是一个函数,您可能想知道是否可以在这里使用 setThing.bind,但如果 setThing 写成使用 this(我没有研究实现,因为它不直接相关),如果您更改 this 是什么,它不会会很高兴!


因此,当您尝试这样做时,这会结合在一起:

const [basket, setBasket] = useState([]);
            // ^^^^^^^^^

inStock.filter(...).map(...).setBasket();
                          // ^^^^^^^^^

与上面的示例一样,这是一个 TypeError,因为 setBasket 不存在于 .map 返回的数组中。就 JavaScript 而言,相同的“词”setBasket 出现两次这一事实完全无关;一个是局部变量,另一个是数组的prop,它们之间没有联系。

.map(...) 返回一个 new 数组,这是我们尚未引用的数组,因此完成这项工作的唯一方法是确保 all 数组有一个setBasket 方法,这意味着修补原型(如adding custom functions into Array.prototype 所述):

Object.defineProperty(Array.prototype, "setBasket", {
  value () {
    setBasket(this);
  },
});

这里的一个问题是 函数 setBasket 是通过闭包访问的,因此它需要发生在定义挂钩的组件内部,因此每次组件都会定义它被渲染(或者你要去useEffect),这是一个问题,因为你不能重新定义那个方法写...

但是让我们忽略这一点,因为更大的问题是 您的应用程序中的每个数组 现在都有该方法,即使在不相关的上下文中也是如此。如果您有多个状态钩子,就像在任何非平凡的应用程序中一样,您的数组将获得许多全局方法,这些方法仅用于小型本地范围。


一种更可行的方法是添加一个通用方法,该方法可用于将任何钩子(实际上是任何函数)应用于数组:

Object.defineProperty(Array.prototype, "andCall", {
  value (func) {
    return func(this);
  },
});

这可以在全局范围内添加一次,并用于应用任何相关的钩子:

inStock.filter(...).map(...).andCall(setBasket);

请注意,如果您使用的是 TypeScript,您还必须将定义添加到全局数组类型,例如:

declare global {
    interface Array<T> {
        andCall<S>(func: (arr: Array<T>) => S): S;
    }
}

【讨论】:

  • 感谢您的这篇文章。我今天下班后读。
猜你喜欢
  • 2020-11-02
  • 2021-05-25
  • 2023-03-19
  • 2021-02-07
  • 1970-01-01
  • 2019-08-06
  • 2021-04-04
  • 2020-04-29
  • 1970-01-01
相关资源
最近更新 更多