javascript - Hur skickar man argument till en callback i React + Typescript utan att skapa en ny funktion?


Säg att du har följande TSX-kod:

render() {
    const someParameter = 5;

    return (
        <div onClick={() => this.doSomething(someParameter)}></div>
    );
}

Nu vet vi att vi inte borde skicka vidare fetpilsfunktioner som denna till onClick-hanteraren eftersom det kommer att skapa en ny funktion varje gång, vilket tvingar fram en omrendering (detta är särskilt dåligt när vi skickar denna callback till en djup komponent, inte bara en div).

Så då skulle lösningen, om det inte fanns några parametrar, vara att binda doSomething till this i konstruktorn för vår klass, som this.doSomething=this.doSomething.bind(this) och sedan skicka den bundna funktionen som callback. Men detta räcker inte i vårt fall, eftersom vi vill skicka en parameter till funktionen - someParameter. Föreställ dig att someParameter inte bara var en dum konstant som det är ovan, utan istället ett element i en array som vi får i en map() över en array i vår render-metod. Hur hanterar vi denna situation? Det vill säga, hur skickar vi en funktion som inte skapas från början varje gång, vilket skulle bryta vår förmåga att smart återrendera endast när det är nödvändigt?

Tack!

Svar #1

bind tillåter att binda inte bara kontext utan också parametrar. Så du kan göra något sådant här i konstruktorn:

this.doSomething = this.doSomething.bind(this, someArgument)

Ovanstående lösning fungerar endast om someArgument inte ändras under komponentens livscykel.
Om detta argument ska vara dynamiskt är den enda lösningen att använda pilfunktion som i ditt exempel - men observera att det kan orsaka ytterligare omladdning endast om denna funktion skickas som en prop till barnkomponenter (inte HTML-element). Enligt React-dokumentationen:

I de flesta fall är detta okej. Men om denna callback skickas som en prop till lägre komponenter, kan dessa komponenter göra en extra omrendering.

Om du inte skickar det till en React-barnkomponent borde det inte vara ett problem och kommer inte att orsaka någon ytterligare rendering. Observera att det finns en skillnad mellan React-komponenter och HTML-taggar. Om du använder en pilfunktion som händelsecallback på ett HTML-element (t.ex. på en div genererad i en map) kommer det inte att trigga ytterligare rendering av denna div eftersom det inte är en komponent och inte har någon render-metod. Reacts Virtual DOM-algoritm kommer att avgöra om det faktiska DOM-elementet som motsvarar denna div bör uppdateras och i vårt fall kommer det befintliga DOM-elementet inte att uppdateras (du kan kontrollera det med utvecklingsverktygen).

class Hello extends React.Component{
  constructor() {
    super();
    this.state = {test: 0};
  }
  componentDidMount() {
    var i =0;
    this.timer = setInterval(() => {
      i++;
    	this.setState({test: i})
    }, 1000)
  }
  componentWillUnmount() {
    clearInterval(this.timer)
  }
  render() {
    return (<div>{this.state.test}
      {
        [1, 4, 6].map((v) => {
          return (<div key={v} onClick={() => { console.log(this.state.test)}}>test</div>)
        })
      }
    </div>);
  }
};

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

Du kan undersöka utdata som genereras av kodsnutten ovan för att se att div-element inte uppdateras i DOM trots att de har en pilfunktion definierad som händelseåteruppringning och föräldrakomponenten återrenderas.

Om du behöver skicka denna funktion till ett barnkomponent kan du separat skicka prop med funktionen och prop med argumentvärdet för att undvika extra omrendering (självklart måste du också implementera shouldComponentUpdate):

<SomeComponent myClick={this.doSomething} myArgument={someArgument} />

och någonstans i SomeComponent:

//JSX in SomeComponent render method
<div onClick={() => this.props.myClick(this.props.myArgument)} >...</div>

Du kan också skapa en ytterligare metod i SomeComponent istället för att använda en pilfunktion:

handleMyClick() {
   this.props.myClick(this.props.myArgument);
}

och använd det sedan i JSX:

<div onClick={this.handleMyClick} >...</div>

Så för att sammanfatta: om du använder en arrow-funktion som händelsecallback på HTML-taggar bör det inte orsaka prestandaproblem och det kommer inte att utlösa någon ytterligare omrendering - det kommer inte att bryta möjligheten att smart återrendera endast när det är nödvändigt. Det kan orsaka ytterligare omrendering endast om arrow-funktionen skickas vidare som en prop till barnkomponenter, och i ett sådant fall kan du använda lösningen jag föreslog.

Svar #2

Kan du vänligen kolla lösningen som föreslås här i avsnittet "Protips"? https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Ser ut som om detta kan fungera:

    var List = createReactClass({
              render() {
        return (
          <ul>
            {this.props.items.map(item =>
              <ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
            )}
          </ul>
        );
      }
    });

    var ListItem = createReactClass({
      render() {
        return (
          <li onClick={this._onClick}>
            ...
          </li>
        );
      },
      _onClick() {
        this.props.onItemClick(this.props.item.id);
      }
    });

Relaterade artiklar