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