如何从React组件的状态对象中删除属性

50

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

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

在这里从Object.keys(this.state)中删除"foo"是否可能?

replaceState方法看起来是一个可尝试的明显方法,但它已经被弃用了。


2
那个已经被弃用了。 - Madbreaks
11个回答

40

你可以这样将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>
        );
    }
});

示例

更新

之前的解决方案只是从 foo 中删除了值和 keystate 中仍然存在,如果您需要完全从 state 中删除键,则可能的一种解决方案是使用带有一个父级 keysetState,如下所示:

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>


36
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>
    );
}
});

2
不确定为什么这个回答被踩了,这绝对是正确的答案。 - Paidenwaffle
1
是的,关键是从副本中删除而不是原始对象,并且知道在哪里使用模式。不要从复杂状态对象中删除键。如果您将状态对象用作简单映射,例如在父组件中注册多个编程管理的子组件实例(对我来说是按时间戳的吐司),那么请这样做。 - Kyle Zimmer
这似乎很简单和合法。 - Aashiq
不知道为什么最合适的在底部。谢谢啊。 - Daman Arora
1
如果键和值已设置,则此操作不会删除键。 - Zijian

6
在GitHub上的React源代码中,有一个名为_processPendingState的方法,它位于ReactCompositeComponent.js文件中。此方法是实现从组件setState调用中合并状态的最终方法;
``` _processPendingState: function(props, context) { 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 });

这看起来有点丑,但它为你提供了额外的信息,帮助你区分一个被设置为undefined的值和根本没有设置值的情况。而且它与React的内部、事务、状态改变批处理以及其他任何恐怖的东西都非常兼容。在这里多花一点复杂度比试图猜测React的内部要好,因为它们充满了像事务协调、管理已弃用的功能(如replaceState)等恐怖的东西。


"setState总是合并的,因此删除的属性将被忽略。这是正确的。尝试了几乎所有的方法,并调试了几个小时的setState后,我找不到任何方法可以使状态对象中的现有键变为未定义/已删除。一旦它在那里,它将永远留在那里。我能想到的最好的办法是将其设置为null...这让我不太满意。" - John Newman
@JohnNewman 嗯,我遇到了类似的问题,但是我的日志记录器在setState中出了问题。当我将日志记录移动到setState(object,callback)中的回调函数中时,我看到了正确的结果。 - Roman Podlinov

4

之前的解决方案是反面模式,因为它改变了 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,它可以删除store中不必要的键,但现在它已经被弃用


4
这两种解决方案也是错误的。第一个原因是,Object.assign()不会进行深度克隆,在更复杂的应用程序中可能会无意中直接修改状态。第二个问题存在于您的两个解决方案中,并且与@alexander-t的答案相同。您没有按照要求从Object.keys()中删除键,而是只更改了其值。 - Vince
4
对我来说,这个主题没有情感意义,而且没人能投两票。我曾经将你的回答投了一次反对票,因为它没有解决问题所提出的问题。尽管如此,我不确定这个解决方案是否真正解决了任何问题。如果你遍历状态对象的属性来创建列表或表格,将其中一个属性的值设置为 null/undefined 将至少生成一个空的列表项或表格行。但是,如果代码编写成使用子对象的属性,则将该值设置为 null / undefined 将导致难以排除的错误。 - Vince

4

在 JavaScript 对象中,当我们使用 undefined 或者 null 来移除一个属性时,实际上并没有真正删除它。因此,我们应该在属性之前使用 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中我们使用hooks,上述方法可能会导致一些bug,特别是当我们使用effects检查状态改变时。为此,我们需要在删除属性后设置状态:

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
你可以使用 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>


0

唯一的方法是创建一个深度复制,然后从深拷贝中删除该属性,并从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;
          });

0

使用 dot-prop-immutable

import dotPropImmutable from "dot-prop-immutable";

onClick() {
    this.setState(
       dotPropImmutable.delete(this.state, 'foo')
    );
}

-1

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

//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 });
}

-1
如果需要在函数中删除并且键需要是一个变量,请尝试以下代码:
removekey = (keyname) => {
    let newState = this.state;
    delete newState[keyname];
    this.setState(newState)
    // do not wrap the newState in additional curly braces  
}

this.removekey('thekey');

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

1
直接状态突变不好。在这种情况下,newState 只是指向 this.state,而不是它的副本。你有95%的概率在这个过程中看不到错误,但它肯定会导致难以调试的问题。请使用 object.assign 来制作副本。 - Paidenwaffle
1
让 newState = this.state // newState 不是指向与 this.state 相同的引用吗?因此,从 newState 中删除内容将会从 this.state 中删除,这样就会发生变异,这是不应该做的。 - Akshay Vijay Jain

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