【问题标题】:React Context API setState callback反应上下文 API setState 回调
【发布时间】:2020-02-28 22:39:59
【问题描述】:

我已经使用 Context API 概念成功地创建了一个 React 应用程序来管理全局状态,但是现在我遇到了一个问题,涉及在从子组件在 Context 组件中调用 setState 之后执行操作。

我有一个基于 ListView 类的组件,其中包含一个 ReactBootstrap NavDropdown 组件。下拉组件有两个列表项:日期和 study_name(我使用它来按日期或 study_name 对列表进行排序):

<NavDropdown title="Order By" id="order-nav-dropdown" style={navElementStyle} onSelect={this.onSelect}>

在同一个ListView组件中,我有如下函数来捕捉NavDropdown的onSelect事件:

onSelect = (e, obj) => {

    var match = obj.target.href.match(/#(.+)$/);

    if(this.context.state[match[1]] !== e){
        this.context.updateState(match[1],  e);
    }
  }

为了管理上下文,我有一个单独的基于类的组件,称为 SearchContext,它有一个状态:

state = {
  order_by: "study_id",  //default value study_id
  sort_by: "desc"        //default value of desc
}

在那个上下文组件中,我有一个名为 updateState 的方法:

updateState(key, value){
   this.setState({[key]: value});
}

我没有把组件的所有代码都放在这里,因为代码很多,但一切都很完美。当我更改组件中的下拉列表时,SearchContext 中的状态会更新,然后可用,例如,当我从该组件路由到另一个组件,然后再返回时。这就是我这样做的原因。我正在使用路由器单击列表元素之一以查看该元素,但是当我返回 ListView 组件时,我猜本地状态总是会丢失,因为我完全重新加载了?

无论如何,经过大量研究,Context API 是我找到的方法,就像我说的......它有效。当我路由回 ListView 时,下拉列表保留了它们的列表项值。

现在的问题是这样的:

在开始使用 ContextAPI 之前,我使用 ListView 组件的本地状态直接调用 setState,因此我可以传递一个回调,然后使用 Axios 从数据库中获取结果(this.updateList 是回调的名称,这里没有显示回调)。它是这样工作的:

onSelect = (e, obj) => {

    var match = obj.target.href.match(/#(.+)$/);

    if(this.state[match[1]] !== e){
        this.setState({[match[1]] :  e},  this.updateList);
    }
  }

这允许我在本地状态更新后调用 this.updateList。那工作得很好......但是现在我已经将状态移动到 Context 组件中,我不能使用这个方法,因为“this”是相对于孩子的......而不是上下文组件。所以我得到一个未定义的方法回调。

我已经进行了很多谷歌搜索,我正在缩小范围,这是一个只能通过钩子解决的问题,但我不想走这条路,因为必须有更简单的方法来获得这与我正在使用的基于类的方法一起使用。除非我的方法完全错误并且我没有以“React”方式这样做,否则我觉得应该有一种方法让我获得与 setState 相同的功能并将回调从孩子传递给父母......

如果有人有任何建议,我将不胜感激。我确信我最终将不得不学习钩子,但是 React 在钩子之前就存在的事实让我认为应该有一个基于类的方法来解决这个问题。

【问题讨论】:

    标签: reactjs react-context


    【解决方案1】:

    如果我对您的理解正确,您希望通过上下文在树中更远的组件中设置父组件的状态。这是一件完全正常的事情,而且你走在正确的轨道上。

    将您的updateState 函数放入您的上下文对象中。如果您使用contextType 通过context 属性访问上下文对象,这将是最简单的:https://reactjs.org/docs/context.html#classcontexttype

    类似这样的:

    import MyContextObject from './MyContextObject'; // defined in its own file
    
    // define your ListView component here
    
    ListView.contextType = MyContextObject
    

    然后像这样访问您的updateState 函数:

    this.context.updateState({key: value});
    

    注意:contextType 仅适用于类组件(这是您所要求的),并且您正在阅读的所有那些说“您应该使用钩子”的文章可能都是正确的,钩子很棒!一旦您对 React 更加熟悉,您可以查看 Redux 以获取应用程序状态管理:https://redux.js.org

    【讨论】:

    • 感谢您抽出宝贵时间回复我的问题。从子组件调用 updateState 没问题。问题是 setState 是异步的..但是由于某种原因,如果我尝试从 ListView 组件执行此操作: this.context.updateState({key: value}, () =>{ console.log('done!') ; }); ...回调函数(在这种情况下,console.logs“完成”的箭头函数......根本不起作用。我需要孩子在 updateState 函数更新上下文状态之后做一些事情......而且它根本行不通。
    • @übermensch 我认为我们离解决方案越来越近了;需要澄清的一些事情:React 合成事件与本机浏览器事件并不完全相同。您可能无法获得匹配的一个原因是合成事件即将到期。你必须调用event.persist() 来存储它。一般来说,用 react 来存储像这样的实际事件并不是一个好主意(一般准则:不要使用 DOM 来识别或存储事物)。你可以重构不存储事件吗?在那之后事情可能会更清楚一些。
    • 我很高兴知道我是否以错误的方式进行操作,但我发布的后续操作对我有用。当您说“没有匹配”时……您指的是代码的这一部分: this.context.state[match[1]] !== e 吗?因为如果你是,那不是问题,我总是得到匹配。问题是 setState 不返回承诺,因此一旦在父级中更新状态,我就无法从子级调用我的 Axios 方法。
    【解决方案2】:

    好的,经过一番研究,我终于让它工作了。不确定这是否会帮助任何人,但它对我有用。

    首先,在 SearchContext 文件中,我改变了 updateState 函数(它改变了上下文状态):

    updateState(key, value){
       this.setState({[key]: value});
    }
    

    对此:

      updateState(key, value){
    
        return new Promise((resolve) =>{
          this.setState({[key]: value}, ()=>{
             resolve('success!');
          });
        })
      }
    

    所以现在,updateState 函数返回了一个承诺。

    现在,回到子组件,我改变了调用 updateState 函数:

        if(this.state[match[1]] !== e){
            this.setState({[match[1]] :  e},  this.updateList);
        }
    

    对此:

        if(this.context.state[match[1]] !== e){
            //The selected state value has changed in the drop down list.
            this.context.updateState(match[1],  e).then((msg) =>{
               console.log(msg)
               this.updateList();
            });
        }
    

    ...它按预期工作。 SearchContext 状态是异步更新的,但由于我正在返回一个承诺,我现在可以等待操作完成,然后再调用 Axios 函数,该函数根据新的下拉选项刷新我的列表。

    非常酷,我很高兴我不必使用钩子重写所有内容,现在我了解了如何实现 Promise。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-30
      • 2020-08-04
      • 2021-12-12
      • 1970-01-01
      • 1970-01-01
      • 2018-06-10
      • 1970-01-01
      相关资源
      最近更新 更多