【问题标题】:Redux-thunk dispatch an action and wait for re-renderRedux-thunk 调度一个动作并等待重新渲染
【发布时间】: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 处理程序而不是生命周期事件”?

标签: javascript reactjs redux redux-thunk


【解决方案1】:

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

  1. 调度异步操作toggleThunk,并等待其解决
  2. then 调度同步操作以更新state(新的state 数据),并等待其解决(?)
  3. 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 函数是检测状态(即存储)更改并将焦点设置到输入的正确生命周期时刻。跨度>
猜你喜欢
  • 2017-04-18
  • 2023-03-23
  • 1970-01-01
  • 2017-06-15
  • 2019-06-23
  • 2019-11-12
  • 1970-01-01
  • 2020-04-23
  • 2021-06-26
相关资源
最近更新 更多