React JS Higher-Order-Component 如果在该函数之外设置状态则不执行该函数

React JS Higher-Order-Component not execute function if state is being set outside that function

我想不出问题标题的合适句子,所以如果标题有点令人困惑,首先我想道歉。

所以我刚刚学习完 React JS 并尝试构建一个应用程序进行练习。在此应用程序中有一个登录表单,如果输入的电子邮件或密码错误,它将显示警报并将字段留空。

使用基本的 React JS 实现一切都很好,但是当我尝试修改我的代码来实现时 higher-order-component,应用程序出现错误,我似乎无法找到问题所在。

所以有问题的是,如果我输入的电子邮件或密码不正确,然后单击登录,那么输入/重试的字段将被清空,但是似乎 字段不接受任何输入 ,它只是 保持空白 无论我在键盘上按什么键,所以我需要刷新页面这些字段可以再次接受我的输入。

这是我的代码:

function ResponseAlert(props){
    if(props.alertStatus!==undefined && props.alertStatus!==null){
        const className = 'alert alert-' + props.alertStatus;
        return <div className={className}>{props.alertMessage}</div>
    }

    return <div></div>;
}
function H2Title(props) {
    return <h2>{props.title}</h2>;
}
class InputText extends React.Component{
    constructor(props){
        super(props);
        this.handleChange = this.handleChange.bind(this);
    }
    handleChange(evt){
        this.props.onInputChange(evt.target.id, evt.target.value);
    }
    render(){
        return (
                <div className="form-group">
                    <label htmlFor={this.props.elemId}>{this.props.label}</label>
                    <input type={this.props.type} className="form-control" name={this.props.elemId} id={this.props.elemId} value={this.props.elemValue} onChange={this.handleChange} required={this.props.required} />
                </div>
            );
    }
}

function withSetStateFormData(WrappedComponents, componentName){
    return class extends React.Component{
        constructor(props){
            super(props);
            this.state = { formdata: { email: '', password: '' } };
            this.setStateFormData = this.setStateFormData.bind(this);
        }
        setStateFormData(field, value){
            this.setState(function(state, props){
                let tempData = state.formdata;
                tempData[field] = value;
                return {
                    formdata: tempData
                };
            });
        }
        render(){
            return <WrappedComponents handleInputChange={this.setStateFormData} {...this.props} {...this.state} />
        }
    }
}

class FormLogin extends React.Component {
            constructor(props){
                super(props);
                this.state = {
                    alertstatus: null,
                    alertmessage: '',
                    formdata: this.props.formdata
                };
                this.handleSubmit = this.handleSubmit.bind(this);
            }  
            handleSubmit(evt){
                evt.preventDefault();

                const self = this;
                fetch("http://someurl.com/login.php",
                    {
                       mode: 'cors',
                       method: 'POST',
                       headers: {
                          'Accept': 'application/json',
                          'Content-Type': 'application/json'
                       },
                       body: JSON.stringify(this.state.formdata)
                    })
                    .then(function (response) {
                        if (response.status === 200) {
                            response.json().then(function (resData) {
                                alert('Yeay! Successful login.');
                            });
                        }
                        else{
                            //here is where the problem lies, I think.
                            self.setState({
                                alertstatus: 'danger',
                                alertmessage: 'Login failed. Email or password is incorrect.',
                                formdata: { email: '', password: '' }
                            });
                        }
                    })
                    .catch(function (err) {
                        console.log(err);
                    });

                return false;
            }
            render(){
                const handleInputChange = this.props.handleInputChange;
                return (
                    <div>
                        <ResponseAlert alertStatus={this.state.alertstatus} alertMessage={this.state.alertmessage} />
                        <H2Title title="Login"/>
                        <form onSubmit={this.handleSubmit}>
                            <InputText type="email" elemId="email" label="Email Address" elemValue={this.state.formdata.email} onInputChange={handleInputChange} required={true}/>

                            <InputText type="password" elemId="password" label="Password" elemValue={this.state.formdata.password} onInputChange={handleInputChange} required={true}/>

                            <button type='submit' className='btn btn-primary'>Login</button>
                        </form>
                    </div>
                  );
                }
}

const EnhancedComponent = withSetStateFormData(FormLogin, "login");
ReactDOM.render(<EnhancedComponent />, document.querySelector('#app'));

所以我认为在执行 setState 函数以清空电子邮件和密码后,onChange 事件以某种方式拒绝在输入更改时更新字段。

任何人请告诉我是什么导致了这种行为?

我只是想说,你不需要在这里使用更高阶的组件,但是,你尝试做的是可以工作的。

我发现有几个问题。

首先,您根据 FormLogin 状态设置 InputText value。但是,这很好,当 InputText 更改时,它会调用 onInputChange,后者从高阶组件设置为 setStateFormData。问题是 setStateFormData 设置高阶组件的状态,而不是 FormLogin 的状态。所以结果是 FormLogin 状态没有更新,这意味着 InputTextvalue 没有更新。

我知道你在想什么,"But the input does update before I click submit"。是的,也不是。

这给我们带来了第二个问题,它与 JS 引用有关。

来自 React 文档

state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.

如果我们查看 setStateFormData,您在技术上直接改变 state,方法是首先使用 let tempData = state.formdata; 引用 state 中的对象,然后使用 [=30] 修改引用的对象=].这是 React 中的一个大禁忌,因为它会导致各种令人困惑的错误,就像这个一样。

您可以从旧状态构建新状态,但您只能复制值,不能复制引用。我们可以通过重写 setStateFormData 来解决这个问题,如下所示:

setStateFormData(field, value){
  this.setState(function(state, props){
    let newFormData = {
      email: state.formdata.email;
      password: state.formdate.password;
    };

    newFormData[field] = value;

    return {
      formdata: newFormData
    };
  });
}

现在,我们正在从旧状态深度复制 formdata。太好了,一个错误。如果我们再次测试应用程序,现在我们会看到输入在提交之前或之后都不会更新。这是因为上面提到的第一个问题。

之前,InputText value 似乎正在更新,因为 FormLogin 状态似乎正在更新,但是 FormLogin 中的 state.formdata 只是一个在 FormLogin 的构造函数中使用 formdata: this.props.formdata 设置的高阶组件中对 state.formdata 的引用。这意味着当 setStateFormData 直接改变高阶组件的状态时,它也直接静音了 FormLogin 的状态。这使它看起来一切正常,但实际上只是因为引用。一旦引用丢失,应用程序就会崩溃。那么,我们什么时候失去了参考?如您所料,当我们在 handleSubmit.

中调用 setState
self.setState({
  alertstatus: 'danger',
  alertmessage: 'Login failed. Email or password is incorrect.',
  formdata: { email: '', password: '' }
});

这将 FormLogin 中的 state.formdata 分配给了一个新对象,打破了对高阶组件中 state.formdata 的引用。

好的,那么如何解决呢。有几种方法。

我建议将 formdataFormLogin 的状态中完全移除。这样您就不必担心保持两个 formdata 对象同步。只需在高阶组件上创建一个 clearFormData 函数并将其传递给 FormLogin 以调用 handleSubmit。另外,需要根据FormLogin的道具设置TextInputvalue。我认为这是最干净的解决方案,同时仍然使用高阶组件。

最简单的解决方案当然是摆脱高阶组件,但如果您的目标是专门了解高阶组件,我不确定这是否是一个选项。

最后,您可以在 FormLogin 上实现一个 getDerivedStateFromProps 函数,它会在每次其 props 更改时重建 FormLogin 状态,这恰好发生在每次高阶状态更改时。这是更新 FormLogin 中的 formdata 的一种干净的方法,每当它在高阶组件中发生变化时。问题是你仍然需要担心每次 FormLogin.

变化时更新高阶组件中的 formdata

希望对您有所帮助。