我不明白如何抽象传递给子组件实例的回调函数,以将状态提升到父组件:
/* Parent */
typeCallback = (dataFromChild) => {
var filter = {...this.state.filter}
filter.type = dataFromChild;
this.setState({filter}, () => console.log(this.state));
}
makeCallback = (dataFromChild) => {
var filter = {...this.state.filter}
filter.make = dataFromChild;
this.setState({filter}, () => console.log(this.state));
}
...
/* Parent render() */
<Child url='http://localhost:5000/device_type' placeholder='Type' parentCallback={this.typeCallback}/>
<Child url='http://localhost:5000/device_make' placeholder='Make' parentCallback={this.makeCallback}/>
我想抽象我的回调函数,以获取应该更新的父级状态变量的名称。目前,我有6个Child组件实例和6个相应的回调函数副本,这些副本专门用于更新目标状态变量(即this.state.filter.type,this.state.filter.make)
/* Child */
handleSelectorValueCreate = () => {
fetch(this.props.url, {
method: 'POST',
headers: {"Content-Type": "application/json"},
body: val,
})
.then(res => res.json())
.then(response => { this.setState({value: response}, () => this.sendData() ); })
sendData = () => {
this.props.parentCallback(this.state.value);
}
/* Child render() */
<Child onChange={this.handleSelectorValueChange} />
答案 0 :(得分:1)
是的,您可以扩展父级的函数参数的支持来做到这一点
/* Parent */
commonCallback = (dataFromChild, type) => {
var filter = { ...this.state.filter };
filter[type] = dataFromChild;
this.setState({ filter }, () => console.log(this.state));
};
....
<Child
url="http://localhost:5000/device_type"
placeholder="Type"
parentCallback={liftState => {
/* Use type as second parameter for differentiating*/
this.commonCallback(liftState, 'type');
}}
/>
这将创建带有两个参数的公用回调-stateToLift
和type
。
答案 1 :(得分:1)
将回调传递给孩子的最好方法是传递不会更改每个渲染的内容,因为该回调很可能用作处理程序,并且会导致React不必要地进行DOM重新渲染。
所以最好只传递一个函数,让该函数同时接收键和值:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { items: [1, 2, 3, 4, 5] };
}
parentCallback = (data, key) => {
const items = [...this.state.items];
items[key] = data + 1;
this.setState({ items });
};
render() {
return (
<React.Fragment>
{this.state.items.map((val, index) => (
<Child
key={index} //never use index in key where you can re sort or re arrange items
parentCallback={this.parentCallback}
callbackKey={index}
val={val}
/>
))}
</React.Fragment>
);
}
}
const Child = React.memo(function Child({
parentCallback,
callbackKey,
val,
}) {
const rendered = React.useRef(0);
rendered.current++;
return (
<div>
<button
onClick={() => parentCallback(val, callbackKey)}
>
{val}
</button>
<div>Rendered: {rendered.current} times.</div>
</div>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
如果您无法更改Child组件,则可以为其创建一个容器并使用useCallback:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { items: [1, 2, 3, 4, 5] };
}
parentCallback = (data, key) => {
const items = [...this.state.items];
items[key] = data + 1;
this.setState({ items });
};
render() {
return (
<React.Fragment>
{this.state.items.map((val, index) => (
<ChildContainer
key={index} //never use index in key where you can re sort or re arrange items
parentCallback={this.parentCallback}
callbackKey={index}
val={val}
/>
))}
</React.Fragment>
);
}
}
const ChildContainer = ({
parentCallback,
callbackKey,
val,
}) => {
const newCallback = React.useCallback(
() => parentCallback(val, callbackKey),
[parentCallback, val, callbackKey]
);
return <Child parentCallback={newCallback} val={val} />;
};
const Child = React.memo(function Child({
parentCallback,
val,
}) {
const rendered = React.useRef(0);
rendered.current++;
return (
<div>
<button onClick={parentCallback}>{val}</button>
<div>Rendered: {rendered.current} times.</div>
</div>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
答案 2 :(得分:0)
您可以这样做:
父组件
#!/usr/bin/env python
"""
Annotate a group of y-tick labels as such.
"""
import matplotlib.pyplot as plt
from matplotlib.transforms import TransformedBbox
def annotate_yranges(groups, ax=None):
"""
Annotate a group of consecutive yticklabels with a group name.
Arguments:
----------
groups : dict
Mapping from group label to an ordered list of group members.
ax : matplotlib.axes object (default None)
The axis instance to annotate.
"""
if ax is None:
ax = plt.gca()
label2obj = {ticklabel.get_text() : ticklabel for ticklabel in ax.get_yticklabels()}
for ii, (group, members) in enumerate(groups.items()):
first = members[0]
last = members[-1]
bbox0 = _get_text_object_bbox(label2obj[first], ax)
bbox1 = _get_text_object_bbox(label2obj[last], ax)
set_yrange_label(group, bbox0.y0 + bbox0.height/2,
bbox1.y0 + bbox1.height/2,
min(bbox0.x0, bbox1.x0),
-2,
ax=ax)
def set_yrange_label(label, ymin, ymax, x, dx=-0.5, ax=None, *args, **kwargs):
"""
Annotate a y-range.
Arguments:
----------
label : string
The label.
ymin, ymax : float, float
The y-range in data coordinates.
x : float
The x position of the annotation arrow endpoints in data coordinates.
dx : float (default -0.5)
The offset from x at which the label is placed.
ax : matplotlib.axes object (default None)
The axis instance to annotate.
"""
if not ax:
ax = plt.gca()
dy = ymax - ymin
props = dict(connectionstyle='angle, angleA=90, angleB=180, rad=0',
arrowstyle='-',
shrinkA=10,
shrinkB=10,
lw=1)
ax.annotate(label,
xy=(x, ymin),
xytext=(x + dx, ymin + dy/2),
annotation_clip=False,
arrowprops=props,
*args, **kwargs,
)
ax.annotate(label,
xy=(x, ymax),
xytext=(x + dx, ymin + dy/2),
annotation_clip=False,
arrowprops=props,
*args, **kwargs,
)
def _get_text_object_bbox(text_obj, ax):
# https://stackoverflow.com/a/35419796/2912349
transform = ax.transData.inverted()
# the figure needs to have been drawn once, otherwise there is no renderer?
plt.ion(); plt.show(); plt.pause(0.001)
bb = text_obj.get_window_extent(renderer = ax.get_figure().canvas.renderer)
# handle canvas resizing
return TransformedBbox(bb, transform)
if __name__ == '__main__':
import numpy as np
fig, ax = plt.subplots(1,1)
# so we have some extra space for the annotations
fig.subplots_adjust(left=0.3)
data = np.random.rand(10,10)
ax.imshow(data)
ticklabels = 'abcdefghij'
ax.set_yticks(np.arange(len(ticklabels)))
ax.set_yticklabels(ticklabels)
groups = {
'abc' : ('a', 'b', 'c'),
'def' : ('d', 'e', 'f'),
'ghij' : ('g', 'h', 'i', 'j')
}
annotate_yranges(groups)
plt.show()
说明: 通过这种方式,我使用currying传递了一个字段,该字段将向您指示要更新哪个字段,而我直接将setState方法与prevState一起使用,这将防止您不关注inmutability rule in react class components。
PD:我想您将能够使用map函数渲染孩子并为他们提供所需的属性。
答案 3 :(得分:0)
感谢Meet Zaveri和HMR的解决方案!
/* Parent */
commonCallback = (dataFromChild, key) => {
var filter = { ...this.state.filter };
filter[key] = dataFromChild;
this.setState({ filter }, () => console.log(this.state.filter));
};
/* Parent render() */
<Child url='http://localhost:5000/device_type' placeholder='Type'
parentCallback={this.commonCallback} callbackKey="type"/>
<Child url='http://localhost:5000/device_make' placeholder='Make'
parentCallback={this.commonCallback} callbackKey="make"/>
/* Child */
sendData = () => {
this.props.parentCallback(this.state.value, this.props.callbackKey);
}