【问题标题】:Why does ReactJS handle radio `checked` attr differently from other attrs?为什么 ReactJS 处理无线电 `checked` 属性与其他属性不同?
【发布时间】:2016-03-25 18:11:43
【问题描述】:

tl;dr React 拒绝在输入上尊重 checked={checkThisOption},即使它在同一组输入上完美地尊重 data-ischecked={checkThisOption}

我还没有在 jsfiddle 上完成这项工作,但我已经使用 this code 重现了这个问题。

长版

我有一个简单的 ReactJS 组件,它向用户显示单选按钮列表。用户应该能够选择一个收音机,然后按下一个按钮来确认他们的选择。

这是组件定义(注意:我使用的是 ES6 和 webpack):

import React from 'react';

class Widget extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            currentValue: null // tracks currently-selected choice, by its value
        };
    }

    onClickOptionRadio = (event) => {
        this.setState({
            currentValue: String(event.currentTarget.value)
        });
    }

    onConfirm = (event) => {
        if(!this.props.onChange) return;
        this.props.onChange(this.state.currentValue);
    };

    render() {
        let choices = this.props.choices;
        let currentValue = this.state.currentValue;

        return (
            <div className="Widget">
                <ol className="choices">
                    {
                        choices.map((choice, i) => {
                            // decide whether to mark radio as checked:
                            // - if no current choice, check first radios
                            // - otherwise, check radio matching current choice
                            let noCurrentChoice      = (currentValue === null);
                            let drawingFirstChoice   = (i === 0);
                            let thisChoiceIsSelected = (String(choice.value) === currentValue);
                            let checkThisOption      = thisChoiceIsSelected || (noCurrentChoice && drawingFirstChoice);

                            return (
                                <li key={i}>
                                    <input type="radio" name="choices"
                                        value={choice.value}
                                        onChange={this.onClickOptionRadio}
                                        checked={checkThisOption?'checked':''}
                                        data-ischecked={checkThisOption}
                                        />

                                    <label>{choice.label}</label>
                                    {' '}

                                    {checkThisOption ? 'CHECKED' : ''}
                                </li>
                            );
                        })
                    }
                </ol>

                <button onClick={this.onConfirm}>Confirm choice</button>

            </div>
        );
    }

}

export default Widget;

这是拥有的组件:

import React from 'react';
import Widget from 'components/widget';

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

    render() {
        let choices = [
            { value: 10, label: 'First' },
            { value: 20, label: 'Second' },
            { value: 30, label: 'Third' }
        ];

        return (
            <div className="Owner">

                <Widget
                    choices={choices}
                    />

            </div>
        );
    }

}

export default Owner;

这是它的 GIF 动图:

请注意视频中的几件事:

  • 该逻辑显然适用于在初始渲染时检查第一个无线电
  • 当用户点击其他收音机时,它们不会被选中
  • 但是,该逻辑显然适用于识别选择了哪个项目,正如无线电中的 margin-right: 2rem 所示,应该检查
  • 指示选择了哪个选项的文本自始至终都是准确的
  • 当我单击一个收音机时,componentWillUpdate 方法仅针对 Widget 本身触发;它的祖先都没有更新

我认为这个演示证明这不是 Widget 实例被另一个状态为空的实例替换的情况。当前选择通过输入上的data- attr 以及纯文本准确反映了这一事实,这表明状态正在按预期持续存在。我确信这种不受欢迎的行为是设计使然,我想知道如何解决 React 应用于受控输入的表单相关属性的奇怪的异常逻辑。

为什么我认为当前的行为是错误的?我不希望拥有的组件知道每个无线电点击——所有者应该绑定到 Widget 的 onChange 方法,以便在做出最终选择后得到通知。

这是一个简化的示例。真正的组件更复杂,但原理是一样的:就像一个选择日期的组件可能有很多自己的组件不知道的内部状态(比如要显示什么时间刻度,要显示哪一年、哪一个月或哪一周)显示等),所以这个组件也有一些有趣的内部状态,拥有组件没有业务管理。

据我所知,我做得非常正确。组件通过onChange(event, newValue) 发布其重要的状态更新,拥有组件应绑定到该位置。我认为很明显,React 决定不更新这些输入上的 checked attr,即使它显然能够更新相同元素上的其他 attr 以响应相同的用户操作。

请注意,所有者当前并未监听onChange,但这无关紧要:即使所有者不在,子组件也应该能够管理自己的内部状态。 t 听。而且我拒绝断言无线电状态不能准确只是因为所有者没有通过道具提供currentValue:小部件显然在没有该道具的情况下管理和呈现其状态。 React 必须做一些特殊的事情来防止 checked 根据适用于所有其他元素和属性的规则进行处理。这是一个例外,我认为这是一个糟糕的例外。

最后,请注意,这个问题似乎只发生在该组件低于某个组合树深度时。当它是 Flux “页面”或 Redux “容器”中的唯一组件时,它的效果很好。当它嵌套得更深时,它就像我所描述的那样失败。我还没有想出一个简洁的方式来展示它。

感谢任何建议。据我所知,这里的 React 违反了它自己的规定规则,我希望这种行为会阻碍构建围绕 vanilla inputs 构建的其他有状态组件。

编辑:我更正了为收音机生成的names,并更新了演示以反映它。向那些开始追究这些东西的人道歉。

【问题讨论】:

  • 对我来说很好用:jsfiddle.net/fcLebhcd。您可能没有将正确的数据传递给组件。选择是具有labelvalue 属性的对象数组吗?请注意,checked 需要一个布尔值,而不是字符串。
  • 这里一样,工作正常:codepen.io/anon/pen/rxeZjX - 可能是浏览器相关问题吗?
  • @FelixKling:是的,传递选择的数组。我将使用拥有 comp 的 sn-p 更新帖子。回复:布尔/字符串:对我来说这两种方式都失败了。
  • @gpmcadam:Prolly 不是浏览器:我使用的是常绿 Chrome。我认为您的组件树不够深。当我作为顶级组件执行此操作时(即直接在 Flux“页面”或 Redux“容器”内,它工作正常。当我将此组合插入到组合树的更深处时,它会失败,正如我所描述的那样。我会看看加强我的示例代码来捕捉这一点。
  • @Tom 最好发布重现问题的 codepen/jsfiddle?

标签: javascript forms reactjs


【解决方案1】:

我已将其编辑为不使用class properties,并且无法重现:

代码:

class Widget extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      currentValue: null // tracks currently-selected choice, by its value
    };
    this.onClickOptionRadio = this.onClickOptionRadio.bind(this)
    this.onConfirm = this.onConfirm.bind(this)
  }

  onClickOptionRadio (event) {
    this.setState({
      currentValue: String(event.currentTarget.value)
    });
  }

  onConfirm (event) {
    if(!this.props.onChange) return;
    this.props.onChange(this.state.currentValue);
  };

  render() {
    let choices = this.props.choices;
    let currentValue = this.state.currentValue;

    return (
      <div className="Widget">
      <ol className="choices">
      {
        choices.map((choice, i) => {
          // decide whether to mark radio as checked:
          // - if no current choice, check first radios
          // - otherwise, check radio matching current choice
          let noCurrentChoice      = (currentValue === null);
          let drawingFirstChoice   = (i === 0);
          let thisChoiceIsSelected = (String(choice.value) === currentValue);
          let checkThisOption      = thisChoiceIsSelected || (noCurrentChoice && drawingFirstChoice);

          return (
            <li key={i}>
            <input type="radio" name="choices"
            value={choice.value}
            onChange={this.onClickOptionRadio}
            checked={checkThisOption?'checked':''}
            data-ischecked={checkThisOption}
            />

            <label>{choice.label}</label>
            {' '}

            {checkThisOption ? 'CHECKED' : ''}
            </li>
          );
        })
      }
      </ol>

      <button onClick={this.onConfirm}>Confirm choice</button>

      </div>
    );
  }

}


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

  render() {
    let choices = [
      { value: 10, label: 'First' },
      { value: 20, label: 'Second' },
      { value: 30, label: 'Third' }
    ];

    return (
      <div className="Owner">

      <Widget
      choices={choices}
      />

      </div>
    );
  }

}

ReactDOM.render(
  <Owner />,
  document.getElementById('container')
);

我的浏览器是 Chrome 47。Here is the jsfiddle.

【讨论】:

    猜你喜欢
    • 2011-09-19
    • 2023-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    相关资源
    最近更新 更多