• 处理用户输入
  • 向父组件传递数据
    • 作者:胡子大哈
    • 原文链接: http://huziketang.com/books/react/lesson15
    • 转载请注明出处,保留原文链接和作者信息。

    上一节我们构建了基本的代码框架,现在开始完善其他的内容。

    处理用户输入

    我们从 ComponentInput 组件开始,学习 React.js 是如何处理用户输入的。首先修改 ComponentInput.js,完善 ComponentInputrender 函数中的 HTML 结构:

    1. import React, { Component } from 'react'
    2. class CommentInput extends Component {
    3. render () {
    4. return (
    5. <div className='comment-input'>
    6. <div className='comment-field'>
    7. <span className='comment-field-name'>用户名:</span>
    8. <div className='comment-field-input'>
    9. <input />
    10. </div>
    11. </div>
    12. <div className='comment-field'>
    13. <span className='comment-field-name'>评论内容:</span>
    14. <div className='comment-field-input'>
    15. <textarea />
    16. </div>
    17. </div>
    18. <div className='comment-field-button'>
    19. <button>
    20. 发布
    21. </button>
    22. </div>
    23. </div>
    24. )
    25. }
    26. }
    27. export default CommentInput

    在浏览器中可以看到 ComponentInput 的结构和样式都已经生效:

    React.js 小书实战之评论功能图片

    因为还没有加入处理逻辑,所以你输入内容,然后点击发布是不会有什么效果的。用户可输入内容一个是用户名(username),一个是评论内容(content),我们在组件的构造函数中初始化一个 state 来保存这两个状态:

    1. ...
    2. class CommentInput extends Component {
    3. constructor () {
    4. super()
    5. this.state = {
    6. username: '',
    7. content: ''
    8. }
    9. }
    10. ...
    11. }
    12. ...

    然后给输入框设置 value 属性,让它们的 value 值等于 this.state 里面相应的值:

    1. ...
    2. <div className='comment-field'>
    3. <span className='comment-field-name'>用户名:</span>
    4. <div className='comment-field-input'>
    5. <input value={this.state.username} />
    6. </div>
    7. </div>
    8. <div className='comment-field'>
    9. <span className='comment-field-name'>评论内容:</span>
    10. <div className='comment-field-input'>
    11. <textarea value={this.state.content} />
    12. </div>
    13. </div>
    14. ...

    可以看到接受用户名输入的 <input /> 和接受用户评论内容的 <textarea />value 值分别由 state.usernamestate.content 控制。这时候你到浏览器里面去输入内容看看,你会发现你什么都输入不了。

    这是为什么呢?React.js 认为所有的状态都应该由 React.js 的 state 控制,只要类似于 <input /><textarea /><select /> 这样的输入控件被设置了 value 值,那么它们的值永远以被设置的值为准。值不变,value 就不会变化。

    例如,上面设置了 <input />valuethis.state.usernameusernameconstructor 中被初始化为空字符串。即使用户在输入框里面尝试输入内容了,还是没有改变 this.state.username 是空字符串的事实。

    所以应该怎么做才能把用户内容输入更新到输入框当中呢?在 React.js 当中必须要用 setState 才能更新组件的内容,所以我们需要做的就是:监听输入框的 onChange 事件,然后获取到用户输入的内容,再通过 setState 的方式更新 state 中的 username,这样 input 的内容才会更新。

    1. ...
    2. <div className='comment-field-input'>
    3. <input
    4. value={this.state.username}
    5. onChange={this.handleUsernameChange.bind(this)} />
    6. </div>
    7. ...

    上面的代码给 input 加上了 onChange 事件监听,绑定到 this.handleUsernameChange 方法中,该方法实现如下:

    1. ...
    2. handleUsernameChange (event) {
    3. this.setState({
    4. username: event.target.value
    5. })
    6. }
    7. ...

    在这个方法中,我们通过 event.target.value 获取 <input /> 中用户输入的内容,然后通过 setState 把它设置到 state.username 当中,这时候组件的内容就会更新,inputvalue 值就会得到更新并显示到输入框内。这时候输入已经没有问题了:

    React.js 小书实战之评论功能图片

    类似于 <input /><select /><textarea> 这些元素的 value 值被 React.js 所控制、渲染的组件,在 React.js 当中被称为受控组件(Controlled Component)。对于用户可输入的控件,一般都可以让它们成为受控组件,这是 React.js 所推崇的做法。另外还有非受控组件,这里暂时不提及。

    同样地,让 <textarea /> 成为受控组件:

    1. ...
    2. handleContentChange (event) {
    3. this.setState({
    4. content: event.target.value
    5. })
    6. }
    7. ...
    8. <div className='comment-field'>
    9. <span className='comment-field-name'>评论内容:</span>
    10. <div className='comment-field-input'>
    11. <textarea
    12. value={this.state.content}
    13. onChange={this.handleContentChange.bind(this)} />
    14. </div>
    15. </div>
    16. ...

    向父组件传递数据

    当用户在 CommentInput 里面输入完内容以后,点击发布,内容其实是需要显示到 CommentList 组件当中的。但这两个组件明显是单独的、分离的组件。我们再回顾一下之前是怎么划分组件的:

    React.js 小书实战之评论功能图片

    可以看到,CommentApp 组件将 CommentInputCommentList 组合起来,它是它们俩的父组件,可以充当桥接两个子组件的桥梁。所以当用户点击发布按钮的时候,我们就将 CommentInput 的 state 当中最新的评论数据传递给父组件 CommentApp ,然后让父组件把这个数据传递给 CommentList 进行渲染。

    CommentInput 如何向 CommentApp 传递的数据?父组件 CommentApp 只需要通过 props 给子组件 CommentInput 传入一个回调函数。当用户点击发布按钮的时候,CommentInput 调用 props 中的回调函数并且将 state 传入该函数即可。

    先给发布按钮添加事件:

    1. ...
    2. <div className='comment-field-button'>
    3. <button
    4. onClick={this.handleSubmit.bind(this)}>
    5. 发布
    6. </button>
    7. </div>
    8. ...

    用户点击按钮的时候会调用 this.handleSubmit 方法:

    1. ...
    2. handleSubmit () {
    3. if (this.props.onSubmit) {
    4. const { username, content } = this.state
    5. this.props.onSubmit({username, content})
    6. }
    7. this.setState({ content: '' })
    8. }
    9. ...

    handleSubmit 方法会判断 props 中是否传入了 onSubmit 属性。有的话就调用该函数,并且把用户输入的用户名和评论数据传入该函数。然后再通过 setState 清空用户输入的评论内容(但为了用户体验,保留输入的用户名)。

    修改 CommentApp.js ,让它可以通过传入回调来获取到新增评论数据:

    1. class CommentApp extends Component {
    2. handleSubmitComment (comment) {
    3. console.log(comment)
    4. }
    5. render() {
    6. return (
    7. <div className='wrapper'>
    8. <CommentInput
    9. onSubmit={this.handleSubmitComment.bind(this)} />
    10. <CommentList />
    11. </div>
    12. )
    13. }
    14. }

    CommentApp 中给 CommentInput 传入一个 onSubmit 属性,这个属性值是 CommentApp 自己的一个方法 handleSubmitComment。这样 CommentInput 就可以调用 this.props.onSubmit(…) 把数据传给 CommenApp

    现在在 CommentInput 中输入完评论内容以后点击发布,就可以看到 CommentApp 在控制台打印的数据:

    React.js 小书实战之评论功能图片

    这样就顺利地把数据传递给了父组件,接下来我们开始处理评论列表相关的逻辑。


    因为第三方评论工具有问题,对本章节有任何疑问的朋友可以移步到 React.js 小书的论坛 发帖,我会回答大家的疑问。