【问题标题】:How can I remove an attribute from a React component's state object如何从 React 组件的状态对象中删除属性
【发布时间】:2015-12-29 08:13:06
【问题描述】:

如果我有一个在其状态上设置了属性的 React 组件:

onClick() {
    this.setState({ foo: 'bar' });
}

是否可以从Object.keys(this.state) 中删除"foo"

replaceState 方法看起来是显而易见的尝试方法,但它已被弃用。

【问题讨论】:

  • 那是deprecated

标签: reactjs state


【解决方案1】:

当我们使用undefinednull 删除一个属性时,我们实际上并没有删除它。因此,对于 Javascript 对象,我们应该在属性之前使用 delete 关键字:

//The original object:
const query = { firstName:"Sarah", gender: "female" };

//Print the object:
console.log(query);

//remove the property from the object:
delete query.gender;

//Check to see the property is deleted from the object:
console.log(query);

然而,在 React Hooks 中,我们使用了钩子,上述方法可能会导致一些错误,尤其是当我们在状态改变时使用效果来检查某些东西时。为此,我们需要在移除属性后设置状态:

import { useState, useEffect } from "react";

const [query, setQuery] = useState({firstName:"Sarah", gender:"female"});
 
//In case we do something based on the changes
useEffect(() => {
    console.log(query);
  }, [query]);

//Delete the property:
delete query.gender;

//After deleting the property we need to set is to the state:
setQuery({ ...query });

【讨论】:

    【解决方案2】:

    使用dot-prop-immutable

    import dotPropImmutable from "dot-prop-immutable";
    
    onClick() {
        this.setState(
           dotPropImmutable.delete(this.state, 'foo')
        );
    }
    

    【讨论】:

      【解决方案3】:

      您可以将foo 设置为undefined,就像这样

      var Hello = React.createClass({
          getInitialState: function () {
              return {
                  foo: 10,
                  bar: 10
              }
          },
      
          handleClick: function () {
              this.setState({ foo: undefined });
          },
      
          render: function() {
              return (
                  <div>
                      <div onClick={ this.handleClick.bind(this) }>Remove foo</div>
                      <div>Foo { this.state.foo }</div>
                      <div>Bar { this.state.bar }</div>
                  </div>
              );
          }
      });
      

      Example

      更新

      之前的解决方案只是从foo 中删除值,而key技能存在于state,如果您需要从state 中完全删除密钥,可能的解决方案之一是@987654330 @ 与一位家长key,就像这样

      var Hello = React.createClass({
        getInitialState: function () {
          return {
            data: {
              foo: 10,
              bar: 10
            }
          }
        },
          	
        handleClick: function () {
          const state = {
            data: _.omit(this.state.data, 'foo')
          };
          
          this.setState(state, () => {
            console.log(this.state);
          });
        },
              
        render: function() {
          return (
            <div>
              <div onClick={ this.handleClick }>Remove foo</div>
              <div>Foo { this.state.data.foo }</div>
              <div>Bar { this.state.data.bar }</div>
            </div>
          );
        }
      });
      
      ReactDOM.render(<Hello />, document.getElementById('container'))
      <script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
      <div id="container"></div>

      【讨论】:

        【解决方案4】:

        唯一的方法是创建一个深层副本,然后从深层克隆中删除该属性并从 setState 更新程序函数返回深层克隆

        this.setState(prevState => {
                    const newState = {
                      formData: {
                        ...prevState.formData,
                        document_details: {
                          ...prevState.formData.document_details,
                          applicant: {
                            ...prevState.formData?.document_details?.applicant
                            keyToBeDeleted: dummVAlue //this is redundant
                          }
                        }
                      }
                    };
                    delete newState.formData.document_details.applicant.keyToBeDeleted;
                    return newState;
                  });
        

        【讨论】:

          【解决方案5】:

          如果删除是在一个函数中并且键需要是一个变量,试试这个:

          removekey = (keyname) => {
              let newState = this.state;
              delete newState[keyname];
              this.setState(newState)
              // do not wrap the newState in additional curly braces  
          }
          
          this.removekey('thekey');
          

          与史蒂夫的答案几乎相同,但在一个函数中。

          【讨论】:

          • 直接状态突变不好。在这种情况下 newState 只是指向 this.state 它不是它的副本。 95% 的时间你不会看到这个错误,但它肯定会导致难以调试问题。使用 object.assign 进行复制
          • let newState = this.state // 不是 newState 指向与 this.state 相同的引用,从 newState 中删除,将从 this.state 中删除,因此发生变异// 这不是要做的
          【解决方案6】:
          var Hello = React.createClass({
          getInitialState: function () {
              return {
                  foo: 10,
                  bar: 10
              }
          },
          
          handleClick: function () {
              let state = {...this.state};
              delete state.foo;
              this.setState(state);
          },
          
          render: function() {
              return (
                  <div>
                      <div onClick={ this.handleClick.bind(this) }>Remove foo</div>
                      <div>Foo { this.state.foo }</div>
                      <div>Bar { this.state.bar }</div>
                  </div>
              );
          }
          

          });

          【讨论】:

          • 不确定为什么会被否决这是 100% 的正确答案
          • 是的,关键是从副本中删除,而不是从原件中删除,并且要知道在哪里使用该模式。不要从复杂的状态对象中删除键。如果您将状态对象用作简单的映射,例如在父组件中注册多个以编程方式管理的子组件实例(对我来说按时间戳在烤面包机中烤面包),那么请执行此操作。
          • 这看起来简单合法
          • 不知道为什么最合适的是在底部。谢谢大佬。
          【解决方案7】:

          我认为这是一个不错的方法 =>

          //in constructor
          let state = { 
             sampleObject: {0: a, 1: b, 2: c }
          }
          
          //method
          removeObjectFromState = (objectKey) => {
             let reducedObject = {}
             Object.keys(this.state.sampleObject).map((key) => {
                if(key !== objectKey) reducedObject[key] = this.state.sampleObject[key];
             })
             this.setState({ sampleObject: reducedObject });
          }
          

          【讨论】:

            【解决方案8】:

            如果您想完全重置状态(删除大量项目),可以使用以下方法:

            this.setState(prevState => {
                let newState = {};
                Object.keys(prevState).forEach(k => {
                    newState[k] = undefined;
                });
                return newState;
            });
            

            使用setState 的这种变体允许您在调用期间访问整个状态,而this.state 可能有点过时(由于之前的setState 调用尚未完全处理)。

            【讨论】:

              【解决方案9】:

              在GitHub的React源码ReactCompositeComponent.js中有一个叫做_processPendingState的方法,它是实现component.setState调用合并状态的终极方法;

              ``` _processPendingState:函数(道具,上下文){ var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null;

              if (!queue) {
                return inst.state;
              }
              
              if (replace && queue.length === 1) {
                return queue[0];
              }
              
              var nextState = replace ? queue[0] : inst.state;
              var dontMutate = true;
              for (var i = replace ? 1 : 0; i < queue.length; i++) {
                var partial = queue[i];
                let partialState = typeof partial === 'function'
                  ? partial.call(inst, nextState, props, context)
                  : partial;
                if (partialState) {
                  if (dontMutate) {
                    dontMutate = false;
                    nextState = Object.assign({}, nextState, partialState);
                  } else {
                    Object.assign(nextState, partialState);
                  }
                }
              }
              

              ```

              在该代码中,您可以看到实现合并的实际行;

              nextState = Object.assign({}, nextState, partialState);
              

              在这个函数中没有任何地方调用delete 或类似的,这意味着它不是真正的预期行为。此外,完全复制 stat、删除属性和调用 setState 将不起作用,因为 setState 始终是合并,所以删除的属性将被忽略。

              还请注意,setState 不会立即起作用,而是批量更改,因此如果您尝试克隆整个状态对象并且只进行一个属性更改,您可能会清除之前对 setState 的调用。正如 React 文档所说;

              React 可以将多个 setState() 调用批处理到单个更新中以提高性能。

              因为 this.props 和 this.state 可能是异步更新的,所以你不应该依赖它们的值来计算下一个状态。

              您实际上可以做的是添加更多信息;

              this.setState({ xSet: true, x: 'foo' });
              this.setState({ xSet: false, x: undefined });
              

              这是丑陋的,当然,但它为您提供了区分设置为未定义的值和根本未设置的值所需的额外信息。此外,它与 React 的内部结构、事务、状态更改批处理和任何其他可怕的东西配合得很好。最好在这里采取一些额外的复杂性,而不是尝试猜测 Reacts 内部结构,其中充满了诸如事务协调、管理诸如 replaceState 等已弃用功能之类的恐怖

              【讨论】:

              • "setState 始终是一个合并,因此删除的属性将被忽略" 这是正确的。在尝试了几乎所有东西并调试到 setState 几个小时之后;没有办法(我能找到)让状态对象中的现有键转到未定义/删除。一旦它在那里,它将永远在那里。我能想到的最好的办法就是将其设置为 null ...我不太高兴。
              • @JohnNewman 好吧,我遇到了类似的问题,但我的 setState 记录器中有问题。一旦我在 setState(object, callback) 中登录到回调函数,我就看到了正确的结果。
              【解决方案10】:

              您可以使用Object.assign 在正确的深度制作应用程序状态的浅表副本,并从副本中删除元素。然后使用setState 将修改后的副本合并回应用程序的状态。

              这不是一个完美的解决方案。像这样复制整个对象可能会导致性能/内存问题。 Object.assign 的浅拷贝有助于缓解内存/性能问题,但您还需要了解新对象的哪些部分是副本,哪些部分是对应用程序状态数据的引用。

              在下面的示例中,修改ingredients 数组实际上会直接修改应用程序状态。

              将不需要的元素的值设置为nullundefined 不会删除它。

              const Component = React.Component;
              
              class App extends Component {
                constructor(props) {
                  super(props);
                  this.state = {
                    "recipes": {
                      "1": {
                        "id": 1,
                        "name": "Pumpkin Pie",
                        "ingredients": [
                          "Pumpkin Puree",
                          "Sweetened Condensed Milk",
                          "Eggs",
                          "Pumpkin Pie Spice",
                          "Pie Crust"
                        ]
                      },
                      "2": {
                        "id": 2,
                        "name": "Spaghetti",
                        "ingredients": [
                          "Noodles",
                          "Tomato Sauce",
                          "(Optional) Meatballs"
                        ]
                      },
                      "3": {
                        "id": 3,
                        "name": "Onion Pie",
                        "ingredients": [
                          "Onion",
                          "Pie Crust",
                          "Chicken Soup Stock"
                        ]
                      },
                      "4": {
                        "id": 4,
                        "name": "Chicken Noodle Soup",
                        "ingredients": [
                          "Chicken",
                          "Noodles",
                          "Chicken Stock"
                        ]
                      }
                    },
                    "activeRecipe": "4",
                    "warningAction": {
                      "name": "Delete Chicken Noodle Soup",
                      "description": "delete the recipe for Chicken Noodle Soup"
                    }
                  };
                  
                  this.renderRecipes = this.renderRecipes.bind(this);
                  this.deleteRecipe = this.deleteRecipe.bind(this);
                }
                
                deleteRecipe(e) {
                  const recipes = Object.assign({}, this.state.recipes);
                  const id = e.currentTarget.dataset.id;
                  delete recipes[id];
                  this.setState({ recipes });
                }
                
                renderRecipes() {
                  const recipes = [];
                  for (const id in this.state.recipes) {
                    recipes.push((
                      <tr>
                        <td>
                          <button type="button" data-id={id} onClick={this.deleteRecipe}
                          >&times;</button>
                        </td>
                        <td>{this.state.recipes[id].name}</td>
                      </tr>
                    ));
                  }
                  return recipes;
                }
                              
                render() {
                  return (
                    <table>
                      {this.renderRecipes()}
                    </table>
                  );
                }
              }
              
              ReactDOM.render(<App />, document.getElementById('app'));
              <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
              <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
              <main id="app"></main>

              【讨论】:

                【解决方案11】:

                以前的解决方案 - 是反模式,因为它改变了 this.state。错了!

                使用这个(旧方法):

                let newState = Object.assign({}, this.state) // Copy state
                newState.foo = null // modyfy copyed object, not original state
                // newState.foo = undefined // works too
                // delete newState.foo // Wrong, do not do this
                this.setState(newState) // set new state
                

                或者使用 ES6 糖:

                this.setState({...o, a:undefined})
                

                很可爱,不是吗? ))

                在旧的 React 语法(原始,不是 ES6)中,这有 this.replaceState,它删除了存储中不必要的键,但现在它是 deprecated

                【讨论】:

                • 这两种解决方案也是错误的。第一个原因是,Object.assign()won't do a deep clone,因此您最终可能会直接修改状态而不打算在更复杂的应用程序中进行。第二个问题出现在您的两个解决方案中,与@alexander-t 的答案相同。您没有按照要求从 Object.keys() 中删除密钥。你只是在改变它的价值。
                • 这个话题对我来说没有情感意义,没有人可以投票两次。我投了你的答案一次,因为它不能解决问题提出的问题。不过,我不确定这如何解决任何问题。如果您遍历状态对象的属性以创建列表或表格,将其中一个的值设置为null / undefined 至少会生成一个空的列表项或表格行。如果编写代码使用子对象的属性,将值设置为null/undefined将导致难以排查的错误。
                猜你喜欢
                • 2018-07-01
                • 1970-01-01
                • 2021-07-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-05-09
                • 2021-01-30
                • 2021-06-17
                相关资源
                最近更新 更多