React:如果输入框的值由状态改变,如何触发onChange事件?

95

编辑: 我不想仅在按钮被点击时调用handleChange。 这与handleClick无关。 我在@shubhakhatri答案的评论中举了一个例子。

我想根据状态更改输入值,该值正在更改,但不触发 handleChange()方法。 如何触发 handleChange()方法?

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    value: 'random text'
    }
  }
  handleChange (e) {
    console.log('handle change called')
  }
  handleClick () {
    this.setState({value: 'another random text'})
  }
  render () {
    return (
      <div>
        <input value={this.state.value} onChange={this.handleChange}/>
        <button onClick={this.handleClick.bind(this)}>Change Input</button>
      </div>
    )
  }
}

ReactDOM.render(<App />,  document.getElementById('app'))

这里是CodePen链接:http://codepen.io/madhurgarg71/pen/qrbLjp


据我所理解,您希望在按钮被点击后才更改输入。我是对的吗? - Shubham Khatri
1
@ShubhamKhatri 对不起,我错了,我不想在按钮被点击后才进行更改。这只是一个例子,其中handleClick仅设置状态。它与handleClick无关。对不起,我理解错误。 - madhurgarg
你解决了吗?据我所知,上述脚本在最新的React版本中应该没有问题。我刚刚在提供的Codepen代码片段中尝试了一下,它可以正常工作。 - Shan
你在codepen上的代码和这里展示的不同。这个可以正常工作,完全没有原因它不能工作。你可能想在控制台上检查一下 "handle change called"。 - KJ Sudarshan
应该使用函数式组件。 - mercury
10个回答

44

您需要手动触发onChange事件。对于文本输入,onChange监听input事件。

因此,在您的handleClick函数中,您需要像下面这样触发事件:


```javascript element.dispatchEvent(new Event('input')); ```
handleClick () {
    this.setState({value: 'another random text'})
    var event = new Event('input', { bubbles: true });
    this.myinput.dispatchEvent(event);
  }

完整的代码

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    value: 'random text'
    }
  }
  handleChange (e) {
    console.log('handle change called')
  }
  handleClick () {
    this.setState({value: 'another random text'})
    var event = new Event('input', { bubbles: true });
    this.myinput.dispatchEvent(event);
  }
  render () {
    return (
      <div>
        <input readOnly value={this.state.value} onChange={(e) => {this.handleChange(e)}} ref={(input)=> this.myinput = input}/>
        <button onClick={this.handleClick.bind(this)}>Change Input</button>
      </div>
    )
  }
}

ReactDOM.render(<App />,  document.getElementById('app'))

Codepen

编辑: 如 @Samuel 在评论中建议的那样,如果您在 handleChange 中不需要事件对象,则可以从 handleClick 调用 handleChange,例如

handleClick () {
    this.setState({value: 'another random text'})
    this.handleChange();
  }

我希望这正是您所需的,并且它能够帮助到您。


使用一个元素上的 ref 几乎总是错误的答案,除非你需要做一些无法用其他方法完成的事情。在这里,一个更简单的解决方案是从 handleClick 中直接调用 handleChange,正如这个回答所建议的:https://dev59.com/g1gQ5IYBdhLWcg3wkE3m#42550931 - user2608603
1
@SamuelScheiderich 如果您不需要handleChange中的onChange事件对象,那么有一种更简单的方法。我在原始答案中没有包含它,因为handleChange将事件作为参数包含了进去。 - Shubham Khatri
2
@ShubhamKhatri 抱歉,但我正在撤回我的点赞。我刚意识到你的答案并没有回答我的问题。你所做的是从handleClick显式地调用handleChange方法。这与handleClick无关。React状态不知道handleClick方法,对吧?我会给你一个更实际的例子,我们可能需要这个。假设您有一个登录表单,并保存了密码。当您再次进入登录页面时,浏览器会自动填充您的表单输入。现在,您想为某些UI动画调用handleChange方法。 - madhurgarg
9
2019 年的版本根本不起作用。现在,由于某些原因,表单不会对手动 dispatchEvent 做出反应。 - Limbo
@ShubhamKhatri,你好Shubham,非常好的答案,我想友好地问你一个问题,我们是否应该始终为输入字段使用onChange处理程序。我刚刚在创建计算器时决定使用例如<input value:this.state.number>而不使用onChange。我只是改变状态,然后输入值也会更改。这是正确的方法吗?或者每当我使用输入字段时都应该使用onChange :) - Dickens

38

我尝试了其他解决方案,但都没有起作用。这是因为React.js中的输入逻辑已经改变。详情请参见此链接:https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04

简而言之,当我们通过改变状态来改变输入值,然后触发一个change事件时,React将同时注册setState和事件,并将其视为重复事件并吞噬它。

解决方案是在输入上调用本机value setter(请参见以下代码中的setNativeValue函数)

示例代码

import React, { Component } from 'react'
export class CustomInput extends Component {

    inputElement = null;
    
    // THIS FUNCTION CALLS NATIVE VALUE SETTER
    setNativeValue(element, value) {
        const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
        const prototype = Object.getPrototypeOf(element);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;

        if (valueSetter && valueSetter !== prototypeValueSetter) {
            prototypeValueSetter.call(element, value);
        } else {
            valueSetter.call(element, value);
        }
    }


    constructor(props) {
        super(props);

        this.state = {
            inputValue: this.props.value,
        };
    }

    addToInput = (valueToAdd) => {
        this.setNativeValue(this.inputElement, +this.state.inputValue + +valueToAdd);
        this.inputElement.dispatchEvent(new Event('input', { bubbles: true }));
    };

    handleChange = e => {
        console.log(e);
        this.setState({ inputValue: e.target.value });
        this.props.onChange(e);
    };

    render() {
        return (
            <div>
                <button type="button" onClick={() => this.addToInput(-1)}>-</button>
                <input
                    readOnly
                    ref={input => { this.inputElement = input }}
                    name={this.props.name}
                    value={this.state.inputValue}
                    onChange={this.handleChange}></input>
                <button type="button" onClick={() => this.addToInput(+1)}>+</button>
            </div>
        )
    }
}

export default CustomInput

结果

enter image description here


prototypeValueSetter.callvalueSetter.call是否颠倒了?在上面的例子中,只有当a)它未定义或b)它严格等于prototypeValueSetter时,才会调用valueSetter - nik10110
我已经测试了它们交换后的代码 - 没有起作用。因此,我认为没有理由不专门使用prototypeValueSetter并删除所有提到valueSetter的内容。 - nik10110
为什么在 setNativeValue() 中需要 if() 条件呢?换句话说,我们不能只使用 Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set.call(element, value); 吗?(请原谅这个一行代码;在注释中阅读一行代码应该更容易) - rinogo
谢谢!你的解释正是我理解问题所缺少的。此外,在我的情况下,我只需要将dispatchEvent添加到按钮中即可。 - Thremulant

14

我认为你应该这样修改:

<input value={this.state.value} onChange={(e) => {this.handleChange(e)}}/>

原则上,这与你在按钮上执行的 onClick={this.handleClick.bind(this)} 是相同的。

因此,如果你想在单击按钮时调用 handleChange(),那么:

<button onClick={this.handleChange.bind(this)}>Change Input</button>
或者
handleClick () {
  this.setState({value: 'another random text'});
  this.handleChange();
}

我想在单击按钮时触发handleChange事件。当手动在输入框内输入时,它是有效的。假设该输入框为只读模式。 - madhurgarg

8
在一个函数组件中,你可以这样做,假设我们有一个 input[type=number]
const MyInputComponent = () => {
    const [numberValue, setNumberValue] = useState(0);
    const numberInput = useRef(null);

    /**
    * Dispatch Event on Real DOM on Change
    */
    useEffect(() => {
        numberInput.current.dispatchEvent(
            new Event("change", {
                detail: {
                    newValue: numberValue,
                },
                bubbles: true,
                cancelable: true,
            })
        );
    }, [numberValue]);

    return (
        <>
            <input    
                type="number"            
                value={numberValue}             
                ref={numberInput}
                inputMode="numeric"
                onChange={(e) => setNumberValue(e.target.value)}
            />
        </>
    )
}

1
这个确实可以工作,但是事件对象中的选项属性只有两个,即bubbles和cancelable。 - Niyi Aromokeye
是的,你说得对。我认为它能够工作是因为该值已经通过React设置好了,所以当监听到“change”事件时,我们可以在“event.target”上访问该值。在这个答案中,“detail: { newValue: numberValue }”部分是多余的,可以被删除。或者,如果需要额外的信息,应该使用支持“detail”属性的“CustomEvent”,而不是“Event”。 - Amin

4
其他答案已经谈到了在render中直接绑定的问题,因此我想补充一些相关的要点。
不建议在render或组件的任何其他地方直接绑定函数,除非在构造函数中。因为对于每个函数绑定,在webpack捆绑的js文件中都会创建一个新的函数/对象,因此捆绑大小将增加。您的组件将因多种原因重新渲染,例如当您进行setState、收到新props时,以及当您执行this.forceUpdate()等操作时。因此,如果您直接在render中绑定函数,它将始终创建一个新函数。相反,在构造函数中始终进行函数绑定,并在需要时调用引用。这样只有一次创建新函数,因为构造函数每个组件仅调用一次。
您应该像下面这样做:
constructor(props){
  super(props);
  this.state = {
    value: 'random text'
  }
  this.handleChange = this.handleChange.bind(this);
}

handleChange (e) {
  console.log('handle change called');
  this.setState({value: e.target.value});
}

<input value={this.state.value} onChange={this.handleChange}/>

你也可以使用箭头函数,但在某些情况下,箭头函数每次组件重新渲染时也会创建新的函数。你应该知道何时使用箭头函数以及何时不应该使用。关于何时使用箭头函数的详细解释,请查看这里的被接受的答案。


5
这个回答完全忽略了实际问题。虽然你的回答中提供的信息通常是有价值的,但我认为不相关的回答不应该存在,因为它只会为试图找到确切(或接近)回答主题的人们制造混乱。 - vsync

1
你必须完成以下4个步骤:

  1. create event

    var event = new Event("change",{
        detail: {
            oldValue:yourValueVariable,
            newValue:!yourValueVariable
        },
        bubbles: true,
        cancelable: true
    });
    event.simulated = true;
    let tracker = this.yourComponentDomRef._valueTracker;
    if (tracker) {
        tracker.setValue(!yourValueVariable);
    }
    
  2. bind value to component dom

    this.yourComponentDomRef.value = !yourValueVariable;
    
  3. bind element onchange to react onChange function

     this.yourComponentDomRef.onchange = (e)=>this.props.onChange(e);
    
  4. dispatch event

    this.yourComponentDomRef.dispatchEvent(event);
    
在上面的代码中,yourComponentDomRef 指的是你的 React 组件的主 dom。例如:<div className="component-root-dom" ref={(dom)=>{this.yourComponentDomRef= dom}}>

6
这段话意思是:这是一团混乱的代码集合,充斥着不正确的巫术式代码,你并没有对其进行解释。 根据我的经验,只有 _valueTracker 是必要的,还有 event.simulated = true,但其他部分毫无意义。为什么你要使用 value = !yourValueVariable?完全没有意义。最好加上“5. 踩你的头并在背上滚两圈”作为所谓步骤中的一部分。我的翻译如下:这段话意思是:这是一团混乱的代码集合,充斥着不正确的巫术式代码,你并没有对其进行解释。 根据我的经验,只有 _valueTracker 是必要的,还有 event.simulated = true,但其他部分毫无意义。为什么你要使用 value = !yourValueVariable?完全没有意义。最好加上“5. 踩你的头并在背上滚两圈”作为所谓步骤中的一部分。 - vsync

1

使用React Native和Hooks的方法:

您可以将TextInput包装成一个新的组件,用于监测值是否发生变化,并在发生变化时触发onChange函数。

import React, { useState, useEffect } from 'react';
import { View, TextInput as RNTextInput, Button } from 'react-native';

// New TextInput that triggers onChange when value changes.
// You can add more TextInput methods as props to it.
const TextInput = ({ onChange, value, placeholder }) => {

  // When value changes, you can do whatever you want or just to trigger the onChange function
  useEffect(() => {
    onChange(value);
  }, [value]);

  return (
    <RNTextInput
      onChange={onChange}
      value={value}
      placeholder={placeholder}
    />
  );
};

const Main = () => {

  const [myValue, setMyValue] = useState('');

  const handleChange = (value) => {
    setMyValue(value);
    console.log("Handling value");
  };

  const randomLetters = [...Array(15)].map(() => Math.random().toString(36)[2]).join('');

  return (
    <View>
      <TextInput
        placeholder="Write something here"
        onChange={handleChange}
        value={myValue}
      />
      <Button
        title='Change value with state'
        onPress={() => setMyValue(randomLetters)}
      />
    </View>
  );
};

export default Main;

0

我知道你的意思,你想通过点击按钮来触发handleChange事件。

但是修改状态值不会触发onChange事件,因为onChange事件是一个表单元素事件。


但是当您保存登录凭证并且浏览器的自动填充设置输入值时,它是如何被触发的呢? - madhurgarg
其实,我认为没有必要触发那个函数,也许你只需要告诉我你想要执行什么操作。 - Xat_MassacrE

-1

我有类似的需求,最终使用了componentDidMount(),它在组件类构造函数之后被调用(您可以从props初始化状态 - 例如使用redux)

在componentDidMount中,您可以调用handleChange方法进行一些UI动画或执行所需的任何类型的组件属性更新。

例如,我遇到了一个问题,无法通过程序更新输入复选框类型,这就是为什么我最终使用此代码,因为onChange处理程序在组件加载时未触发:

   componentDidMount() {

    // Update checked 
    const checkbox = document.querySelector('[type="checkbox"]');

    if (checkbox) 
      checkbox.checked = this.state.isChecked;
  }

State 首先在组件类构造函数中进行了更新,然后用于更新一些输入组件的行为。


-2

如果状态对象具有子对象,例如this.state.class.fee,请尝试使用此代码。我们可以使用以下代码传递值:

this.setState({ class: Object.assign({}, this.state.class, { [element]: value }) }

你好,Atchutha!请使用适当的代码格式化,以便内容易于阅读。 - Maciej Jureczko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接