React JSX重构:如何将RepeatableAttributeSetContextProvider移至StyledHorizontalAttributesTable外层无报错
报错原因说明
你调整后触发变量未定义报错的核心原因是enteredObject是objects.map循环的局部迭代变量,如果你直接把RepeatableAttributeSetContextProvider移出当前的循环作用域,自然无法访问该变量;同时原本每个Provider对应单个行的独立上下文,直接整体上移会丢失每行的独立状态。
可行实现方案
方案1:抽离独立行组件实现解耦(推荐)
把单个可重复属性集行的逻辑封装为独立组件,父组件只负责循环渲染,完全隔离Provider和外层逻辑,满足你把Provider关联内容和其余JSX解耦的需求:
// 抽离的独立行组件,Provider逻辑封装在内层,外层无需感知 const RepeatableAttributeSetRow = ({ enteredObject, index, form, filterRepeatAttributes, renderAttribute, formLayout, onRemove }) => { return ( <RepeatableAttributeSetContextProvider form={form} object={enteredObject} > <StyledHorizontalAttributesTable className="attributeset-row"> {enteredObject.attributeCollection.questions .filter(filterRepeatAttributes) .map((attribute) => ( <Fragment key={attribute.key}> {renderAttribute(enteredObject, attribute, formLayout)} </Fragment> ))} <StyledButton className="remove-btn" type="link" buttonStyle="LINK" name="delete" dataId={`delete-${enteredObject.key}-${index}`} icon="bin" onClick={() => onRemove(enteredObject)} > <Message id="Form.Button.Remove" defaultMessage="Remove" /> </StyledButton> </StyledHorizontalAttributesTable> </RepeatableAttributeSetContextProvider> ) } // 改造后的父组件代码,不存在Provider包裹JSX的耦合逻辑 return ( <div className={className}> {objects.map((enteredObject, index) => ( <RepeatableAttributeSetRow key={`${enteredObject.key}-${enteredObject.repeatIndex}`} enteredObject={enteredObject} index={index} form={form} filterRepeatAttributes={filterRepeatAttributes} renderAttribute={renderAttribute} formLayout={formLayout} onRemove={onRemove} /> ))} </div> )
方案2:取消Context使用,改为Props透传
如果不需要Context做深层传值,直接把原来消费Context的组件改为接收props参数,就可以完全去掉RepeatableAttributeSetContextProvider的包裹:
return ( <div className={className}> {objects.map((enteredObject, index) => ( <StyledHorizontalAttributesTable className="attributeset-row"> {enteredObject.attributeCollection.questions .filter(filterRepeatAttributes) .map((attribute) => ( <Fragment key={attribute.key}> {/* 把原本从Context取的参数直接传给renderAttribute生成的组件 */} {renderAttribute(enteredObject, attribute, formLayout, form)} </Fragment> ))} <StyledButton className="remove-btn" type="link" buttonStyle="LINK" name="delete" dataId={`delete-${enteredObject.key}-${index}`} icon="bin" onClick={() => onRemove(enteredObject)} > <Message id="Form.Button.Remove" defaultMessage="Remove" /> </StyledButton> </StyledHorizontalAttributesTable> ))} </div> );
该方案适合嵌套层级浅、消费Context的组件不多的场景,代码更简洁。
方案3:上层统一Context存储所有实例数据
如果确实需要把Provider提到map之外的上层位置,可以改造Context的存储结构,把所有enteredObject的状态按key存储,消费组件按当前行的key取对应数据:
// 上层Provider改造,仅在最外层包裹一次 return ( <RepeatableAttributeSetContextProvider form={form} allEnteredObjects={objects} // 传入所有实例的数组/对象映射 > <div className={className}> {objects.map((enteredObject, index) => ( <StyledHorizontalAttributesTable className="attributeset-row" data-row-key={enteredObject.key}> {/* 内部消费组件按当前行的key取对应数据即可 */} ... </StyledHorizontalAttributesTable> ))} </div> </RepeatableAttributeSetContextProvider> )
该方案改造成本较高,需要同时修改Provider和所有消费组件的取值逻辑,适合需要全局管控所有可重复行状态的场景。
内容的提问来源于stack exchange,提问作者cts
相关产品推荐
相关产品推荐

