Реагировать на присвоение ключа уже отображенному компоненту

Является ли это возможным?

У меня есть компонент, где дети отображаются с помощью произвольной функции отображения, поступающей как 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 является тупым компонентом и не имеет доступа к состоянию приложения (приставка). Но дети должны иметь доступ к 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}>
  }
}

2 ответа

Решение

То, как я решил эту проблему, приняв фактический компонент реакции в качестве аргумента:

Так что в немом компоненте, который ранее принимал функцию рендерера, теперь я беру компонент:

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

Вы можете использовать cloneElement для ребенка, полученного от renderChild:

React.cloneElement(
  child,
  {...child.props, key: yourKeyValue}
)
Другие вопросы по тегам