将键分配给已渲染的组件

React assign key to already rendered component

可能吗?

我有一个组件,其中 children 由作为 props 传入的任意映射函数呈现。一个简化的例子:

class SomeComponent extends Component {
  render() {
    const { renderChild, businessObjects } = this.props
    return <div>
       {businessObjects.map(renderChild)}
    </div>
  }
}

我显然收到一条警告,说 children 在没有 key 属性的情况下呈现。

我尝试在呈现 vdom 元素后分配密钥:

...
{
  businessObjects.map(e => {
    const vdom = renderChild(e)
    vdom.key = e.id
    return vdom
  })
}
...

但是从 JSX 转换返回的 object 被冻结了,所以我不能这样做。也没有API临时解冻然后re-freezeobjects在js中。出于性能原因,克隆是不可能的(成千上万的组件是这样呈现的)

我能做什么?

同样,出于性能原因,我不能将渲染的 children 包装到另一个组件中,因此这样的解决方案行不通:

const Child = ({renderChild, bo}) => (<div>{renderChild(bo)}</div>)

// in SomeComponent
...
{
  businessObjects.map(e => (<Child 
    key={e.id}
    bo={e} 
    renderChild={renderChild} 
  />)
  )
}
...

更新 这种结构的原因是 SomeComponent 是一个哑组件,无法访问应用程序状态 (redux)。但是呈现的 children 确实需要访问 dispatch(我以连接动作创建者的形式进行)。

所以你可以想象整个事情是这样的:

const createChildRenderer = ({actionFoo, actionBar}) => (obj) => {
  switch(obj.type) {
    case FOO:
      return <div onClick={() => actionFoo()}>{obj.text}</div>
    case BAR:
      return <div onClick={() => actionBar()}>{obj.text}</div>
    default:
      return null
  }
}

并且在连通分量中

@connect(
  ({ businessObjects }) => { businessObjects },
  { actionFoo, actionBar}
)
class SmartComponent extends Component {
  render() {
    const renderChild = createChildRenderer({
      actionFoo: this.props.actionFoo, // action creators
      actionBar: this.props.actionBar
    })
    return (<SomeComponent 
       renderChild={renderChild} 
       businessObjects={this.props.businessObjects}>
  }
}

您可以在从 renderChild 收到的 child 上使用 cloneElement

React.cloneElement(
  child,
  {...child.props, key: yourKeyValue}
)

我最终通过将实际的反应组件作为参数来解决这个问题的方式:

所以在以前带渲染器功能的哑组件中,现在我带一个组件:

class SomeComponent extends Component {
  render() {
    const { ChildComponent, businessObjects } = this.props
    return <div>
       {businessObjects.map((o) => (<ChildComponent 
           businessObject={o}
           key={o.id}
       />)}
    </div>
  }
}

在我之前创建渲染器函数的地方,现在我创建组件:

const createChildComponent = ({actionFoo, actionBar}) => 
  ({ businessObject: obj }) => { // this is now a component created dynamically
    switch(obj.type) {
      case FOO:
        return <div onClick={() => actionFoo()}>{obj.text}</div>
      case BAR:
        return <div onClick={() => actionBar()}>{obj.text}</div>
      default:
        return null
    }
}

并且在连接的组件中:

@connect(
  ({ businessObjects }) => { businessObjects },
  { actionFoo, actionBar}
)
class SmartComponent extends Component {
  render() {
    const ChildComponent = createChildComponent({
      actionFoo: this.props.actionFoo, // action creators
      actionBar: this.props.actionBar
    })
    return (<SomeComponent 
       ChildComponent={ChildComponent} 
       businessObjects={this.props.businessObjects}>
  }
}