使用React的新上下文API,您可以像这样创建类型化的上下文生产者/消费者:
type MyContextType = string;
const { Consumer, Producer } = React.createContext<MyContextType>('foo');
但是,说我有一个列出项目的通用组件。
// To be referenced later
interface IContext<ItemType> {
items: ItemType[];
}
interface IProps<ItemType> {
items: ItemType[];
}
class MyList<ItemType> extends React.Component<IProps<ItemType>> {
public render() {
return items.map(i => <p key={i.id}>{i.text}</p>);
}
}
如果我想渲染一些自定义组件作为列表项,并将MyList
的属性作为上下文传递,我该怎么做?甚至有可能吗?
我尝试过的事情:
class MyList<ItemType> extends React.Component<IProps<ItemType>> {
// The next line is an error.
public static context = React.createContext<IContext<ItemType>>({
items: []
}
}
这种方法行不通,因为您无法从静态上下文访问类的类型,这很有意义。
使用标准上下文模式,我们在模块级别(即不在类内部)创建使用者和生产者。这里的问题是,我们必须先创建使用者和生产者,然后才能知道它们的类型参数。
我发现a post on Medium反映了我正在尝试做的事情。交换的关键在于,在知道类型信息之前,我们无法创建生产者/消费者(似乎很明显吧?)。这导致以下方法。
class MyList<ItemType> extends React.Component<IProps<ItemType>> {
private localContext: React.Context<IContext<ItemType>>;
constructor(props?: IProps<ItemType>) {
super(props);
this.localContext = React.createContext<IContext<ItemType>>({
items: [],
});
}
public render() {
return (
<this.localContext.Provider>
{this.props.children}
</this.localContext.Provider>
);
}
}
(也许)这是进展,因为我们可以实例化正确类型的提供者,但是子组件将如何访问正确的使用者?
正如下面的答案所提到的,这种模式是尝试过度抽象的标志,这在React上效果不佳。如果要尝试解决此问题,我将创建一个通用的ListItem
类来封装项目本身。这样,上下文对象可以键入任何形式的ListItem
,而我们不必动态创建使用者和提供者。
答案 0 :(得分:4)
我不知道TypeScript,所以我不能用相同的语言回答,但是如果您希望您的Provider是MyList
类的“特定”对象,则可以在同一函数中创建两者。 / p>
function makeList() {
const Ctx = React.createContext();
class MyList extends Component {
// ...
render() {
return (
<Ctx.Provider value={this.state.something}>
{this.props.children}
</Ctx.Provider>
);
}
}
return {
List,
Consumer: Ctx.Consumer
};
}
// Usage
const { List, Consumer } = makeList();
总体而言,我认为您可能过于抽象。在React组件中大量使用泛型不是很常见的样式,并且可能导致相当混乱的代码。
答案 1 :(得分:4)
我遇到了同样的问题,我想我以一种更为优雅的方式解决了它:
您可以使用lodash once
(或者很容易地创建一个自己)来用泛型类型初始化一次上下文,然后从函数内部调用他,在其余组件中,您可以使用自定义useContext钩子来获取数据:
import React, { useContext } from 'react';
import { once } from 'lodash';
const createStateContext = once(<T,>() => React.createContext({} as State<T>));
export const useStateContext = <T,>() => useContext(createStateContext<T>());
const ParentComponent = <T>(props: Props<T>) => {
const StateContext = createStateContext<T>();
return (
<StateContext.Provider value={[YOUR VALUE]}>
<ChildComponent />
</StateContext.Provider>
);
}
import React from 'react';
import { useStateContext } from './parent-component';
const ChildComponent = <T>(props: Props<T>) => {
const state = useStateContext<T>();
...
}
希望它对某人有帮助
答案 2 :(得分:0)
让我们退后一步;上下文是通用的意味着什么?某些表示上下文生产者一半的组件Producer<T>
可能只提供类型T
的值,对吧?
现在考虑以下几点:
<Producer<string> value="123">
<Producer<number> value={123}>
<Consumer />
</Producer>
</Producer>
这应该如何表现?消费者应该获得什么价值?
Producer<number>
覆盖Producer<string>
(即消费者获得123
),则通用类型不会执行任何操作。在生产者级别将其称为number
并不会强制您在使用时会得到number
,因此指定它存在错误的希望。"123"
),则它们必须来自两个单独的Contexts实例,这些实例特定于它们所拥有的类型。但是然后它们不是通用的!在任何一种情况下,直接将类型传递给Producer
都是没有价值的。这并不是说当Context在起作用时,泛型是无用的...
作为使用通用组件已有一段时间的人,我不认为您的列表示例过于抽象。只是您无法在生产者和消费者之间强制执行类型协议-就像您无法“强制”从Web请求,本地存储或第三方代码中获得的值的类型一样!
最终,这意味着在定义上下文时使用any
之类的东西,而在使用该上下文时指定期望的类型。
const listContext = React.createContext<ListProps<any>>({ onSelectionChange: () => {} });
interface ListProps<TItem> {
onSelectionChange: (selected: TItem | undefined) => void;
}
// Note that List is still generic!
class List<TItem> extends React.Component<ListProps<TItem>> {
public render() {
return (
<listContext.Provider value={this.props}>
{this.props.children}
</listContext.Provider>
);
}
}
interface CustomListItemProps<TItem> {
item: TItem;
}
class CustomListItem<TItem> extends React.Component<CustomListItemProps<TItem>> {
public render() {
// Get the context value and store it as ListProps<TItem>.
// Then build a list item that can call onSelectionChange based on this.props.item!
}
}
interface ContactListProps {
contacts: Contact[];
}
class ContactList extends React.Component<ContactListProps> {
public render() {
return (
<List<Contact> onSelectionChange={console.log}>
{contacts.map(contact => <ContactListItem contact={contact} />)}
</List>
);
}
}