如何重用react回调来提升状态?

时间:2019-11-18 12:40:15

标签: reactjs

我不明白如何抽象传递给子组件实例的回调函数,以将状态提升到父组件:

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

4 个答案:

答案 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');
   }}
/>

这将创建带有两个参数的公用回调-stateToLifttype

答案 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 ZaveriHMR的解决方案!

/* 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);
}