【问题标题】:React conditionally rendering反应条件渲染
【发布时间】:2019-03-25 00:17:54
【问题描述】:

我有一个从 React 构建的简单表单。提交后,如果错误再次出现,我希望渲染一个额外的 div 以在表单上显示错误。

现在我可以让它工作,但我不喜欢这个解决方案。我的解决方案基于这样的知识,即只有在渲染函数内部的状态发生变化时组件才会重新渲染(在本例中为 this.state.errorMessages)。所以我必须像这样明确地将 if 条件放在渲染函数中

renderError() {
    var errArr = [];
    for (var key in this.state.errorMessages) {
        errArr = [...errArr, ...this.state.errorMessages[key]];
    }
    return (
        <div className="alert alert-danger">
            {errArr.map((err) => {
                return <p>{err}</p>
            })}
        </div>
    )
}

renderForm() {
    return (
        <form onSubmit={this.handleRegisterFormSubmit}>
            <div className="form-group">
                <label>First Name</label>
                <input type="text" className="form-control" name="name" placeholder="Name" value={this.state.firstName} onChange={this.handleFirstNameChange} required/>
            </div>
            <div className="form-group">
                <label>Last Name</label>
                <input type="text" className="form-control" name="lastName" placeholder="Last Name" value={this.state.lastName} onChange={this.handleLastNameChange} required/>
            </div>
            <div className="form-group">
                <label>Email address</label>
                <input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" value={this.state.email} onChange={this.handleEmailChange} />
                <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
            </div>
            <div className="form-group">
                <label>Password</label>
                <input type="password" className="form-control" name="password" placeholder="Password" value={this.state.password} onChange={this.handlePasswordChange}/>
            </div>
            <div className="form-group">
                <label>Password Confirmation</label>
                <input type="password" className="form-control" name="password_confirmation" placeholder="Password Confirmation" value={this.state.passwordConfirmation} onChange={this.handlePasswordConfirmationChange}/>
            </div>
            <div>
                <button type="submit" className="btn btn-primary">Submit</button>
                <button type="button" className="btn btn-danger" onClick={this.handleCancelClick}>Cancel</button>
            </div>
        </form>
    )
}

render () {
    if (!this.state.errorMessages) {
        return (
            <div>
                {this.renderForm()}
            </div>
        )
    } else {
        return (
            <div>
                {this.renderForm()}
                {this.renderError()}
            </div>
        )
    }
}

我不太喜欢这种方法,因为如果我有更多条件需要重新渲染,这可能会变得很糟糕。我希望有一个解决方案,即在实际渲染函数中没有太多逻辑并将其提取出来。比如……

renderError() {
    if (!this.state.errorMessages) {
        return;
    }
    var errArr = [];
    for (var key in this.state.errorMessages) {
        errArr = [...errArr, ...this.state.errorMessages[key]];
    }
    return (
        <div className="alert alert-danger">
            {errArr.map((err) => {
                return <p>{err}</p>
            })}
        </div>
    )
}

render () {
    <form onSubmit={this.handleRegisterFormSubmit}>
        <div className="form-group">
            <label>First Name</label>
            <input type="text" className="form-control" name="name" placeholder="Name" value={this.state.firstName} onChange={this.handleFirstNameChange} required/>
        </div>
        <div className="form-group">
            <label>Last Name</label>
            <input type="text" className="form-control" name="lastName" placeholder="Last Name" value={this.state.lastName} onChange={this.handleLastNameChange} required/>
        </div>
        <div className="form-group">
            <label>Email address</label>
            <input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" value={this.state.email} onChange={this.handleEmailChange} />
            <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
        </div>
        <div className="form-group">
            <label>Password</label>
            <input type="password" className="form-control" name="password" placeholder="Password" value={this.state.password} onChange={this.handlePasswordChange}/>
        </div>
        <div className="form-group">
            <label>Password Confirmation</label>
            <input type="password" className="form-control" name="password_confirmation" placeholder="Password Confirmation" value={this.state.passwordConfirmation} onChange={this.handlePasswordConfirmationChange}/>
        </div>
        <div>
            <button type="submit" className="btn btn-primary">Submit</button>
            <button type="button" className="btn btn-danger" onClick={this.handleCancelClick}>Cancel</button>
        </div>
        {this.renderError}
    </form>
}

这会引发错误,因为它抱怨 this.renderError 应该作为函数调用。但是当我把它作为 this.renderError() 错误将永远不会呈现,因为它不会在错误再次出现时自动调用。

-----------更新----------

另外,为什么我不能执行以下操作

render () {
    <div>
        <form onSubmit={this.handleRegisterFormSubmit}>
            ...
        </form>
        {if (this.state.errorMessages) {this.renderError()}}
    </div>
}

这会引发控制台错误

未捕获的错误:模块构建失败:SyntaxError: Unexpected token (117:13)

---------更新2------------

本质上,我正在寻找一种解决方案,在渲染函数内部,当状态发生变化时,我可以轻松地显示整个代码块。在 Vue 中,我可以做类似的事情

<form>
    <input type="text">
</form>
<div v-if="hasError">
    <div class="alert alert-danger">{{something}}</div>
</div>

Can I do something as easy as this in React?

【问题讨论】:

  • 把它当作 this.renderError() 是正确的。现在您需要了解为什么它不重新渲染。可能是因为您没有使用 setState 函数来更新状态。也许您可以分享您如何设置错误消息?
  • 将逻辑放入渲染中是不好的做法。如果语句在您的 dom 中不起作用。此外,触发 this.renderError() 在这里没有多大意义,因为您只需要迭代您的错误。请在下面查看我的答案。
  • 你可以做的是三元条件。但是如果 if(this.state.errorMessages) 是一个空对象,它可能总是为真。相反,请使用 {Object.keys(this.state.errorMessages).length ? this.renderError() : null}{Object.keys(this.state.errorMessages).length &amp;&amp; this.renderError()}
  • 在上面的那种情况下,这并不是关于我应该使用 if 还是三元条件,看起来 react 不喜欢在 render 的 {} 中使用这种 if 语句。 Iv 在下面的答案中添加了评论。感谢您的帮助

标签: javascript reactjs


【解决方案1】:

您可以只使用 map 来从您的对象中提取错误消息。

下面是 React 中表单验证和错误的最小示例。很高兴了解它的工作原理,但就我而言,我使用 Formik 来简化此过程。

class Test extends React.Component {
    constructor(props) {
        super(props);
        this.state = { errorMessages: {} };
    }

    handleRegisterFormSubmit = e => {
        e.preventDefault(); // don't submit the form until we run what's below
        let errorMessages = {};

        if (!this.state.lastName) errorMessages.lastName = 'You need to enter your last name';
        // And so on for each field validation

        // Do we have errors ?
        if (Object.keys(errorMessages).length > 0) {
            this.setState(errorMessages);
        } else {
            // Submit to server
        }
    };

    handleChange = e => {
        this.setState({
            [e.target.name]: e.target.value,
            errorMessages: {
                ...this.state.errorMessages,
                [e.target.name]: null // remove the error of this field if it's being edited
            }
        });
    };

    render() {
        const errArr = Object.keys(this.state.errorMessages).map(key => this.state.errorMessages[key]);
        return (
            <form onSubmit={this.handleRegisterFormSubmit}>
                <div className="form-group">
                    <label>Last Name</label>
                    <input type="text" className="form-control" name="lastName" placeholder="Last Name" value={this.state.lastName} onChange={this.handleChange} />
                </div>

                {/* ... your dom */}

                <div>
                    <button type="submit" className="btn btn-primary">
                        Submit
                    </button>
                    <button type="button" className="btn btn-danger" onClick={this.handleCancelClick}>
                        Cancel
                    </button>
                </div>

                {errArr.length > 0 && (
                    <div className="alert alert-danger">
                      {errArr.map(err => {
                        return <p>{err}</p>;
                      })}
                    </div>
                )}
            </form>
        );
    }
}

另一种不显示警报 div 的方法是使用三元运算符作为 className 并使用引导程序的 d-none

<div className={errArr.length ? "alert alert-danger" : "d-none"}>
    {errArr.map(err => {
        return <p>{err}</p>;
    })}
</div>

【讨论】:

  • 似乎非常接近我想要的。但是,您的解决方案将意味着即使没有错误(不可见,但存在),也总会有一个 alert alert-danger 的 div。是否可以对其进行修改,以便整个 div 仅在出现错误时才存在
  • 刚刚编辑。您始终可以将它们包装在逻辑运算符 see this
  • 感谢@user172902 的支持。如果您对此答案满意并且编码愉快,请标记为答案。 Ps:再次强调,Formik 真的很酷。干杯
  • 谢谢阿斯滕。正在消化并尝试您的代码。它就像一个魅力。不是我期望的语法,但它相对干净!
【解决方案2】:

我相信这是一个架构问题。

尝试遵循这些做法:

1- 直接在 JSX 中注入条件语句

2- 使用函数式组件来渲染 JSX 而不是对象方法


1- 直接在 JSX 中注入条件语句

不好:

if (!this.state.errorMessages) {
    return (
        <div>
            {this.renderForm()}
        </div>
    )
} else {
    return (
        <div>
            {this.renderForm()}
            {this.renderError()}
        </div>
    )
}

好:

return <div>
            {this.renderForm()}
            {this.state.errorMessages && this.renderError()}
        </div>

2- 使用函数式组件来渲染 JSX 而不是对象方法

不好:

class FormComponent {
  // ....

  renderError() {
    // Blah blah blah 
    return (
        <div className="alert alert-danger">
            {errArr.map((err) => {
                return <p>{err}</p>
            })}
        </div>
    )
   }

   render() {
     return (
       <div>
         <AnotherComponent />
         {this.renderError()}
      <div/>
     )
   }

}

然后{this.renderError()}

class FormComponent {
  // ....

   render() {
     return (
       <div>
         <AnotherComponent />
         {<Error />} {/* render it as componet ⚠️*/} 
      <div/>
     )
   }

}
// Build it as component outside the main component (FormComponent) ⚠️
function Error(props) { 
    return (
        <div className="alert alert-danger">
            {props.errArr.map((err) => {
                return <p>{err}</p>
            })}
        </div>
     )

 }

我也从 Udacity 毕业,在 React 开发企业应用程序方面花了很多年时间,我在这里分享我的经验。恭喜!

【讨论】:

    【解决方案3】:

    通常会在表示列表的元素中呈现项目,并应用一些样式。如果我们没有任何项目,有时我们也不想要这个包装元素。

    我编写了以下允许条件渲染的组件。当条件为 false 时,不会渲染任何元素以保持 DOM 干净。

    export default class If extends Component<{ condition: any }, {}> {
    
        render() {
            return this.props.condition ? this.props.children : <Fragment></Fragment>;
        }
    
    }
    

    现在可以简单地使用该组件,如下所示:

    <If condition={items.length}>
        <-- item list -->
    </If>
    

    【讨论】:

      猜你喜欢
      • 2022-01-17
      • 2021-05-01
      • 1970-01-01
      • 2020-07-31
      • 1970-01-01
      • 2018-08-13
      • 2018-01-25
      • 2019-07-27
      相关资源
      最近更新 更多