React-redux:渲染是否应该总是在调度动作的同时发生?

React-redux: should the render always happen in the same tick as dispatching an action?

在我的 react-redux 应用程序中,我有一个受控的文本输入。每次组件更改值时,它都会调度一个动作,最后,该值通过 redux 循环返回并被渲染。

在下面的示例中,这很有效,但实际上,我 运行 遇到了一个问题,即渲染与动作分派异步发生并且输入丢失光标位置。为了演示这个问题,我添加了另一个带有明确延迟的输入。在单词中间添加 space 会导致光标在异步输入中跳过。

对此我有两种说法,想知道哪一种是正确的:

哪一个是正确的?

工作示例:

http://jsbin.com/doponibisi/edit?html,js,output

const INITIAL_STATE = {
  value: ""
};

const reducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case 'SETVALUE':
      return Object.assign({}, state, { value: action.payload.value });
    default:
      return state;
  }
};

const View = ({
  value,
  onValueChange
}) => (
  <div>
    Sync: <input value={value} onChange={(e) => onValueChange(e.target.value)} /><br/>
    Async: <input value={value} onChange={(e) => { const v = e.target.value; setTimeout(() => onValueChange(v), 0)}} />
  </div>
);

const mapStateToProps = (state) => {
  return {
    value: state.value
  };
}

const mapDispatchToProps = (dispatch) => {
  return {
    onValueChange: (value) => {
      dispatch({
        type: 'SETVALUE',
        payload: {
          value
        } 
      })            
    }
  };
};


const { connect } = ReactRedux;
const Component = connect(
  mapStateToProps,
  mapDispatchToProps
)(View);

const { createStore } = Redux;
const store = createStore(reducer);

ReactDOM.render(
  <Component store={store} />,
  document.getElementById('root')
);

编辑:澄清问题

Marco 和 Nathan 都正确地指出这是 React 中的一个已知问题,不会被修复。如果在 onChange 和设置值之间存在 setTimeout 或其他延迟,则光标位置将丢失。

但是,setState 仅安排更新这一事实不足以导致此错误发生。在 Marco 链接的 Github issue 中,有一条评论:

Loosely speaking, setState is not deferring rendering, it's batching updates and executing them immediately when the current React job has finished, there will be no rendering frame in-between. So in a sense, the operation is synchronous with respect to the current rendering frame. setTimeout schedules it for another rendering frame.

这在JsBin例子中可以看出:"sync"版本也使用了setState,但一切正常。

悬而未决的问题仍然是:Redux 内部是否有某些东西会产生延迟,让渲染帧介于两者之间,或者 Redux 能否以避免这些延迟的方式使用?

不需要手头问题的变通办法,我找到了适合我的情况的变通办法,但我有兴趣找出更一般问题的答案。

编辑:问题已解决

我对 Clarks 的回答很满意,甚至授予了赏金,但当我通过删除所有中间件来真正测试它时,事实证明这是错误的。我还发现了与此相关的 github 问题。

https://github.com/reactjs/react-redux/issues/525

答案是:

Asynchronously updating without losing the position was never supported

--- Dan Abramov (gaearon)

解决办法是跟踪光标位置,使用componentDidUpdate()里面的ref来正确放置光标。


附加信息:

当您在 React 中设置属性时,内部会发生这种情况:

node.setAttribute(attributeName, '' + value);

当您这样设置 value 时,行为不一致:

Using setAttribute() to modify certain attributes, most notably value in XUL, works inconsistently, as the attribute specifies the default value.

--- https://developer.mozilla.org/en/docs/Web/API/Element/setAttribute


关于您关于渲染是否同步发生的问题,react 的 setState() 是异步的并且由 react-redux 内部使用:

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains

--- https://facebook.github.io/react/docs/react-component.html#setstate

There is an internal joke in the team that React should have been called "Schedule" because React does not want to be fully "reactive".

--- https://facebook.github.io/react/contributing/design-principles.html#scheduling

问题与 Redux 无关,与 React 有关。它是 known issue 并且不会在 React 核心中修复,因为它不被视为错误而是 "unsupported feature"。

完美诠释了场景。

Some attempts 已经解决了这个问题,但正如您可能看到的那样,它们都涉及输入周围的包装器组件,所以如果您问我,这是一个非常讨厌的解决方案。

我认为 react-redux 和 redux 与你的情况完全无关,这是纯粹的 React 行为。 React-redux 最终会在您的组件上调用 setState,没有魔法。

你的 async setState 在反应渲染和浏览器原生事件之间创建渲染帧的问题是因为批量更新机制只发生在反应合成事件处理程序和生命周期方法中。可以查看此 post 了解详细信息。

您在 Redux 应用程序中使用什么中间件?也许其中之一是围绕您的行动派遣做出承诺。在没有中间件的情况下使用 Redux 不会表现出这种行为,所以我认为这可能是您的设置所特有的。