Как избежать связывания или встроенных функций стрелок внутри метода рендеринга

Мы должны избегать привязки методов внутри рендера, потому что во время повторного рендеринга он будет создавать новые методы вместо использования старых, что повлияет на производительность.

Так что для таких сценариев, как это:

<input onChange = { this._handleChange.bind(this) } ...../>

Мы можем связать метод _handleChange либо в конструкторе:

this._handleChange = this._handleChange.bind(this);

Или мы можем использовать синтаксис инициализатора свойства:

_handleChange = () => {....}

Теперь давайте рассмотрим случай, когда мы хотим передать некоторый дополнительный параметр, скажем, в простом приложении todo, щелкнув по элементу, мне нужно удалить элемент из массива, для этого мне нужно передать либо индекс элемента, либо имя todo в каждом метод onClick:

todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)

А пока предположим, что имена todo уникальны.

Согласно DOC:

Проблема с этим синтаксисом заключается в том, что каждый раз при рендеринге компонента создается новый обратный вызов.

Вопрос:

Как избежать этого способа привязки внутри метода рендеринга или каковы альтернативы этому?

Пожалуйста, предоставьте любую ссылку или пример, спасибо.

Ответ 1

Первое: простое решение будет состоять в том, чтобы создать компонент для содержимого внутри функции карты и передать значения как реквизиты, а когда вы вызываете функцию из дочернего компонента, вы можете передать значение функции, переданной как реквизиты.

родитель

deleteTodo = (val) => {
    console.log(val)
}
todos.map(el => 
    <MyComponent val={el} onClick={this.deleteTodo}/> 

)

MyComponent

class MyComponent extends React.Component {
    deleteTodo = () => {
        this.props.onClick(this.props.val);
    }
    render() {
       return <div  onClick={this.deleteTodo}> {this.props.val} </div>
    }
}

Образец фрагмента

class Parent extends React.Component {
     _deleteTodo = (val) => {
        console.log(val)
    }
    render() {
        var todos = ['a', 'b', 'c'];
        return (
           <div>{todos.map(el => 
             <MyComponent key={el} val={el} onClick={this._deleteTodo}/> 
        
           )}</div>
        )
    }
    
   
}

class MyComponent extends React.Component {
        _deleteTodo = () => {
                     console.log('here');   this.props.onClick(this.props.val);
        }
        render() {
           return <div onClick={this._deleteTodo}> {this.props.val} </div>
        }
    }
    
ReactDOM.render(<Parent/>, document.getElementById('app'));
<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="app"></div>

Ответ 2

Этот ответ fooobar.com/questions/1267907/..., безусловно, является исчерпывающим, но я бы сказал, что лучше решить эту проблему, внедрив надлежащий shouldComponentUpdate в дочернем компоненте.

Даже если реквизиты точно такие же, следующий код будет по-прежнему перерисовывать дочерние shouldComponentUpdate если они не предотвращают это в своем собственном shouldComponentUpdate (они могут наследовать его от PureComponent):

handleChildClick = itemId => {}

render() {
    return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}

Доказательство: https://jsfiddle.net/69z2wepo/92281/.

Таким образом, чтобы избежать повторного рендеринга, дочерний компонент должен в любом случае реализовывать shouldComponentUpdate. Теперь единственной разумной реализацией является полное игнорирование onClick независимо от того, изменилось ли оно:

shouldComponentUpdate(nextProps) {
    return this.props.array !== nextProps.array;
}

Ответ 3

Как избежать этого способа привязки внутри метода рендеринга или альтернативы этого?

Если вам нужна повторная рендеринг, тогда ваши друзья будут shouldComponentUpdate и PureComponent, и они помогут вам оптимизировать рендеринг.

Вам нужно извлечь компонент "Ребенок" из "Родительского" и передать всегда те же реквизиты и реализовать shouldComponentUpdate или использовать PureComponent. То, что мы хотим, это случай, когда мы удаляем дочерний элемент, другие дети не должны повторно отображаться.

Пример

import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';

class Product extends PureComponent {
  render() {
    const { id, name, onDelete } = this.props;

    console.log(`<Product id=${id} /> render()`);
    return (
      <li>
        {id} - {name}
        <button onClick={() => onDelete(id)}>Delete</button>
      </li>
    );
  }
}

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      products: [
        { id: 1, name: 'Foo' },
        { id: 2, name: 'Bar' },
      ],
    };

    this.handleDelete = this.handleDelete.bind(this);
  }

  handleDelete(productId) {
    this.setState(prevState => ({
      products: prevState.products.filter(product => product.id !== productId),
    }));
  }

  render() {
    console.log(`<App /> render()`);
    return (
      <div>
        <h1>Products</h1>
        <ul>
          {
            this.state.products.map(product => (
              <Product 
                key={product.id}
                onDelete={this.handleDelete}
                {...product}
              />
            ))
          }
        </ul>
      </div>
    ); 
  }
}

render(<App />, document.getElementById('root'));

Демо: https://codesandbox.io/s/99nZGlyZ

Ожидаемое поведение

  • <App /> render()
  • <Product id=1... render()
  • <Product id=2... render()

Когда мы удаляем <Product id=2 ..., возвращается только <App />.

  • render()

Чтобы просмотреть эти сообщения в демо, откройте консоль инструментов dev.

Тот же метод используется и описан в статье: "React is Slow" , "React is Fast: оптимизация действия приложений на практике" Франсуа Занинотто.

Ответ 4

Документация призывает использовать атрибуты данных и получать к ним доступ из evt.target.dataset:

_deleteTodo = (evt) => {
  const elementToDelete = evt.target.dataset.el;
  this.setState(prevState => ({
    todos: prevState.todos.filter(el => el !== elementToDelete)
  }))
}

// and from render:

todos.map(
  el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)

Также обратите внимание, что это имеет смысл, только если у вас есть проблемы с производительностью:

Можно ли использовать функции стрелок в методах рендеринга?

В общем, да, это нормально, и часто это самый простой способ передать параметры в функции обратного вызова.

Если у вас есть проблемы с производительностью, обязательно оптимизируйте!

Ответ 5

Вы можете использовать функцию curried для инкапсуляции значений

class MyComponent extends React.Component {
  constructor() {
    this.deleteTodo = this.deleteTodo.bind(this);
  }

  deleteTodo = (el) => () => {
    this.props.onClick(el, this.props.val);
  }

  render() {
    return <div key={el} onClick={this.deleteTodo(el)}> {el} </div>
  }
}

подробнее здесь: https://www.sitepoint.com/currying-in-functional-javascript/