You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 17:27:01