React-redux:渲染是否总是与调度动作的时间相同?

时间:2017-02-24 10:18:00

标签: redux redux-saga

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

在下面的示例中,这很有效,但在实践中,我遇到了一个问题,即渲染从操作调度异步发生,输入失去光标位置。为了演示这个问题,我添加了另一个显式放入延迟的输入。在单词中间添加一个空格会导致光标在异步输入中跳过。

我有两个关于此的理论,并想知道哪一个是真的:

  • 这应该可行,但我的生产应用程序中的某个地方有一个导致延迟的错误
  • 它在简单示例中起作用的事实只是运气和反应 - redux并不能保证渲染同步发生

哪一个是对的?

工作示例:

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中一个已知问题,不会被修复。如果在setTimeout之间存在onChange或其他延迟并设置该值,则光标位置将丢失。

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

  

松散地说,setState不是推迟渲染,而是批处理   当前的React作业具有更新并立即执行它们   完成后,中间不会有渲染帧。从某种意义上说,   该操作与当前渲染同步   帧。 setTimeout将其安排为另一个渲染帧。

这可以在JsBin示例中看到:&#34; sync&#34;版本也使用setState,但一切正常。

开放性问题仍然是:Redux中是否存在一些延迟,允许渲染帧介于其间,或者是否可以以避免这些延迟的方式使用Redux?

手头的问题的解决方法不是必需的,我找到一个适用于我的案例,但我有兴趣找到更一般的问题的答案。

编辑:问题已解决

我对Clarks的回答感到高兴,甚至还给了赏金,但事实证明,当我通过删除所有中间件来测试它时,这是错误的。我还发现了与此相关的github问题。

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

答案是:

  • 这是react-redux中的一个问题,将使用react-redux 5.1修复并反应v16

4 个答案:

答案 0 :(得分:1)

您在Redux应用程序中使用了哪些中间件?也许其中一个就是围绕你的行动派遣承诺。使用没有中间件的Redux不会出现这种行为,因此我认为这可能是您的设置特有的。

答案 1 :(得分:0)

  

从不支持异步更新而不会失去位置

     

--- Dan Abramov (gaearon)

解决方案是跟踪光标位置并使用ref componentDidUpdate()来正确放置光标。

其他信息:

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

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

以这种方式设置value时,行为不一致:

  

使用setAttribute()修改某些属性,尤其是XUL中的值,由于属性指定了默认值,因此工作不一致。

     

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

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

  

无法保证对setState的调用同步操作,并且可以对调用进行批处理以获得性能增益

     

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

  

团队中有一个内部笑话,React应该被称为“Schedule”,因为React不想完全“被动”。

     

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

答案 2 :(得分:0)

问题与Redux无关,而与React无关。它是known issue,不会在React核心中修复,因为它不被视为错误,而是“不支持的功能”。

This answer完美地解释了这个场景。

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

答案 3 :(得分:0)

我认为react-redux和redux与你的情况完全无关,这是纯粹的React行为。 React-redux最终在你的组件上调用setState,没有什么神奇之处。

您的异步setState在反应呈现和浏览器本机事件之间创建呈现框架的问题是因为批处理更新机制仅在React合成事件处理程序和生命周期方法中发生。可以查看此post了解详细信息。