儿童使用地图和Redux时触发重新渲染

时间:2018-11-13 12:54:16

标签: javascript reactjs redux

我正在使用Redux创建一个测验应用程序,其中包括带有一些嵌套字段的表单。我刚刚意识到(我认为),如果我使用children道具,则每次按输入框的键都会触发重新渲染,即按以下方式设计应用程序:

const keys = Object.keys(state)

<QuizContainer>
  {keys.map(key => 
    <QuizForm key={key}>
        {state[key].questions.map(({ questionId }) => 
           <Question key={questionId} questionId={questionId}>
               {state[key]questions[questionId].answers.map(({ answerId })=>
                   <Answer answerId={answerId} key={answerId} />
               )}
           </Question>
        )}
    </QuizForm>
  )}
</QuizContainer>

QuizContainer使用mapStateToProps和mapDispatchToProps连接到redux,并吐出一个数组数组,这些数组中都包含对象。商店结构是根据Dan Abramov的“ Redux嵌套指南”(使用类似于关系数据库的商店类型)设计的,可以这样描述。

{
    ['quizId']: {
        questions: ['questionId1'],
        ['questionId1']:
            { question: 'some question', 
                answers: ['answerId1', 'answerId2']
            },
        ['answerId1']: { some: 'answer'},
        ['answerId2']: { some: 'other answer'}
 }

上面的代码在所有东西都被更新等方面都起作用,没有错误,但是它会触发大量的重新渲染,但是只有在我使用合成语法的情况下。如果我将每个组件放到另一个组件中(即不使用props.children),而只是发送quizId-key(和其他id-number作为props),它就会按预期工作-无需疯狂地重新渲染。为了清晰起见,当我执行以下操作时,它会起作用:

// quizId and questionId being passed from parent component's map-function (QuizForm)

const Question ({ answers }) => 
    <>
      {answers.map(({ answerId }) => 
        <Answer key={answerId} answerId={answerId} />
      )}
    </>

const mapStateToProps = (state, { questionId, quizId }) => ({
    answers: state[quizId][questionId].answers
})

export default connect(mapStateToProps)(Question)

但是为什么?两者有什么区别?我意识到其中一个是作为道具传递给父母的,而不是被渲染为那个父母的孩子,可以这么说,但是为什么这样做最终会带来不同的结果呢?他们不是应该相等但可以允许更好的语法吗?

编辑:我现在可以验证子道具是否引起了问题。设置

shouldComponentUpdate(nextProps) {
    if (nextProps.children.length === this.props.children.length) {
       return false
       } else {
       return true
    }
}

解决了问题。但是,这似乎是一个非常不错的解决方案,不太确定我现在错过了什么...

2 个答案:

答案 0 :(得分:0)

使用渲染道具图案时,每次组件渲染时都有效地声明了一个新函数。 因此,props.children之间的任何浅层比较都将失败。这是该模式的已知的缺点,而您的“黑匣子”解决方案是有效的。

答案 1 :(得分:0)

好的,所以我知道了:

我做了两件事:

  1. 我将顶级组件连接到状态,如下所示:mapStateToProps(state)=>({keys:Object.keys(state)})。我以为对象函数将返回“静态”数组并阻止我侦听整个状态,但事实证明我错了。显然(现在对我来说),每次更改状态时,我都会得到一个新的数组(但具有相同的条目)。现在,我将它们存储在一个完全独立的属性quizIds中。

  2. 我把地图功能放到了不好的地方。我现在继续像这样渲染QuizContainer:

     <QuizContainer>
       {quizIds.map(quizId => 
         <QuizForm>
          <Question>
           <Answer />
          </Question>
         </QuizForm>
       )}
     </QuizContainer>
    

然后我渲染我的孩子数组,为他们注入道具以便能够单独使用connect,如下所示:

    {questions.map((questionId, index) => (
      <React.Fragment key={questionId}>
        {React.cloneElement(this.props.children, {
          index,
          questionId,
          quizId
        })}
      </React.Fragment>
    ))}

如果您决定将多个元素作为子元素,则最后一段代码将不起作用。无论如何,看起来更干净,现在效果更好! :D