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

Posted

技术标签:

【中文标题】如何强制重新安装 React 组件?【英文标题】:How to force remounting on React components? 【发布时间】:2016-06-17 23:10:10 【问题描述】:

假设我有一个具有条件渲染的视图组件:

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 只是无法区分 'job-title' 和 'unemployment-reason'。

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

【问题讨论】:

employed 开关前后的每个 MyInput(或 input,如 DOM)元素上的 data-reactid 是什么? @Chris 我未能指定 MyInput 组件呈现包装在 &lt;div&gt; 中的输入。 data-reactid 属性在包装 div 和输入字段上似乎都不同。 job-title 输入得到 id data-reactid=".0.1.1.0.1.0.1",而unemployment-reason 输入得到 id data-reactid=".0.1.1.0.1.2.1" 那么unemployment-duration呢? @Chris 抱歉,我说得太早了。在第一个示例中(没有“diff me”跨度)reactid 属性在 job-titleunemployment-reason 上是相同的,而在第二个示例中(使用 diff 跨度)它们是不同的。 @Chris for unemployment-duration reactid 属性始终是唯一的。 【参考方案1】:

更改组件的键。

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

组件将被卸载并安装一个新的组件实例,因为密钥已更改。

编辑:记录在You Probably Don't Need Derived State:

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

【讨论】:

这在 React 文档中应该更加明显。这正是我所追求的,但花了几个小时才找到。 这对我帮助很大!谢谢!我需要重置 CSS 动画,但唯一的方法是强制重新渲染。我同意这应该在反应文档中更明显 @Alex.P.好的! CSS 动画有时会很棘手。我有兴趣看一个例子。 描述此技术的 React 文档:reactjs.org/blog/2018/06/07/… 谢谢。我对此非常感激。我尝试将随机数提供给未声明的道具,例如“randomNumber”,但唯一有效的道具是关键。【参考方案2】:

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

当 React 进行 diff 时,它将根据 key 属性确定哪些组件是新的,哪些是旧的。如果代码中没有提供这样的密钥,它将生成自己的。

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

官方React documentation是这样说的:

当孩子们被打乱(如在搜索结果中)或新组件被添加到列表的前面(如在流中)时,情况会变得更加复杂。在这些必须跨渲染通道维护每个子节点的身份和状态的情况下,您可以通过为其分配一个键来唯一标识每个子节点。

当 React 协调键控子级时,它将确保任何具有键的子级都将被重新排序(而不是破坏)或销毁(而不是重用)。

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

例如:

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 时,它会看到 divs 不同,并将重新渲染它,包括它的所有孩子(第一个示例)。在第二个示例中,差异将在 job-titleunemployment-reason 上成功,因为它们现在具有不同的键。

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


2017 年 8 月更新

为了更好地了解 React 中键的工作原理,我强烈建议阅读我对 Understanding unique keys in React.js 的回答。


2017 年 11 月更新

此更新应该在不久前发布,但现在已弃用在 ref 中使用字符串文字。例如,ref="job-title" 现在应该改为 ref=(el) =&gt; this.jobTitleRef = el(例如)。请参阅我对Deprecation warning using this.refs 的回复了解更多信息。

【讨论】:

it will determine which components are new and which are old based on their key property. - 这是...关键...我的理解 非常感谢!我还发现地图中的所有子组件都已成功重新渲染,但我不明白为什么。 一个很好的提示是使用状态变量(它会发生变化,例如 id)作为 div 的键!【参考方案3】:

在您的视图中使用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>
        );
    

【讨论】:

我已经这样做了。 'employed' 变量是视图组件状态的一部分,它通过它的 setState 函数进行更改。很抱歉没有解释。 'employed' 变量应该是 React 状态的属性。在您的代码示例中并不明显。 如何更改 this.state.employees?请显示代码。您确定在代码中的适当位置使用 setState 吗? 视图组件比我的示例显示的要复杂一些。除了 MyInput 组件之外,它还呈现一个单选按钮组,该组使用 onChange 处理程序控制 'employed' 的值。在 onChange 处理程序中,我相应地设置了视图组件的状态。当单选按钮组的值发生变化时,视图重新渲染得很好,但 React 认为第一个 MyInput-component 与 'employed' 更改之前相同。【参考方案4】:

我正在为我的应用程序开发 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!您可能想查看how to write a good answer。除了发布代码之外,对您如何解决问题添加一些说明。

以上是关于如何强制重新安装 React 组件?的主要内容,如果未能解决你的问题,请参考以下文章

React-Router 在路由更改时重新安装组件

如何在父组件中强制刷新子组件

什么可能导致 React Native 组件不断地卸载和重新安装?

Vue之重新渲染组件的正确方式

是否可以仅在 react-router 转换时仅重新安装新的子组件

React Hook 功能组件防止重新渲染