【问题标题】:React assign key to already rendered componentReact 将键分配给已渲染的组件
【发布时间】:2018-02-23 12:22:45
【问题描述】:

有可能吗?

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

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

我显然收到一条警告,说子级渲染时没有 key 属性。

我尝试在渲染 vdom 元素后分配键:

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

但是从 JSX 转换返回的对象被冻结了,所以我不能这样做。也没有 API 可以暂时解冻然后重新冻结 js 中的对象。出于性能原因,克隆是不可能的(成千上万的组件都是这样渲染的)

我能做什么?

同样,出于性能原因,我无法将渲染的子组件包装到另一个组件中,因此这样的解决方案不起作用:

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

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

更新 这种结构的原因是SomeComponent 是一个哑组件,无法访问应用程序状态(redux)。但是渲染的孩子确实需要访问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 函数在哪里?
  • @VamshiGudipati 不相关,这是一个任意的Object =&gt; VDom 函数。
  • 确实如此。您正在将键分配给 vdom 对象,而不是 HTML 或包装 HTML 的 div
  • @BalázsÉdes,它实际上非常相关。至少让我们看看。这将使这更容易理解。
  • 我相信你需要的是React.createElement()(或者React.cloneElement(),如果你自己创建元素)

标签: reactjs key render


【解决方案1】:

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

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

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}>
  }
}

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 复制原始道具后,无需将child.props 传递给cloneElement()
    • 您可能想为此添加一些上下文,例如解释cloneElement 的作用、举例等。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 1970-01-01
    • 2018-12-08
    • 2020-12-12
    • 2016-03-19
    • 1970-01-01
    • 2018-06-16
    相关资源
    最近更新 更多