【问题标题】:Inside of a function the state is always same/initial在函数内部,状态始终相同/初始
【发布时间】:2021-03-25 09:32:05
【问题描述】:

从对象调用 remove() 函数。如何在 remove() 函数中获取更新的状态值。

const [InfoBoxPin, setInfoBoxPin] = useState([]) 

const createInfoBoxPin = (descriptions) =>{
       
           var newPin = {
                "location":currentLoc, 
                "addHandler":"mouseover", 
                "infoboxOption": { 
                title: 'Comment', 
                description: "No comment Added",
                actions: [{
                    label:'Remove Pin',
                    eventHandler: function () {
                        remove(newPin.location) //FUNCTION CALLED HERE
                    }
                }] }
                
                }
            setInfoBoxPin((InfoBoxPin)=>[...InfoBoxPin, newPin ]) // UPDATE STATE. Push the above object.
       
    }

const remove = (pos) =>{    
        console.log(InfoBoxPin)    //NEVER GETTING UPDATED STATE HERE.

        //Other codes here......
    }

这是一张必应地图信息卡。事件处理程序创建一个可以调用任何函数的按钮。

【问题讨论】:

  • 你在 JavaScript 中遇到了被称为“陈旧闭包”的问题,它不是特定于 react,但在提到 state 时很容易在 react 中遇到...css-tricks.com/…跨度>

标签: javascript reactjs react-hooks javascript-objects


【解决方案1】:

问题在于您在 remove 函数中引用旧状态信息。

当您调用 setInfoBoxPin 时,InfoBoxPin 的状态会在下次 UI 渲染时进行更新。这意味着在当前状态下它将是相同的(空),并且指向它的所有链接都将引用一个空数组。

为了解决这个问题,您必须将新状态从视图本身传递给适当的函数。

示例 #1

在这里,我为你创建了一个 CodeSandBox:

https://codesandbox.io/s/react-setstate-example-4d5eg?file=/src/App.js

以下是从中截取的代码:

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [state, setState] = useState({
    InfoBoxPin: [],
    output: []
  });

  const createInfoBoxPin = (descriptions) => {
    var newPin = {
      location: Math.round(Math.random(10) * 1000),
      addHandler: "mouseover",
      infoboxOption: {
        title: "Comment",
        description: "No comment Added",
        actions: [
          {
            label: "Remove Pin",
            eventHandler: removePin
          },
          {
            label: "Update Pin",
            eventHandler: updatePin
          }
        ]
      }
    };
    setState({ ...state, InfoBoxPin: [...state.InfoBoxPin, newPin] });
  };

  const updatePin = (key, state) => {
    var text = `Updating pin with key #${key} - ${state.InfoBoxPin[key].location}`;

    setState({ ...state, output: [...state.output, text] });

    console.log(text, state.InfoBoxPin);
  };

  const removePin = (key, state) => {
    var text = `Removing pin with key #${key} - ${state.InfoBoxPin[key].location}`;

    setState({ ...state, output: [...state.output, text] });

    console.log(text, state.InfoBoxPin);
  };

  return (
    <div className="App">
      <h1>React setState Example</h1>
      <h2>Click on a button to add new Pin</h2>
      <button onClick={createInfoBoxPin}>Add new Pin</button>
      <div>----</div>
      {state.InfoBoxPin.map((pin, pin_key) => {
        return (
          <div key={pin_key}>
            <span>Pin: {pin.location} &nbsp;</span>
            {pin.infoboxOption.actions.map((action, action_key) => {
              return (
                <button
                  key={action_key}
                  onClick={() => action.eventHandler(pin_key, state)}
                >
                  {action.label}
                </button>
              );
            })}
          </div>
        );
      })}
      <h4> OUTPUT </h4>
      <ul style={{ textAlign: "left" }}>
        {state.output.map((txt, i) => {
          return <li key={i}>{txt}</li>;
        })}
      </ul>
    </div>
  );
}

如您所见,我正在为一个名为 eventHandler 的函数提供一个带有 InfoBoxPin 值的新状态,用于按钮的 onclick 事件侦听器。

然后在该函数中,我可以使用新的InfoBoxPin 值,说明我需要它。

示例 #2 (ES6)

在这个例子中,我为 App 使用了一些不同的结构 - 使用类 (ES6)

通过为 App 使用一个类,我们可以使用不同的方法来操作 App 状态。

  1. func.bind(this) 可用于初始化时定义的函数
  2. func.call(this) 可用于调用不带参数的动态函数
  3. func.apply(this, [args]) 可用于调用带参数的动态函数

CodeSandBox 链接:

https://codesandbox.io/s/react-setstate-example-using-class-cz2u4?file=/src/App.js

代码片段:

import React from "react";
import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      InfoBoxPin: [],
      pinName: ""
    };

    /* ------------ method #1 using .bind(this) ------------ */
    this.setPinName = this.setPinName.bind(this);
  }

  remove(key) {
    this.state.InfoBoxPin.splice(key, 1);
    this.setState({ InfoBoxPin: this.state.InfoBoxPin });
  }

  add(pinName) {
    this.state.InfoBoxPin.push(pinName);
    this.setState({ InfoBoxPin: this.state.InfoBoxPin });
  }

  processPinNameAndAdd() {
    let pinName = this.state.pinName.trim();

    if (pinName === "") pinName = Math.round(Math.random() * 1000);

    this.add(pinName);
  }

  setPinName(event) {
    this.setState({ pinName: event.target.value });
  }

  render() {
    return (
      <div className="shopping-list">
        <h1>Pin List</h1>
        <p>Hit "Add New Pin" button.</p>
        <p>(Optional) Provide your own name for the pin</p>

        <input
          onInput={this.setPinName}
          value={this.state.pinName}
          placeholder="Custom name"
        ></input>

        {/* ------------ method #2 using .call(this) ------------ */}
        <button onClick={() => this.processPinNameAndAdd.call(this)}>
          Add new Pin
        </button>

        <ul>
          {this.state.InfoBoxPin.map((pin, pinKey) => {
            return (
              <li key={pinKey}>
                <div>pin: {pin}</div>

                {/* ------------ method #3 using .apply(this, [args]) ------------ */}
                <button onClick={() => this.remove.apply(this, [pinKey])}>
                  Delete Pin
                </button>
              </li>
            );
          })}
        </ul>
      </div>
    );
  }
}

export default App;

示例 #3 (ES6) 无法访问创建的元素

此示例将展示如何使用我们自己的参数和来自自动生成的 HTML 元素事件的状态数据来处理来自第三方库的回调

CodeSandBox 链接:

https://codesandbox.io/s/react-setstate-example-using-class-no-element-control-lcz5d?file=/src/App.js

代码片段:

import React from "react";
import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      InfoBoxPin: [],
      lastPinId: 0,
      pinName: ""
    };

    this.setPinName = this.setPinName.bind(this);
  }

  remove(id) {
    let keyToRemove = null;

    this.state.InfoBoxPin.forEach((pin, key) => {
      if (pin.id === id) keyToRemove = key;
    });

    this.state.InfoBoxPin.splice(keyToRemove, 1);
    this.setState({ InfoBoxPin: this.state.InfoBoxPin });
  }

  add(data, id) {
    this.state.InfoBoxPin.push({ id: id, data: data });
    this.setState({
      InfoBoxPin: this.state.InfoBoxPin,
      lastPinId: id
    });
  }

  processPinNameAndAdd() {
    let pinName = this.state.pinName.trim();

    if (pinName === "") pinName = Math.round(Math.random() * 1000);

    var newPinId = this.state.lastPinId + 1;

    var newPin = {
      location: pinName,
      addHandler: "mouseover",
      infoboxOption: {
        title: "Comment",
        description: "No comment Added",
        actions: [
          {
            label: "Remove Pin #" + newPinId,
            // [ES6 class only] using () => func() for callback function
            // By doing so we don't need to use bind,call,apply to pass class ref [this] to a function.
            eventHandler: () => this.remove(newPinId)
          }
        ]
      }
    };

    this.add(newPin, newPinId);
  }

  setPinName(event) {
    this.setState({ pinName: event.target.value });
  }

  render() {
    return (
      <div className="shopping-list">
        <h1>Pin List</h1>
        <p>Hit "Add New Pin" button.</p>
        <p>(Optional) Provide your own name for the pin</p>

        <input onInput={this.setPinName} value={this.state.pinName}></input>

        {/* 
          [ES6 class only] Using {() => func()} for event handler.
          By doing so we don't need to use func.bind(this) for passing class ref at constructor 
        */}
        <button onClick={() => this.processPinNameAndAdd()}>Add new Pin</button>

        <ul>
          {this.state.InfoBoxPin.map((pin, pKey) => {
            return (
              <li key={pKey}>
                <div>pin: {pin.data.location}</div>

                {pin.data.infoboxOption.actions.map((action, aKey) => {
                  return (
                    <button key={aKey} onClick={action.eventHandler}>
                      {action.label}
                    </button>
                  );
                })}
              </li>
            );
          })}
        </ul>
      </div>
    );
  }
}

export default App;

我在 State 中创建了 lastPinId 条目来跟踪新创建的 Pin 的 ID。 稍后可以使用 Pin id 在 InfoBoxPin 集合中找到所需的 pin 以进行删除。

如何注册你的 eventHandler 最重要的部分是:

eventHandler: () => this.remove(newPinId)

请注意,使用箭头函数() =&gt; func 对将类引用传递给remove 函数很重要。

【讨论】:

  • 感谢您的努力,但您无法更改 newPin 对象的结构,因为 Bing 地图将以这种格式接收它。事件处理程序键在 Bing 地图上生成一个可点击的按钮。所以你必须维护他们提供的结构, eventHandler: function (){} ..我们必须为事件处理程序分配一个函数。我们不能在那里写类型。有什么想法吗?
  • 我已经更新了我的答案和代码框示例。你可以检查一下。希望现在对您有所帮助。
  • 添加了另一个使用类的示例。这为使用不同的方法检索应用程序状态创造了机会。
  • 非常感谢。我会试试你的方法。
  • 我试过你的方法,但我的“钥匙”正在接受指针事件。
猜你喜欢
  • 2019-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-23
相关资源
最近更新 更多