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
中更新是正确的方式,因为重新渲染和焦点都是组件的状态和行为,您无法将它们完全解耦。我什至会说您应该将切换状态移动到组件中,并为onToggle
和onClick
提供一些回调道具。
@Avery235 是什么让你说“这不是一个干净的解决方案,因为逻辑特定于 onClick 处理程序而不是生命周期事件”?
【参考方案1】:
我认为,由于数据流,您可以确信在执行 focus()
调用之前更新的 Redux state
数据就在那里:
-
调度异步操作
toggleThunk
,并等待其解决
then
调度同步操作以更新state
(新的state
数据),并等待其解决(?)
then
focus()
你的推荐人
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 then
focus() 您的参考。我忽略了什么?【参考方案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 - 异步动作创建者承诺和链接不起作用