为什么 ReactJS 处理 Radio `checked` attr 的方式与其他 attrs 不同?

IT技术 javascript forms reactjs
2021-04-30 08:46:47

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

我还没有在 jsfiddle 上做过这项工作,但我已经使用这段代码重现了这个问题

长版

我有一个简单的 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实例被另一个状态为空实例替换的情况。当前选择由input 上的data-attr以及纯文本准确反映的事实表明状态按需要持续存在。我确信这种不受欢迎的行为是设计使然,我想知道如何解决 React 应用于受控输入的表单相关属性的奇怪的异常逻辑。

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

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

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

需要注意的是所有者不当前监听onChange,但是这不应该事:孩子组件应该能够管理,即使业主不听自己的内部状态。并且我拒绝仅仅因为 Owner 没有提供currentValuevia props就认为无线电状态不能准确的断言:Widget 在没有那个 props 的情况下直接管理和呈现它的状态。React 必须做一些特殊的事情,以防止checked根据适用于所有其他元素和属性的规则进行处理这是一个例外,我认为这是一个糟糕的例外。

最后,请注意,此问题似乎仅在此组件低于某个 comp-tree 深度时才会发生。当它是 Flux“页面”或 Redux“容器”中的唯一组件时,它工作得很好。当它嵌套得更深时,就会如我所描述的那样失败。我还没有想出一个简洁的方式来证明这一点。

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

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

1个回答

我已将其编辑为不使用类属性,并且无法重现:

在此处输入图片说明

代码:

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。这是 jsfiddle。