【发布时间】:2020-01-17 19:57:13
【问题描述】:
我的 React+TypeScript 应用程序有一个组件,其 props 类型是可区分的联合。这有助于防止在编译时无效的道具组合。但是,如果我需要将其中一些道具传递到 React Hook 的依赖列表中,那么正确的方法是什么?钩子规则说我不能在条件中调用钩子,但是如果它们 not 在条件(又名类型保护)内,区分的道具将导致 TS 编译器错误。
我考虑过以下变通方法,所有这些似乎都会降低我的代码的可读性和/或安全性:
- 拆分为多个组件并将挂钩推送到每个实现中。这会重复很多代码。
- 将每个已识别的 prop 提取到局部变量中,例如
const values = p.isMulti === true && p.values并在依赖项列表中使用它。除了看起来不必要的冗长之外,这种方法还可以让以后的代码绕过 TypeScript 的类型检查。 - 在依赖列表中,将普通属性访问(例如
p.value)替换为类型安全的表达式,例如p.isMulti === true && p.values。这里的问题是 React 的穷举依赖 ESLint 规则抱怨(“React Hook React.useMemo 在依赖数组中有一个复杂的表达式。将它提取到一个单独的变量中,以便对其进行静态检查。”)因为这些表达式不是静态可测试的。我必须禁用我想避免的穷举规则,因为以后的问题不会被 ESLint 捕获。 - 在依赖列表中添加类型转换以消除 TS 错误。这会触发来自 ESLint 的详尽的 deps 投诉,就像上面的类型保护表达式一样。
- 更改可区分联合以使所有道具都显示在两个分支中,但要求“错误”分支的值始终为
readonly和undefined。这将解决钩子问题,但会降低组件的类型安全性。 - 重构组件以删除鉴别器道具,而是将联合化下推到每个道具中。这在下面的人为示例中很容易(例如,单个
values道具可以是string或string[]),但在我的真实应用程序中,联合要复杂得多,所以这个“合并道具”选项会使组件更难正确使用。 - 对 deps 列表中的 props 应用类型转换,例如(
p.value as string | undefined。这似乎是目前最不令人反感的选择。
还有更好的选择吗?
这是一个说明问题的人为示例:https://codesandbox.io/s/interesting-tereshkova-4siy4。代码重复如下。
import * as React from 'react';
import { render } from 'react-dom';
type Props =
| {
isMulti: true;
values: string[];
}
| {
isMulti: false;
value: string;
};
const X = (p: Props) => {
return React.useMemo(
() => (
<div>
value(s):
{p.isMulti === true && p.values.join(',')}
{p.isMulti === false && p.value}
</div>
),
[p.isMulti, p.values, p.value], // TS errors on p.value/p.values
);
};
function App() {
return (
<div className="App">
<X isMulti={false} value={'foo'} />
<X isMulti={true} values={['foo', 'bar', 'baz']} />
</div>
);
}
const rootElement = document.getElementById('root');
render(<App />, rootElement);
【问题讨论】:
-
根据我的回答,选项 (5) 是您最好的选择。我还认为它本质上并不比其他任何东西都不安全。如果不检查您的判别属性,您无论如何都不会知道哪个是正确的属性,这与
typeof或instanceof检查没有什么不同。由于您需要useMemo的所有属性,因此您别无选择。
标签: reactjs typescript react-hooks