我正在使用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
}
}
解决了问题。但是,这似乎是一个非常不错的解决方案,不太确定我现在错过了什么...
答案 0 :(得分:0)
使用渲染道具图案时,每次组件渲染时都有效地声明了一个新函数。
因此,props.children
之间的任何浅层比较都将失败。这是该模式的已知的缺点,而您的“黑匣子”解决方案是有效的。
答案 1 :(得分:0)
好的,所以我知道了:
我做了两件事:
我将顶级组件连接到状态,如下所示:mapStateToProps(state)=>({keys:Object.keys(state)})。我以为对象函数将返回“静态”数组并阻止我侦听整个状态,但事实证明我错了。显然(现在对我来说),每次更改状态时,我都会得到一个新的数组(但具有相同的条目)。现在,我将它们存储在一个完全独立的属性quizIds中。
我把地图功能放到了不好的地方。我现在继续像这样渲染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