JavaScript/ReactJS中多态性的FP替代方案
我目前正在做一个ReactJS项目,我需要创建“可重用”组件,其中一些方法需要“重写”。在OOP中,我将使用多态性。我读了一些书,似乎大家都同意使用HoC/composition,但我不太明白如何实现这一点。我想,如果我可以使用composition获得一个ES6样本,那么之后将想法应用于ReactJS可能会更容易 下面是一个ES6 OOP示例(忽略事件处理,这只是为了测试),它几乎是我希望在ReactJS中实现的。是否有人对如何将ReactJS组件分解成一个HoC组件有一些指导,或者仅仅演示如何根据示例在ES6中使用compositionJavaScript/ReactJS中多态性的FP替代方案,javascript,reactjs,ecmascript-6,functional-programming,Javascript,Reactjs,Ecmascript 6,Functional Programming,我目前正在做一个ReactJS项目,我需要创建“可重用”组件,其中一些方法需要“重写”。在OOP中,我将使用多态性。我读了一些书,似乎大家都同意使用HoC/composition,但我不太明白如何实现这一点。我想,如果我可以使用composition获得一个ES6样本,那么之后将想法应用于ReactJS可能会更容易 下面是一个ES6 OOP示例(忽略事件处理,这只是为了测试),它几乎是我希望在ReactJS中实现的。是否有人对如何将ReactJS组件分解成一个HoC组件有一些指导,或者仅仅演示如
类传输组件{
构造函数(){
让timeout=null;
这个。render();
此为.events();
}
事件(){
让范围=这个;
document.getElementById('button')。addEventListener('click',function(){
范围。验证。应用(范围);
});
}
验证(){
if(this.isValid()){
这个是.ajax();
}
}
isValid(){
if(document.getElementById('username').value!=''){
返回true;
}
返回false;
}
ajax(){
clearTimeout(this.timeout);
document.getElementById('message').textContent='Loading…';
this.timeout=setTimeout(函数(){
document.getElementById('message').textContent='Success';
}, 500);
}
render(){
document.getElementById('content')。innerHTML='\n\
验证';
}
}
类重写TransferComponent扩展TransferComponent{
isValid(){
if(document.getElementById('username').value!=''&&document.getElementById('password').value!=''){
返回true;
}
返回false;
}
render(){
document.getElementById('content')。innerHTML='\n\
\n\
验证';
}
}
const overrideTransferComponent=新的overrideTransferComponent()代码>
阅读您的问题,不清楚您指的是组合还是继承,但它们是不同的OOP概念。如果你不知道它们之间的区别,我建议你看看
关于您的具体问题,请回复。我建议您尝试使用用户组合,因为它为您构建UI和传递道具提供了很大的灵活性
例如,如果您正在使用React,则在动态填充对话框时可能已经在使用合成。如图所示:
您可以导入它们并在组件中使用。非反应FP示例
首先,在函数式编程中,函数是一等公民。这意味着您可以像对待OOP中的数据一样对待函数(即作为参数传递、分配给变量等)
您的示例将数据与对象中的行为混合在一起。为了编写一个纯功能性的解决方案
函数式编程从根本上讲就是将数据与行为分离
那么,让我们从isValid
开始
函数是有效的
这里有几种方法可以对逻辑进行排序,但我们将使用以下方法:
给出了一个ID列表
如果不存在无效id,则所有id均有效
在JS中,这可以翻译为:
const areAllElementsValid = (...ids) => !ids.some(isElementInvalid)
我们需要一对助手函数来实现这一点:
const isElementInvalid = (id) => getValueByElementId(id) === ''
const getValueByElementId = (id) => document.getElementById(id).value
我们可以把所有的内容都写在一行上,但是把它分解会让它更可读。这样,我们就有了一个通用函数,可以用来确定组件的isValid
areAllElementsValid('username') // TransferComponent.isValid
areAllElementsValid('username', 'password') // TransferOverrideComponent.isValid
功能渲染
我在isValid
上用document
作弊了一点。在真正的函数式编程中,函数应该是纯函数。或者,换句话说,函数调用的结果只能由其输入确定(也就是说,它是幂等的),并且不能有
那么,我们如何在没有副作用的情况下渲染DOM呢?React为核心库使用虚拟DOM(一种驻留在内存中的奇特数据结构,通过传入函数和从函数返回来保持函数的纯度)。React的副作用存在于React dom
库中
对于我们的例子,我们将使用超级简单的虚拟DOM(类型为string
)
这可能看起来过于简单,根本不起作用。但是+
操作符实际上是功能性的!想想看:
- 它接受两个输入(左操作数和右操作数)
- 它返回一个结果(对于字符串,是操作数的串联)
- 它没有副作用
- 它不改变输入(结果是一个新字符串——操作数不变)
因此,render
现在可以正常工作了
那ajax呢?
不幸的是,在没有副作用的情况下,我们无法执行ajax调用、修改DOM、设置事件侦听器或设置超时。我们可以通过复杂的方式为这些操作创建monad,但是出于我们的目的,我们只需要继续使用非函数方法就足够了
在React中应用
下面是使用常见反应模式对示例的重写。我正在使用表单输入。我们所讨论的大多数函数概念实际上都是在React中实现的,所以这是一个非常简单的实现,不使用任何花哨的东西
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
success: false
};
}
handleSubmit() {
if (this.props.isValid()) {
this.setState({
loading: true
});
setTimeout(
() => this.setState({
loading: false,
success: true
}),
500
);
}
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
{this.props.children}
<input type="submit" value="Submit" />
</form>
{ this.state.loading && 'Loading...' }
{ this.state.success && 'Success' }
</div>
);
}
}
您也可以编写另一个表单
,但输入不同
class CommentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
comment: ''
};
}
isValid() {
return this.state.comment !== '';
}
handleCommentChange(event) {
this.setState({ comment: event.target.value });
}
render() {
return (
<Form
validate={this.isValid}
>
<input value={this.state.comment} onChange={this.handleCommentChange} />
</Form>
);
}
}
最后,我们使用ReactDOM
而不是innerHTML
ReactDOM.render(
<App />,
document.getElementById('content')
);
ReactDOM.render(
很好地覆盖这个
为了进一步阅读,James K.Nelson收集了一些关于React的重要资源,这些资源应该有助于您理解功能:关于示例代码的答案在本文的中间/底部
去abo的好方法
USERNAME_INPUT + VALIDATE_BUTTON // TransferComponent.render
USERNAME_INPUT + PASSWORD_INPUT + VALIDATE_BUTTON // TransferOverrideComponent.render
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
success: false
};
}
handleSubmit() {
if (this.props.isValid()) {
this.setState({
loading: true
});
setTimeout(
() => this.setState({
loading: false,
success: true
}),
500
);
}
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
{this.props.children}
<input type="submit" value="Submit" />
</form>
{ this.state.loading && 'Loading...' }
{ this.state.success && 'Success' }
</div>
);
}
}
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
}
isValid() {
return this.state.username !== '' && this.state.password !== '';
}
handleUsernameChange(event) {
this.setState({ username: event.target.value });
}
handlePasswordChange(event) {
this.setState({ password: event.target.value });
}
render() {
return (
<Form
validate={this.isValid}
>
<input value={this.state.username} onChange={this.handleUsernameChange} />
<input value={this.state.password} onChange={this.handlePasswordChange} />
</Form>
);
}
}
class CommentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
comment: ''
};
}
isValid() {
return this.state.comment !== '';
}
handleCommentChange(event) {
this.setState({ comment: event.target.value });
}
render() {
return (
<Form
validate={this.isValid}
>
<input value={this.state.comment} onChange={this.handleCommentChange} />
</Form>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<LoginForm />
<CommentForm />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('content')
);
const App = () => <Delegator ImplementationComponent={ImplementationB} />;
class Delegator extends React.Component {
render() {
const { ImplementationComponent } = this.props;
return (
<div>
<ImplementationComponent>
{ ({ doLogic }) => {
/* ... do/render things based on doLogic ... */
} }
</ImplementationComponent>
</div>
);
}
}
class ImplementationA extends React.Component {
doSomeLogic() { /* ... variation A ... */ }
render() {
this.props.children({ doLogic: this.doSomeLogic })
}
}
class ImplementationB extends React.Component {
doSomeLogic() { /* ... variation B ... */ }
render() {
this.props.children({ doLogic: this.doSomeLogic })
}
}
class Delegator extends React.Component {
render() {
const { ImplementationComponent, AnotherImplementation, SomethingElse } = this.props;
return (
<div>
<ImplementationComponent>
{ ({ doLogic }) => { /* ... */} }
</ImplementationComponent>
<AnotherImplementation>
{ ({ doThings, moreThings }) => { /* ... */} }
</AnotherImplementation>
<SomethingElse>
{ ({ foo, bar }) => { /* ... */} }
</SomethingElse>
</div>
);
}
}
const App = () => (
<div>
<Delegator
ImplementationComponent={ImplementationB}
AnotherImplementation={AnotherImplementation1}
SomethingElse={SomethingVariationY}
/>
<Delegator
ImplementationComponent={ImplementationC}
AnotherImplementation={AnotherImplementation2}
SomethingElse={SomethingVariationZ}
/>
</div>
);
<div id="content-inputs"></div>
<div id="content-button"></div>
export default class TransferComponent extends React.Component {
constructor() {
super();
this.displayDOMButton = this.displayDOMButton.bind(this);
this.onButtonPress = this.onButtonPress.bind(this);
}
ajax(){
console.log('doing some ajax')
}
onButtonPress({ isValid }) {
if (isValid()) {
this.ajax();
}
}
displayDOMButton({ isValid }) {
document.getElementById('content-button').innerHTML = (
'<button id="button" type="button">Validate</button>'
);
document.getElementById('button')
.addEventListener('click', () => this.onButtonPress({ isValid }));
}
render() {
const { VaryingComponent } = this.props;
const { displayDOMButton } = this;
return (
<div>
<VaryingComponent>
{({ isValid, displayDOMInputs }) => {
displayDOMInputs();
displayDOMButton({ isValid });
return null;
}}
</VaryingComponent>
</div>
)
}
};
export default class UsernameComponent extends React.Component {
isValid(){
return document.getElementById('username').value !== '';
}
displayDOMInputs() {
document.getElementById('content-inputs').innerHTML = (
'<input type="text" id="username" value="username"/>'
);
}
render() {
const { isValid, displayDOMInputs } = this;
return this.props.children({ isValid, displayDOMInputs });
}
}
export default class UsernamePasswordComponent extends React.Component {
isValid(){
return (
document.getElementById('username').value !== '' &&
document.getElementById('password').value !== ''
);
}
displayDOMInputs() {
document.getElementById('content-inputs').innerHTML = (
'<input type="text" id="username" value="username"/>\n\
<input type="text" id="password" value="password"/>\n'
);
}
render() {
const { isValid, displayDOMInputs } = this;
return this.props.children({ isValid, displayDOMInputs });
}
}
<TransferComponent VaryingComponent={UsernameComponent} />
<TransferComponent VaryingComponent={UsernamePasswordComponent} />