如何强制在React组件上重新安装?


103

可以说我有一个具有条件渲染的视图组件:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

MyInput看起来像这样:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

可以说employed是真的。每当我将其切换为false并渲染另一个视图时,都只会unemployment-duration重新初始化。还会unemployment-reason从中预填充来自的值job-title(如果条件更改之前已给出值)。

如果我将第二个渲染例程中的标记更改为如下所示:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

似乎一切正常。看起来React只是无法区分'职位-头衔'和'失业原因'。

请告诉我我做错了什么...


1
什么是data-reactid对每一个MyInput(或者input,如在DOM)的之前和之后的元素employed开关?
克里斯

@Chris我未能指定MyInput-component呈现输入包装在中<div>data-reactid包装的div和输入字段上的属性似乎不同。job-title输入获取id data-reactid=".0.1.1.0.1.0.1",而unemployment-reason输入获取iddata-reactid=".0.1.1.0.1.2.1"
2016年

1
那又如何unemployment-duration呢?
克里斯

@克里斯抱歉,我讲得太早了。在第一个示例(不带“ diff me”跨度)reactid中,job-title和的属性相同unemployment-reason,而在第二个示例(带“ diff me”跨度)中,它们是不同的。
o01年

@克里斯对unemployment-durationreactid属性始终是唯一的。
2016年

Answers:


108

可能发生的事情是React认为在渲染之间仅添加了一个MyInputunemployment-duration)。因此,job-title永不替换为unemployment-reason,这也是交换预定义值的原因。

当React进行比较时,它将根据它们的key属性确定哪些是新组件,哪些是旧组件。如果代码中未提供此类密钥,它将生成自己的密钥。

您提供的最后一个代码片段起作用的原因是,React本质上需要更改父对象下所有元素的层次结构,div并且我相信这会触发所有子对象的重新渲染(这就是它起作用的原因)。如果您将span而不是顶部添加到底部,则前面的元素的层次结构不会改变,并且这些元素的层次也不会重新呈现(问题仍然存在)。

这是官方的React文档说的:

当孩子们随机移动(如在搜索结果中)或将新组件添加到列表的前面(如在流中)时,情况变得更加复杂。在这些情况下,必须在整个渲染过程中维护每个孩子的身份和状态,您可以通过为其分配密钥来唯一地标识每个孩子。

当React调和带键的子代时,它将确保所有带键的子代都将重新排序(而不是破坏)或销毁(而不是重用)。

您应该能够通过key为父级div或所有MyInput元素自己提供一个唯一元素来解决此问题。

例如:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

要么

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

现在,当React执行diff时,它会发现diff divs是不同的,并将重新渲染它,包括其所有子对象(第一个示例)。在第二个示例中,差异比较成功job-titleunemployment-reason因为它们现在具有不同的键。

当然,您可以使用任何想要的键,只要它们是唯一的即可。


2017年8月更新

为了更好地了解键在React中的工作方式,我强烈建议阅读我对理解React.js中的唯一键的回答


2017年11月更新

此更新应该已经发布了一段时间,但是ref现在不建议使用in中的字符串。例如ref="job-title",现在应该改为ref={(el) => this.jobTitleRef = el}(例如)。有关更多信息,请参阅我对使用this.refs的弃用警告的回答。


10
it will determine which components are new and which are old based on their key property.-这是...关键...据我了解
Larry

1
非常感谢 !我还发现,地图中的所有子组件均已成功重新渲染,我不明白为什么。
ValentinVoilean

一个不错的提示是使用状态变量(例如ID会发生变化)作为div的键!
Macilias

200

更改组件的键。

<Component key="1" />
<Component key="2" />

由于密钥已更改,因此将卸载Component并安装新的Component实例。

编辑:记录在您上可能不需要派生状态

当一个键改变时,React将创建一个新的组件实例,而不是更新当前实例。键通常用于动态列表,但在这里也很有用。


45
这应该在React文档中更加明显。这完全达到了我的要求,但是花了数小时才找到。
保罗

4
好吧,这对我有很大帮助!谢谢!我需要重置CSS动画,但是唯一的方法是强制重新渲染。我同意在React文档
Alex中

@ Alex.P。真好!CSS动画有时可能很棘手。我希望看到一个例子。
亚历克斯K

1
描述该技术的React文档:reactjs.org/blog/2018/06/07/…–
GameSalutes

谢谢。我对此深表感谢。我尝试将随机数提供给未声明的道具,例如“ randomNumber”,但唯一起作用的道具是钥匙。
ShameWare

5

使用setState在视图中改变employed状态的财产。这是React渲染引擎的示例。

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

我已经在做这个了。“已使用”变量是视图组件状态的一部分,可通过其setState函数对其进行更改。很抱歉没有解释。
2016年

“使用”变量应为React状态的属性。在您的代码示例中这并不明显。
德米特里(Dmitriy)

您如何更改this.state.employed?请显示代码。您确定在代码的正确位置使用setState吗?
德米特里(Dmitriy)

视图组件比我的示例显示的要复杂一些。除了MyInput组件之外,它还呈现一个单选按钮组,该组使用onChange处理程序控制“已使用”的值。在onChange处理程序中,我相应地设置了视图组件的状态。当单选按钮组的值更改时,视图会重新渲染良好,但是React认为第一个MyInput-component与“ employed”更改之前相同。
2016年

0

我正在为我的应用程序开发Crud。这就是我做到的方式Reactstrap作为我的依赖。

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

里面有一些React Hooks


欢迎来到SO!您可能想回顾一下如何写一个好的答案。除了发布代码之外,还对如何解决问题添加一些解释。
displacedtexan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.