我希望组件的用户能够传递组件的对象,并且希望允许他们使用基于功能或基于类的组件。
type InputComponents = Record<string, React.ComponentType>
这允许使用FC或类组件,但是我没有找到有关如何呈现它们的明确指南。
const inputComponents = {
a: ({ placeholder }) => ( // Functional component
<input
type="text"
value={state.a}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, a: value }));
}}
/>
),
b: InputClass // Class component
};
这两个组件都能很好地呈现,而Typescript不会这样抱怨:
const A = inputComponents.a;
const B = inputComponents.b;
<A placeholder="Type something" />
<B placeholder="Type something" />
但这实际上是令人误解的-功能键(a)将使每次按键失去焦点。
使功能组件每次按键都不会失去焦点的唯一方法是这样的:
inputComponents.a({ placeholder: 'Type something' })
Typescript甚至不认为这是一种呈现组件的有效方法,但是它是唯一可以完全正常工作的方法.....它错误提示“此表达式不可调用”。并且对于Class组件也失败,所以我必须这样做:
// Render as JSX for class components and call as a function for FC ones...
Component.prototype.isReactComponent ? <Component placeholder={x} /> : Component({ placeholder: x })
您可以在此处查看实际问题:
function SomeComponent({ inputComponents }) {
const B = inputComponents.b;
const C = inputComponents.c;
return (
<div className="SomeComponent">
<p>This FC component doesn't loose focus:</p>
{inputComponents.a({ placeholder: 'Type something' })}
<p>This one does:</p>
<B placeholder="Type something" />
<p>Rendering a class component as JSX works though:</p>
<C placeholder="Type something" />
</div>
);
}
class InputClass extends React.Component {
state = {
value: ''
};
render() {
return (
<input
type="text"
value={this.state.value}
placeholder={this.props.placeholder}
onChange={(e) => {
this.setState({ value: e.currentTarget.value });
}}
/>
);
}
}
function App() {
const [state, setState] = React.useState({
a: '',
b: ''
});
const inputComponents = {
a: ({ placeholder }) => (
<input
type="text"
value={state.a}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, a: value }));
}}
/>
),
b: ({ placeholder }) => (
<input
type="text"
value={state.b}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, b: value }));
}}
/>
),
c: InputClass
};
return (
<div className="App">
<SomeComponent inputComponents={inputComponents} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
React坏了吗?还是不依靠破解TS错误和使用诸如isReactComponent
之类的内部内容来处理此问题的正确方法是什么?谢谢
答案 0 :(得分:2)
问题在于,由于在inputComponents
中有App
,因此在每个重新渲染上都将创建一个新组件(函数本身)。如果将类声明移至内部作用域,则类组件也会发生相同的行为
inputComponent = {
c: class InputClass extends Component {}
}
要解决该问题,您可以将组件映射移动到外部范围,并传递state
和setState
作为道具。或通过上下文提供。
function SomeComponent({ inputComponents, args }) {
const B = inputComponents.b;
const C = inputComponents.c;
return (
<div className="SomeComponent">
<p>This one does:</p>
<B placeholder="Type something" {...args} />
<p>Rendering a class component as JSX works though:</p>
<C placeholder="Type something" {...args} />
</div>
);
}
class InputClass extends React.Component {
state = {
value: ''
};
render() {
return (
<input
type="text"
value={this.state.value}
placeholder={this.props.placeholder}
onChange={(e) => {
this.setState({ value: e.currentTarget.value });
}}
/>
);
}
}
const inputComponents = {
b: ({ placeholder, state, setState }) => (
<input
type="text"
value={state.b}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, b: value }));
}}
/>
),
c: InputClass
};
function App() {
const [state, setState] = React.useState({
a: '',
b: ''
});
return (
<div className="App">
<SomeComponent inputComponents={inputComponents} args={{state, setState}} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.development.min.js"></script>
<div id="app"></div>