为什么ReactJS处理无线电`checked` attr与其他attrs不同? [英] Why does ReactJS handle radio `checked` attr differently from other attrs?

查看:105
本文介绍了为什么ReactJS处理无线电`checked` attr与其他attrs不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tl; dr React拒绝接受 checked = {checkThisOption} 输入,即使它尊重数据 - 缺陷= {checkThisOption} 完全在同一组输入上。



我没有在jsfiddle上做这个工作,但我已经复制了问题使用



请注意视频中的一些内容:




  • 逻辑清楚地用于检查初始渲染时的第一个无线电

  • 其他无线电在用户点击时不会被检查

  • 但是,逻辑清楚地用于识别选择了哪个项目,如 margin-right:2rem 所示,应该检查的无线电

  • 指示选择了哪个选项的文本在

  • 中是准确的。当我点击一个收音机时, componentWillUpdate 方法仅触发小部件本身;没有一个祖先更新



我认为这个演示证明这不是小部件的情况实例被状态为空的其他实例替换。当前选择由输入上的数据 - attr 以及纯文本准确反映的事实表明状态是持久的如预期的。我确信这种不受欢迎的行为是设计的,我想知道如何解决React适用于受控输入的表单相关属性的奇异异常逻辑。



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



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



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



请注意,所有者当前正在侦听 onChange ,但这无关紧要:即使主人没有收听,子组件也应该能够管理自己的内部状态。我拒绝断言无线电状态不能准确,因为所有者没有通过道具提供 currentValue :Widget明显地管理并呈现其状态而没有该支柱。 React必须采取一些特殊措施,以防止选中根据适用于所有其他元素和属性的规则进行处理。这是一个例外,我认为这是一个不好的。



最后,请注意,当此组件低于某个组合树深度时,似乎只会发生此问题。当它是Fluxpage或Redux容器中唯一的组件时,它的效果很好。当它嵌套得更深时,它就像我所描述的那样失败了。我还没有简明扼要地表明这一点。



任何建议都表示赞赏。据我所知,这里的React违反了自己规定的规则,我希望这种行为能够阻止构建围绕vanilla 输入 s构建的其他有状态组件。



编辑:我更正了无线电的生成的名称,并将演示更新为反思吧。向任何开始追逐这些东西的人道歉。

解决方案

我编辑它不使用



代码:

 类Widget扩展React.Component {
构造函数(props){
super(props);

this.state = {
currentValue:null //跟踪当前选择的选项,其值为
};
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;

返回(
< div className =Widget>
< ol className =choices>
{
choices.map ((choice,i)=> {
//决定是否将收音机标记为已选中:
// - 如果没有当前选择,请检查第一个收音机
// - 否则,请检查收音机匹配当前选择
let noCurrentChoice =(currentValue === null);
let drawingFirstChoice =(i === 0);
let thisChoiceIsSelected =(String(choice.value)=== currentValue);
让checkThisOption = thisChoiceIsSelected ||(noCurrentChoice&&" drawingFirstChoice);

return(
< li key = {i}>
< input type =radioname =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}>确认选择< / button>

< / div>
);
}

}


类所有者扩展React.Component {
构造函数(道具){
super(道具) ;
this.state = {};
}

render(){
让选择= [
{值:10,标签:'第一'},
{值:20, label:'Second'},
{value:30,label:'Third'}
];

return(
< div className =Owner>

< Widget
choices = {choices}
/> ;

< / div>
);
}

}

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

我的浏览器是Chrome 47. 这是jsfiddle。


tl;dr React refuses to honor checked={checkThisOption} on inputs, even though it honors data-ischecked={checkThisOption} perfectly on the same set of inputs.

I haven't made this work on jsfiddle, but I have reproduced the issue using this code.

the long version

I've got a simple ReactJS component that presents a list of radio buttons to the user. The user is supposed to be able to pick a radio and then push a button to confirm their choice.

Here's the component def (note: I'm using 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;

Here's the owning component:

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;

Here's a gif of it in action:

Note several things from the video:

  • the logic clearly works for checking the first radio on initial render
  • the other radios don't become checked when the user clicks on them
  • however, the logic clearly works for identifying which item is selected, as indicated by margin-right: 2rem on the radio that ought to be checked
  • the text indicating which option has been chosen is accurate throughout
  • when I click a radio, the componentWillUpdate method fires only for the Widget itself; none of its ancestors update

I think this demo proves that this isn't a case of the Widget instance being replaced by a different instance whose state is empty. The fact that the current selection is accurately reflected by a data- attr on the input, as well as plain text, shows that the state is persisting as desired. I am certain this unwanted behavior is by design, and I want to know how to work around the bizarre, exceptional logic that React applies to the form-related properties of controlled inputs.

Why do I think the current behavior is wrong? I don't want the owning component to know about each radio click -- the owner should bind to Widget's onChange method to be notified once a final choice is made.

This is a simplified example. The real component is more complicated, but the principle is the same: just as a date-picking component may have lots of internal state that the owning component is unaware of (like what time scale to show, which year, month, or week to display, etc.), so too does this component have some interesting internal state that owning components have no business managing.

As far as I can tell, I've done this exactly correctly. The component publishes its important state updates via onChange(event, newValue), which owning components should bind to. I think it's quite clear that React is deciding to not update the checked attr on these inputs, even though it's clearly capable of updating other attrs on the same elements in response to the same user actions.

Note that the owner isn't currently listening for the onChange, but that shouldn't matter: the child component should be able to manage its own internal state even when the owner isn't listening. And I reject the assertion that the radio state can't be accurate simply because Owner isn't providing a currentValue via props: Widget is plainly managing and rendering its state without that prop. React must be doing something special to prevent checked from being handled according to the rules that apply to every other element and attribute. This is an exception, and I think it's a bad one.

Finally, note that this problem only seems to occur when this component is beneath a certain comp-tree depth. When it is the only component in a Flux "page" or a Redux "container," it works great. When it's nested more deeply, it fails as I've described. I haven't yet worked out a concise way of showing that.

Any advice is appreciated. As far as I can tell, here React is violating its own stated rules, and I expect this behavior to frustrate building other stateful components that are built around vanilla inputs.

Edit: I corrected the generated names for the radios, and updated the demo to reflect it. Apologies to anyone who started chasing that stuff down.

解决方案

I've edited it to not use class properties, and are not able to reproduce:

Code:

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')
);

My browser is Chrome 47. Here is the jsfiddle.

这篇关于为什么ReactJS处理无线电`checked` attr与其他attrs不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆