我认为潜在的问题是您不清楚方法链和钩子可能实际发生了什么。具体问题:
可以修改挂钩以从 this 获取状态吗?
真的没有意义。因此,让我们分解为什么,然后在最后返回您如何 解决这个问题。
对于方法链接,让我们尝试一个简单的示例,使用两个方法,.filter 和 .map,它们具有两个重要属性:
- 它们实际上返回数组(不像
.push,它返回数组的新长度);和
- 它们实际上存在于数组中(与
.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.allUppercase 是 undefined 并且 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];
thing、setThing 命名只是一个约定;实际上,我们正在按位置访问这两个对象(当前值和 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;
}
}