reactjs - React/TypeScript: Använder context via HOC
Jag försöker implementera exemplet Consuming Context med en HOC från React-dokumentationen (React 16.3) i TypeScript (2.8) och misslyckas fullständigt. Som referens, koden från Reacts manual:
const ThemeContext = React.createContext('light');
// This function takes a component...
export function withTheme(Component) {
// ...and returns another component...
return function ThemedComponent(props) {
// ... and renders the wrapped component with the context theme!
// Notice that we pass through any additional props as well
return (
<ThemeContext.Consumer>
{theme => <Component {...props} theme={theme} />}
</ThemeContext.Consumer>
);
};
}
Det bästa jag kunde komma på:
export interface ThemeAwareProps {
theme: string;
}
const ThemeContext = React.createContext('light');
export function withTheme<P extends ThemeAwareProps, S>(Component: new() => React.Component<P, S>) {
return function ThemedComponent(props: P) {
return (
<ThemeContext.Consumer>
{theme => <Component {...props} theme={theme} />}
</ThemeContext.Consumer>
);
};
}
class App extends React.Component {
public render() {
return (
<ThemeContext.Provider value={'dark'}>
<ThemedButton/>
</ThemeContext.Provider>
);
}
}
ThemedButton.tsx:
interface ThemedButtonProps extends ThemeAwareProps {
}
interface ThemedButtonState{
}
class ThemedButton extends React.Component<ThemedButtonProps, ThemedButtonState> {
constructor(props: ThemedButtonProps) {
super(props);
}
public render() {
return (
<button className={this.props.theme}/>
)
}
}
export default withTheme(ThemedButton);
Problemet är den sista raden (export default withTheme(ThemedButton)). TypeScript-kompilatorn klagar på att
Argument av typen
typeof ThemedButtonkan inte tilldelas parametern av typennew() => Component<ThemedButtonProps, ThemedButtonState, any>.
Vad missar jag?
Svar #1
Du hade rätt för det mesta, bara med några saknade delar:
För
Component, användReact.ComponentType<Props>, som korrekt accepterar klasskomponenter och funktionella komponenter. Jag antar att att användanew() => ...ensam inte fungerar här eftersom signaturerna inte stämde helt överens.För att utesluta props från
ThemedButtonnär du använder den, måste du använda någon magiskt utseende syntax:
function ThemedComponent(props: Pick<P, Exclude<keyof P, keyof ThemeAwareProps>>)
Här är vad detta gör:
Exclude<keyof P, keyof ThemeAwareProps>betyder "ta nycklarna frånPoch ta sedan bort de nycklar som finns iThemeAwareProps"Pick<P, ...>säger sedan, "frånP, returnera en objekttyp med endast dessa egenskaper"
Genom att kombinera dessa får vi en komponent som accepterar alla props som ThemedButton gör, minus theme-prop, så att vi kan göra <ThemedButton /> utan fel.
Här är hela HOC:
function withTheme<P extends ThemeAwareProps>(Component: React.ComponentType<P>) {
return function ThemedComponent(props: Pick<P, Exclude<keyof P, keyof ThemeAwareProps>>) {
return (
<ThemeContext.Consumer>
{(theme) => <Component {...props} theme={theme} />}
</ThemeContext.Consumer>
)
}
}
Och slutligen, ett bra blogginlägg om ämnet, från vilket jag hämtade det mesta av denna information. Det inkluderar också ett sätt att korta ner Pick<...>-grejerna med en Omit-typ, om du föredrar det.
REDIGERA: Beteendet för rest/spread har förändrats i 3.2, och denna bugg uppstod som en olycklig bieffekt, vilket gjorde att typen av props raderades när den slogs ihop med andra props. Ett för närvarande fungerande tillfälligt fix är att kasta props som P:
return (
<ThemeContext.Consumer>
{(theme) => <Component {...props as P} theme={theme} />}
</ThemeContext.Consumer>
)
