React.js - 重新渲染时输入框失去焦点

190

我只是在文本输入框中编写,并在 onChange 事件中调用 setState,这样React会重新渲染我的用户界面。问题是文本输入框总是失去焦点,因此我需要为每个字母重新聚焦它 :D。

var EditorContainer = React.createClass({

    componentDidMount: function () {
        $(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
    },

    componentDidUpdate: function () {
        console.log("zde");
        $(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
    },

    changeSelectedComponentName: function (e) {
        //this.props.editor.selectedComponent.name = $(e.target).val();
        this.props.editor.forceUpdate();
    },

    render: function () {

        var style = {
            height: this.props.height + 'px'
        };
        return (
            <div className="container" style={style}>
                <div className="row">
                    <div className="col-xs-6">
                    {this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
                    {this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
                    </div>
                    <div className="col-xs-6">
                        <ComponentTree editor={this.props.editor} components={this.props.components}/>
                    </div>
                </div>
            </div>
        );
    }

});

唯一可能发生这种情况的原因是:a)其他东西抢占了焦点,或者b)输入在闪烁。您能否提供一个 jsfiddle/jsbin 来展示问题?这里有一个基本的 React jsbin - Brigand
1
哈哈...听起来有点烦人 :P 使用jQuery,我会为新渲染的输入字段设置一个标识符,然后调用它的焦点。不确定在纯JS中代码会是什么样子,但我相信你可以做到 :) - Medda86
1
@FakeRainBrigand:你所说的闪烁是什么意思? - Krab
@Krab,如果this.props.selected变为false,然后再变为true,那么会导致输入框卸载,然后重新挂载。 - Brigand
@FakeRainBrigand:你说得对,谢谢。在componentDidUpdate中使用slimScroll会导致这个问题。但是我认为React只是比较连续渲染调用的输出。那个slimScroll并没有修改render函数的输出。 - Krab
显示剩余3条评论
26个回答

140

没有看到您代码的其余部分,这只是一个猜测。

当您创建一个EditorContainer时,为组件指定一个唯一的键:

<EditorContainer key="editor1"/>

在重新渲染发生时,如果看到相同的键,这会告诉React不要覆盖并重新生成视图,而是重复使用。然后,聚焦的项目应该保持聚焦状态。


3
这解决了我的子组件包含输入表单无法渲染的问题。但在我的情况下,我需要相反的结果——当我想要重新渲染表单时它却没有重新渲染。添加key属性可以解决这个问题。 - Sam Texas
9
在输入中添加关键字并没有起到帮助作用。 - Mïchael Makaröv
15
可能包含您输入的组件也重新渲染了。请检查封闭组件的键。 - Petr Gladkikh
2
谢谢您的答案!这样的问题通常是由键(keys)引起的。我也曾经遇到过同样的问题。此外,我还不小心将其放在了通用密钥中 key={Math.random()}。 - VanilJS

109

我一次又一次地回到这里,总是能在最后找到解决其他地方问题的办法。所以,我会在这里记录下来,因为我知道我会再次忘记它!

在我的情况下,input 失去焦点的原因是因为我在状态改变时重新渲染了 input

错误代码:

import React from 'react';
import styled from 'styled-components';

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    const Container = styled.div``;
    const Input = styled.input``;
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

因此,问题在于我总是从一个地方开始编写代码以快速测试,然后将其全部拆分成单独的模块。但是,在这种情况下,更新输入更改的状态会触发渲染函数并且焦点会丢失。

解决方法很简单,从一开始就进行模块化,换句话说,“将Input组件从render函数中移出”

修复后的代码

import React from 'react';
import styled from 'styled-components';

const Container = styled.div``;
const Input = styled.input``;

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

参考解决方案:https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947


1
我在render函数中使用了一个HOC,将HOC调用移动到组件定义中解决了问题。与此答案有些相似。 - Mark E
3
我认为这是我的问题,但是我在将组件移出render()class ... extends Component时遇到了困难,因为涉及到对this的引用,例如onChange={this.handleInputChange} - Nateous
34
对于 React 类组件而言,这个故事的寓意是不要在 render 函数内定义组件;对于 React 函数式组件而言,不要在函数体内定义组件。请注意,本翻译仅提供文字转换,不包含解释或其他额外信息。 - Wong Jia Hau
2
@Wong Jia Hau:当你在React函数组件的函数体中定义组件时会发生什么? 我之前也遇到了类似的问题,通过采用你的建议解决了它。 - j10
1
我的组件也遇到了类似的问题,原因是"'styled-components'"在渲染函数内部,导致每次都会渲染一个新的styledComponent。感谢您给我提示! - Kyzer
显示剩余5条评论

54

我遇到了与hooks相同的症状。然而,我的问题是在父组件内定义了一个子组件。

错误:

const Parent =() => {
    const Child = () => <p>Child!</p>
    return <Child />
}

正确:

const Child = () => <p>Child!</p>
const Parent = () => <Child />


1
在“错误”的例子中,“const child”应该改为“const Child”。 - David Torres
4
请注意,您仍然可以通过常规函数调用使用此嵌套子元素。例如,return <div>{Child()}</div> 是可以的,尽管有点丑。有时我会将JSX表达式中不可重用的部分提取到本地闭包中以提高可读性,而无需将它们变成具有大量属性传递的完整独立组件。焦点行为可能需要特别注意。 - bluenote10
2
每次父组件重新渲染时,它都会将子组件挂载(mount)上去。 - ilkerkaran
3
补充一下@ilkerkaran所说的,因为React会在每次状态更新时运行你的组件函数(即Parent函数),所以其中的Child值将在每次渲染时都不同。因此,React无法在每个渲染中重复使用子组件,每次更新都会得到新的元素。 - Yihao Gao

48

如果问题出现在 React 路由器的 <Route/> 中,使用render属性而不是component属性。

<Route path="/user" render={() => <UserPage/>} />

失去焦点是因为component属性每次都使用React.createElement而不是仅重新渲染更改。

详见:https://reacttraining.com/react-router/web/api/Route/component


3
这基本上是我的问题。具体来说,我将一个匿名函数传递给了组件属性,触发了焦点丢失: <Route component={() => <MyComponent/>} />,而我应该这样做 <Route component={MyComponent} /> - Daniel
请参考 https://dev59.com/R1gQ5IYBdhLWcg3wkEtJ#47660885。在我的情况下,这很有帮助。 - Shivam Jha

22

我的答案与@z5h所说的类似。

在我的情况下,我使用Math.random()生成一个唯一的key来给组件使用。

我以为key仅仅用于触发该特定组件的重新渲染,而不是重新渲染数组中的所有组件(我在代码中返回了一个组件数组)。我不知道它用于在重新渲染后恢复状态。

删除它对我有帮助。


2
像 https://www.npmjs.com/package/shortid 这样的模块也是处理类似问题的好方法。 - skwidbreth
请参考 https://dev59.com/R1gQ5IYBdhLWcg3wkEtJ#47660885。在我的情况下,这很有帮助。 - Shivam Jha
谢谢。这也是我的问题。我使用了 key={index+Date.now()}。因为它会改变,所以组件无法保留其状态。切换到 key={index} 对我有帮助。 - Hayden Linder

16
我所做的是将value属性更改为defaultValue,第二个更改是将onChange事件更改为onBlur

OnBlur解决了我的问题,谢谢。 - Matheus Ribeiro
哇,那就是它了。 - haron68
非常感谢!这个问题让我烦恼了整整两天。我尝试添加密钥和其他一切我能想到的方法,但似乎都没有起作用。 - Sumeet

11

autoFocus属性应用于input元素可以作为解决方案,当只有一个需要聚焦的输入时。在这种情况下,key属性是不必要的,因为它只是一个元素,而且您不必担心将input元素分解为自己的组件,以避免在重新渲染主组件时失去焦点。


不得已才尝试这个解决方法。我很高兴它奏效了。很明显我的应用程序存在一个漏洞,因为每当我尝试在页面的唯一搜索输入框中键入时,应用程序会重新渲染。但是让autofocus到输入字段是一个开始。 - KeshavDulal
1
这对我也起作用了,在尝试了许多其他解决方案之后。它仍然会重新渲染,这并不是很好,但它是可行的。谢谢! - Jacob Sherwood
请注意,如果您在多个输入框中使用此功能,可能会引入其他问题。例如:在一个输入框中输入内容并失去焦点后,用户将自动跳转到启用了自动聚焦的输入框。 - CWSites

6

我遇到了同样的问题。

我的代码问题在于我创建了一个嵌套的JSX元素数组,就像这样:

const example = [
            [
                <input value={'Test 1'}/>,
                <div>Test 2</div>,
                <div>Test 3</div>,
            ]
        ]

...

render = () => {
    return <div>{ example }</div>
}

每当我更新父元素时,此嵌套数组中的每个元素都会重新渲染。因此,输入框每次都会丢失其“ref”属性。
我通过将内部数组转换为一个React组件(具有一个渲染函数的函数)来解决这个问题。
const example = [
            <myComponentArray />
        ]

 ...

render = () => {
    return <div>{ example }</div>
}

编辑:

当我构建嵌套的React.Fragment时,出现了同样的问题。

const SomeComponent = (props) => (
    <React.Fragment>
        <label ... />
        <input ... />
    </React.Fragment>
);

const ParentComponent = (props) => (
    <React.Fragment>
        <SomeComponent ... />
        <div />
    </React.Fragment>
);

请参考 https://dev59.com/R1gQ5IYBdhLWcg3wkEtJ#47660885。在我的情况下,这很有帮助。 - Shivam Jha

5

我解决了相同的问题,删除了input和其父元素中的key属性。

// Before
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    key={ Math.random }
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>
// After
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>


在我的情况下,我删除了该键,它完美地工作了。我正在映射多个文本输入。 - Appy Mango
需要注意的是,Math.random作为键是React反模式。键的整个意义在于告诉React,“这是我的组件,你可以通过这个键找到它。当需要时重新渲染此组件”。使用一个新的随机键无法让React跟踪它。 - van

4

提供的答案并没有帮助到我,但是我遇到了独特的情况。

为了清理代码,我倾向于使用这种格式,直到我准备将组件移入另一个文件中。

render(){
   const MyInput = () => {
      return <input onChange={(e)=>this.setState({text: e.target.value}) />
   }
   return(
      <div>
         <MyInput />
      </div>
   )

但是这样会导致失去焦点,当我直接将代码放在div中时,它可以正常工作。
return(
   <div>
      <input onChange={(e)=>this.setState({text: e.target.value}) />
   </div>
)

我不知道为什么会这样,这是我写代码时遇到的唯一问题,而且我在大多数文件中都这么做。但如果有人也这样做,那么就会导致失去焦点。


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