如何更新存储在State中的JSX元素所引用的Counter状态?
嘿,我帮你捋清楚这个问题到底出在哪,以及怎么修复它~
你现在遇到的核心问题是把已经渲染好的JSX元素直接存在Redux状态里了。简单来说,当你在MeetingItem的showModal方法里创建multiModalContent时,里面的{counter}是当时this.props.counter的即时值,然后你把这个已经生成的JSX对象存到了Redux中。之后你更新Redux里的counter时,这个旧的JSX并不会“知道”状态变了——它就像一张拍好的照片,只会显示拍照瞬间的counter值,不会自动更新成最新的。
最推荐的解决方案:存储数据而非成品JSX
Redux的本职工作是存数据,不是存UI元素。我们应该把生成Modal内容需要的元数据存在Redux里,然后在需要渲染的地方(也就是Meeting组件的Modal里)动态生成JSX,这样每次Redux状态变化时,都会重新计算JSX,自然能拿到最新的counter。
具体修改步骤:
修改Redux存储的内容
在MeetingItem的showModal方法里,不要直接生成JSX,而是把需要的数据dispatch到Redux。比如针对MultiAnswer类型:// MeetingItem.js 里的MultiAnswer分支 dispatch(setModalContent({ type: 'MultiAnswer', questionTitle: question.question.Title, answers: question.answer, // counter不用存在这里,因为它已经在Redux的state.meeting.counter里了 }));同理,
OneAnswer类型也改成存储对应的问题标题、答案列表等数据。在Meeting组件里动态渲染Modal内容
把原来直接渲染modalContent的逻辑,改成根据Redux里的元数据动态生成JSX:// Meeting.js 的render方法中 const renderModalContent = () => { const { modalContent, counter, dispatch } = this.props; if (!modalContent) return null; switch (modalContent.type) { case 'OneAnswer': const answers = modalContent.answers.map((record, i) => ( <Radio.Button style={{ display: 'block', height: '30px', lineHeight: '30px', marginTop: '6px' }} key={i} value={record.Id} > {record.Title} </Radio.Button> )); return ( <div> <p>{modalContent.questionTitle}</p> <Radio.Group buttonStyle="solid">{answers}</Radio.Group> </div> ); case 'MultiAnswer': // 把handleChange逻辑移到这里,或者通过dispatch触发统一的action const handleChange = (event) => { if (event.target.checked) { dispatch(setCounter(counter - 1)); } else { dispatch(setCounter(counter + 1)); } }; const multiAnswers = modalContent.answers.map((record, i) => ( <Checkbox key={i} value={record.Id} onChange={handleChange}> {record.Title} </Checkbox> )); return ( <div> <p>{modalContent.questionTitle}</p> <p>counter: {counter}</p> {/* 这里每次都会读取最新的counter值 */} <Checkbox.Group>{multiAnswers}</Checkbox.Group> </div> ); default: return <div></div>; } }; // 然后在Modal里调用这个渲染方法 <Modal title="Title" visible={modalVisible} onOk={() => dispatch(showModal(false))} onCancel={() => dispatch(showModal(false))} okText="Ok" cancelText="Cancel" > <div>{renderModalContent()}</div> </Modal>调整选中状态的存储(可选但推荐)
原来MeetingItem里的checked本地state还有个问题:当Modal关闭再打开时,选中状态会丢失。建议把选中状态也存在Redux里,这样在renderModalContent里可以读取Redux的选中状态,给Checkbox设置checked属性,保持状态的一致性。
备选方案:存储组件而非JSX元素(不推荐)
如果你实在想在Redux里存可渲染的内容,可以存一个React组件(函数组件),而不是渲染好的JSX元素。组件会在每次渲染时重新执行,从而获取最新的Redux状态:
在
MeetingItem里创建组件:// MeetingItem.js 的MultiAnswer分支 const MultiModalContent = ({ counter, dispatch, question }) => { const handleChange = (event) => { if (event.target.checked) { dispatch(setCounter(counter - 1)); } else { dispatch(setCounter(counter + 1)); } }; return ( <div> <p>{question.question.Title}</p> <p>counter: {counter}</p> <Checkbox.Group> {question.answer.map((record, i) => ( <Checkbox key={i} value={record.Id} onChange={handleChange}>{record.Title}</Checkbox> ))} </Checkbox.Group> </div> ); }; dispatch(setModalContent(MultiModalContent));在
Meeting组件里渲染这个组件并传props:// Meeting.js 的Modal里 <div> {modalContent && React.createElement(modalContent, { counter: this.props.counter, dispatch: this.props.dispatch, question: // 这里需要把question数据也传到Redux里 })} </div>
不过这种方式会把UI逻辑分散到不同组件里,维护起来不如第一种方案清晰,所以更推荐第一种存储数据的做法。
小提醒
Redux的核心是管理应用状态数据,不是用来存UI元素或组件的。把UI存在Redux里会让状态和UI过度耦合,不仅容易出现这种状态不同步的问题,后续维护也会很麻烦。所有需要响应状态变化的UI,都应该通过读取最新状态动态渲染来实现~
内容的提问来源于stack exchange,提问作者Amirhosein Rajabi

