Redux-thunk 调度一个动作并等待重新渲染

Posted

技术标签:

【中文标题】Redux-thunk 调度一个动作并等待重新渲染【英文标题】:Redux-thunk dispatch an action and wait for re-render 【发布时间】:2018-09-28 05:47:39 【问题描述】:
import React from "react";
import  render  from "react-dom";
import  createStore, applyMiddleware  from "redux";
import  Provider, connect  from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => 
  return action.type === "TOGGLE" ? !state : state;
;

class Button extends React.Component 
  componentDidUpdate(prevProps) 
    if (prevProps.disabled !== this.props.disabled && !this.props.disabled) 
      //  this.ref.focus();  // uncomment this to see the desired effect
    
  
  render() 
    const  props  = this;
    console.log("rendering", props.value);
    return (
      <div>
        <input
          type="checkbox"
          onClick=() => 
            props.toggle();
            this.ref.focus(); // doesn't work
          
        />
        <input
          disabled=props.disabled
          ref=ref => 
            this.ref = ref;
          
        />
      </div>
    );
  


const toggle = () => (
  type: "TOGGLE"
);

const A = connect(state => ( disabled: state ),  toggle )(Button);

const App = () => (
  <Provider store=createStore(disabled, applyMiddleware(thunk))>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

当复选框被选中时,我想关注input。 但是,this.ref.focus() 必须在组件使用 props.disabled === false 重新渲染后才能调用,因为带有 disabled 属性的 input 无法聚焦。

如果我执行componentDidUpdate 中的逻辑,我就能实现我想要的。但这不是一个干净的解决方案,因为逻辑特定于 onClick 处理程序而不是生命周期事件。

还有其他方法可以做到这一点吗? (最好有一个有效的代码框示例)

【问题讨论】:

IMO,在componentDidUpdate 中更新是正确的方式,因为重新渲染和焦点都是组件的状态和行为,您无法将它们完全解耦。我什至会说您应该将切换状态移动到组件中,并为onToggleonClick 提供一些回调道具。 @Avery235 是什么让你说“这不是一个干净的解决方案,因为逻辑特定于 onClick 处理程序而不是生命周期事件”? 【参考方案1】:

我认为,由于数据流,您可以确信在执行 focus() 调用之前更新的 Redux state 数据就在那里:

    调度异步操作toggleThunk,并等待其解决 then 调度同步操作以更新state(新的state 数据),并等待其解决(?) thenfocus()你的推荐人

https://codesandbox.io/s/r57v8r39om

请注意,在您的 OP 中,您的 toggle() 动作创建者不是 thunk。此外,强制您的 thunk 返回 Promise 是一个很好的规则,这样您就可以按照您描述的方式控制数据流。

import React from "react";
import  render  from "react-dom";
import  createStore, applyMiddleware  from "redux";
import  Provider, connect  from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => 
  return action.type === "TOGGLE" ? !state : state;
;

class Button extends React.Component 
  textInput = React.createRef();

  handleClick = () => 
    const  toggleThunk  = this.props;
    toggleThunk().then(() => 
      this.textInput.current.focus();
    );
  ;

  render() 
    const  disabled, value  = this.props;
    return (
      <div>
        <input type="checkbox" onClick=this.handleClick />
        <input disabled=disabled ref=this.textInput />
      </div>
    );
  


// Action
const toggle = () => (
  type: "TOGGLE"
);

// Thunk
const toggleThunk = () => dispatch => 
  // Do your async call().then...
  return Promise.resolve().then(() => dispatch(toggle()));
;

const A = connect(state => ( disabled: state ),  toggleThunk )(Button);

const App = () => (
  <Provider store=createStore(disabled, applyMiddleware(thunk))>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

【讨论】:

这是我原来的方法(见编辑),我删除了它,因为它假定 this.props.toggleThunk() 只会在组件重新渲染后解决。为什么您认为这是一种有保证的行为,并且该行为是否在任何地方都有记录/引用? 好问题,我看看能找到什么。 @Avery235 关于我的更新有什么消息吗?我相信我原来的解决方案毕竟确实保证了这种行为。但我编辑中的其他解决方案也可以完成这项工作。 您的所有解决方案似乎都围绕着我在上述评论中提到的假设展开,您没有解决。 我想我确实解决了这个问题:1 调度异步动作并等待其解决,2 then调度同步动作以更新状态(您需要检查的新状态数据)并等待其解决,3 thenfocus() 您的参考。我忽略了什么?【参考方案2】:

我认为最好的办法是不要依赖 refs 使用状态来管理焦点。

此解决方案改为在输入上使用 autoFocus 属性,并在复选框状态更改时对其进行修改。

import React from "react";
import  render  from "react-dom";
import  createStore, applyMiddleware  from "redux";
import  Provider, connect  from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => 
  return action.type === "TOGGLE" ? !state : state;
;

class Button extends React.Component 
  state = 
    checked: false,
    focus: false
  ;
  componentDidUpdate(prevProps, prevState) 
    if (prevState.checked !== this.state.checked) 
      this.props.toggle();
      this.setState(
        focus: this.state.checked
      );
    
  
  render() 
    const  props  = this;
    const  checked, focus  = this.state;
    console.log("rendering", props.value, checked);
    return (
      <div>
        <input
          type="checkbox"
          checked=checked
          onClick=( target ) => 
            this.setState( checked: target.checked );
          
        />
        <input key=`input_$checked` autoFocus=focus />
      </div>
    );
  


const toggle = () => (
  type: "TOGGLE"
);

const A = connect(state => ( disabled: state ),  toggle )(Button);

const App = () => (
  <Provider store=createStore(disabled, applyMiddleware(thunk))>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));


我不知道为什么,但是当组件先前被禁用时更改 autoFocus 属性不会触发重新渲染输入。所以我还在输入中添加了一个键来强制它。

【讨论】:

这会导致额外的重新渲染(与使用 ref.focus() 相比)。【参考方案3】:

这是一种假设情况,并且是 REACT 中的一个未解决问题(同时不是),因为它符合自动对焦的 html 规范 (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes#autofocus)。焦点是在装饰上非常棘手的事情之一,因为它是共享全局状态的一部分。如果 2 个不相关的组件声明它们应该集中在单个渲染通道中,那么谁是对的?因此,REACT 为您提供了自己管理该状态的钩子,但它不会为您做这件事(因此,像您正在使用的那样的工作就来了)。

但是,如果 REACT 添加了专注于渲染的选项(可能只是 autoFocusOnRender),并且如果同时需要关注多个事物,只需让文档警告人们该行为即可。理想情况下,这不会发生,因为具有良好 UX 的应用程序具有在不同输入上调用 autoFocusOnRender 的特定条件。

我会建议您所做的是最好的方法:)。希望我们在 REACT 中对此有所增强。

【讨论】:

怎么不符合HTML规范? HTML 规范说什么与 React 不同? “如果 REACT 添加了专注于渲染的选项”是不是已经可以使用 autoFocus 道具? 为您的第一点。我没有说明它不一致,它与 HTML 规范一致,你可能读错了,因为第二个我提供了 html 规范(PFA 链接),在属性部分将有关于自动对焦的文档。 autoFocus 是如何起作用的却又不起作用?描述这种情况的最佳用例是当页面具有多个字段时,其中 autoFocus 属性仅适用于初始渲染,因为预期的行为只是将焦点移到第一次渲染上,这与自动对焦的 HTML 规范一致。 【参考方案4】:

您可以使用 prop 和 ref 来管理它。 ref 将避免重新渲染输入的需要(即autoFocus 工作):

import React,  Component  from "react";
import  render  from "react-dom";
import  createStore, applyMiddleware  from "redux";
import  Provider, connect  from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => 
  return action.type === "TOGGLE" ? !state : state;
;

class Button extends Component 

  componentDidUpdate(prevProps) 
    if (!this.props.disabled && prevProps.disabled) 
      this.ref.focus();
    
  

  render() 
    const  disabled  = this.props;
    return (
      <div>
        <input
          type="checkbox"
          checked=!disabled
          onClick=() => 
            this.props.toggle();
          
        />
        <input
          disabled=disabled
          ref=ref => 
            this.ref = ref;
          
        />
      </div>
    );
  


const toggle = () => (
  type: "TOGGLE"
);

const A = connect(state => ( disabled: state ),  toggle )(Button);

const App = () => (
  <Provider store=createStore(disabled, applyMiddleware(thunk))>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

【讨论】:

我需要将状态存储在 redux 中(以保持它)。可以在组件中复制它,但这违反了单一事实来源。 不是和我在 OP 中的解决方案一样吗? 是的,非常相似 - 也许我不明白你的问题 - 这是处理行为的正确模式 - 在启用输入之前,你不能将焦点设置为输入,这不会直到动作传播后的渲染周期才会发生 - 有意义吗? 我希望有一个更好的解决方案,您可以通过某种方式设计动作调度程序和onClick 处理程序,以便您可以在onClick 处理程序中调用.focus()。我想没有这样的解决方案...... onClick 事件是派发动作的正确位置,componentDidUpdate 函数是检测状态(即存储)更改并将焦点设置到输入的正确生命周期时刻。跨度>

以上是关于Redux-thunk 调度一个动作并等待重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

Redux-Thunk - 异步动作创建者承诺和链接不起作用

Redux - 如何调用一个动作并等待它解决

jest redux-thunk 测试是不是调度了相同模块的操作

Redux thunk:等待异步函数调度

在 redux 中在哪里调度多个操作?

Redux thunk - 嵌套调度的函数/动作