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

IT技术 reactjs state
2021-03-26 02:19:04

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

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

可以从"foo"这里删除Object.keys(this.state)吗?

replaceState方法看起来像明显的方法来尝试,但它是不被提倡。

6个回答

你可以设置fooundefined,像这样

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

更新

以前的解决方案只是从fookey技能中删除值state,如果您需要完全删除密钥state,可能的解决方案之一可以是setState一个父级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>

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

});

是的,关键是从副本中删除而不是从原件中删除,并且完全知道在哪里使用该模式。不要从复杂的状态对象中删除键。如果您使用状态对象作为简单的映射,例如在父组件中注册多个以编程方式管理的子组件实例(对我来说是按时间戳在烤面包机中烤面包),那么请执行此操作。
2021-05-26 02:19:04
这似乎简单而合法
2021-05-31 02:19:04
不知道为什么这会被低估,这是 100% 正确的答案
2021-06-01 02:19:04
不知道为什么最合适在底部。谢啦。
2021-06-05 02:19:04

ReactCompositeComponent.jsGitHub 上的 React 源码中有一个方法叫做_processPendingState,它是实现从调用 component.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或类似,这意味着它不是真正的预期行为。此外,完全复制统计信息、删除属性和调用 setState 将不起作用,因为 setState 始终是一个合并,因此删除的属性将被忽略。

另请注意,setState 不会立即生效,而是批量更改,因此如果您尝试克隆整个 state 对象并仅更改一个属性,则可能会清除之前对 setState 的调用。正如 React 文档所说;

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

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

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

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

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

@JohnNewman 好吧,我遇到了类似的问题,但我的记录器中的 setState 出现了问题。当我在 setState( object, callback) 中登录回调函数时,我看到了正确的结果。
2021-05-23 02:19:04
“setState 始终是一个合并,因此删除的属性将被忽略”这是正确的。在尝试了几乎所有东西并调试到 setState 几个小时之后;没有办法(我能找到)让状态对象中的现有键转到未定义/删除。一旦它在那里,它将永远留在那里。我能想到的最好方法是将它设置为 null ......我对此不太满意。
2021-06-06 02:19:04

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

这两种解决方案也是错误的。第一个原因是,Object.assign() 不会进行深度克隆,因此您最终可能会直接修改状态而不打算在更复杂的应用程序中进行。第二个问题出现在您的两个解决方案中,它与@alexander-t 的答案相同。您没有Object.keys()按照要求从中删除密钥你只是在改变它的value。
2021-05-24 02:19:04
这个话题对我来说没有情感意义,没有人可以投票两次。我曾经否决了您的答案,因为它没有解决问题提出的问题。不过,我不确定这如何解决任何问题。如果迭代 state 对象的属性来创建列表或表格,将其中一个的值设置为null/undefined至少会生成一个空的列表项或表格行。如果编写代码使用子对象的属性,将值设置为null/undefined将导致难以排查的错误。
2021-05-27 02:19:04

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