React js onClick не может передать значение методу

avatar
user1924375
22 апреля 2015 в 23:40
742222
35
812

Я хочу прочитать свойства значения события onClick. Но когда я нажимаю на нее, я вижу на консоли что-то вроде этого:

SyntheticMouseEvent {dispatchConfig: Object, dispatchMarker: ".1.1.0.2.0.0:1", nativeEvent: MouseEvent, type: "click", target

Мой код работает правильно. Когда я бегу, я вижу {column}, но не могу получить его в событии onClick.

Мой код:

var HeaderRows = React.createClass({
  handleSort:  function(value) {
    console.log(value);
  },
  render: function () {
    var that = this;
    return(
      <tr>
        {this.props.defaultColumns.map(function (column) {
          return (
            <th value={column} onClick={that.handleSort} >{column}</th>
          );
        })}
        {this.props.externalColumns.map(function (column) {
          // Multi dimension array - 0 is column name
          var externalColumnName = column[0];
          return ( <th>{externalColumnName}</th>);
        })}
      </tr>
    );
  }
});

Как передать значение событию onClick в React js?

Источник
WiredPrairie
23 апреля 2015 в 00:11
2

возможный дубликат привязки события OnClick в React.js

Dan
30 июня 2017 в 14:53
2

подумайте об использовании себя вместо этого. Это вводит в заблуждение, так как это должно быть синонимом слова "это" (хотя на самом деле это не важно, для каждого свое).

Merugu Prashanth
12 сентября 2017 в 07:49
0

Используя метод привязки и метод стрелки, мы можем передать значение событию Onclick

perfectionist1
24 февраля 2021 в 14:14
0

Согласился с @WiredPrairie, и парень по имени ZenMaster объяснил это точно и эффективно.

Ответы (35)

avatar
Austin Greco
22 апреля 2015 в 23:43
1544

Easy Way

Используйте функцию стрелки:

return (
  <th value={column} onClick={() => this.handleSort(column)}>{column}</th>
);

Это создаст новую функцию, которая вызывает handleSort с правильными параметрами.

Лучший способ

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

Если вы создаете подкомпонент, вы можете передать обработчик и использовать реквизиты в качестве аргументов, которые затем будут повторно визуализироваться только при изменении реквизита (поскольку ссылка на обработчик теперь никогда не изменяется):

Подкомпонент

class TableHeader extends Component {
  handleClick = () => {
    this.props.onHeaderClick(this.props.value);
  }

  render() {
    return (
      <th onClick={this.handleClick}>
        {this.props.column}
      </th>
    );
  }
}

Главный компонент

{this.props.defaultColumns.map((column) => (
  <TableHeader
    value={column}
    onHeaderClick={this.handleSort}
  />
))}

Old Easy Way (ES5)

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

return (
  <th value={column} onClick={this.handleSort.bind(this, column)}>{column}</th>
);
user1924375
23 апреля 2015 в 09:13
8

React выдает предупреждение, когда я использую как ваш код. Я меняю свой код на onClick = {that.onClick.bind (null, column)}

Bruno
11 августа 2015 в 02:25
0

у вас есть официальная справочная документация по этой функции? Не могу найти слишком много библиотек найдено для ключевого слова bind ...

Austin Greco
12 августа 2015 в 09:02
2

bind является частью обычного JavaScript (ES5) - ознакомьтесь с документацией для .bind на MDN

Simon H
24 октября 2015 в 09:43
13

Как бы вы использовали это с тегом <a>, где вам нужно передать событие, чтобы использовать preventDefault()

smudge
4 ноября 2015 в 17:26
40

@SimonH Событие будет передано как последний аргумент после аргументов, переданных через bind.

vipin8169
13 мая 2016 в 14:21
0

как это сделать в coffeescript?

AndrewMcLagan
17 мая 2016 в 07:44
47

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

E. Sundin
3 июля 2016 в 22:46
14

@AndrewMcLagan Это так. Я нашел этот для описания правила и наиболее эффективного решения.

NeverEndingQueue
4 ноября 2016 в 08:35
0

@ E.Sundin Как передать динамический аргумент функции, используя этот подход?

aaronofleonard
2 марта 2017 в 22:27
1

@NeverEndingQueue Основываясь на разделе этой статьи «Protips - List of Items», вы передаете все, что хотите, в качестве аргумента в качестве опоры, а затем заставляете функцию передавать его обратно. Это может показаться громоздким, но это наиболее эффективный способ с точки зрения производительности. Или просто снизьте производительность и используйте привязку.

Alex Shwarc
11 апреля 2017 в 16:37
7

Это совершенно неверный ответ. Если вы его используете, вы будете получать новую ссылку на функцию внутри onClick = {() => this.handleSort (column)} каждый раз, и каждый отрисованный DOM будет отличаться, и реагировать придется, чтобы обновить его. Это нарушает согласование React.

Félix Adriyel Gagnon-Grenier
14 мая 2018 в 17:39
5

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

hannad rehman
16 сентября 2018 в 14:05
0

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

Sodbileg Gansukh
23 ноября 2018 в 00:13
4

Спасибо, что предоставили «Лучший способ». Это было очень полезно.

Mohan Rajput
7 февраля 2019 в 11:27
0

Спасибо, это идеальный способ передать ценность события.

msqar
25 ноября 2019 в 16:04
0

Привязка функций к рендерингу - плохое решение!

Lijomon C John
28 мая 2020 в 19:39
1

Этот лучше. Работал у меня.

Johann
12 июня 2020 в 07:18
3

Простой способ: 3 строчки кода. Лучший способ: 13 строк кода. Угадай, кто победит?

Saveen
5 июля 2021 в 19:29
0

React js - это странно :)

callback
25 ноября 2021 в 07:38
0

или используйте useCallBack с первым методом, и он никогда не создаст новую функцию!

avatar
francis
6 августа 2021 в 07:14
0

Я бы сделал так:

const HeaderRows = props => {
    const handleSort = value => () => {

    }

    return <tr>
        {props.defaultColumns.map((column, i) =>
            <th key={i} onClick={handleSort(column)}>{column}</th>)}
        {props.externalColumns.map((column, i) => {
            // Multi dimension array - 0 is column name
            const externalColumnName = column[0]
            return (<th key={i}>{externalColumnName}</th>)
        })}
    </tr>
}
avatar
Showrin Barua
23 мая 2021 в 19:14
-1

Используйте this.handleSort. В js нет ключевого слова that. Спасибо, брат.

onClick={this.handleSort}
Emile Bergeron
24 июня 2021 в 00:43
0

В 2015 году var that = this; был обычным способом потери this контекста в функциях обратного вызова.

avatar
yfalik
23 мая 2021 в 18:10
0

Работать с функцией, а не с классом, на самом деле довольно просто.


    const [breakfastMain, setBreakFastMain] = useState("Breakfast");

const changeBreakfastMain = (e) => {
    setBreakFastMain(e.target.value);
//sometimes "value" won't do it, like for text, etc. In that case you need to 
//write 'e.target/innerHTML'
 }

<ul  onClick={changeBreakfastMain}>
   <li>
"some text here"
   </li>
<li>
"some text here"
   </li>
</ul>

avatar
Tom Bombadil
21 марта 2021 в 18:45
5

Решение React Hooks 2022

const arr = [
  { id: 1, txt: 'One' },
  { id: 2, txt: 'Two' },
  { id: 3, txt: 'Three' },
]

const App = () => {
  const handleClick = useCallback(
    (id) => () => {
      console.log("ID: ", id)
    },
    [],
  )

  return (
    <div>
      {arr.map((item) => (
        <button onClick={handleClick(item.id)}>{item.txt}</button>
      ))}
    </div>
  )
}

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

Лучшее решение с React> = 16

Самый чистый способ, который я нашел для вызова функций с несколькими параметрами в onClick, onChange и т. Д. Без использования встроенных функций, - это использовать настраиваемый атрибут data, доступный в версиях React 16 и выше.

const App = () => {
  const onClick = (e) => {
    const value1 = e.currentTarget.getAttribute("data-value1")
    const value2 = e.currentTarget.getAttribute("data-value2")
    const value2 = e.currentTarget.getAttribute("data-value2")
    console.log("Values1", value1)
    console.log("Values2", value2)
    console.log("Values3", value3)
  }
  return (
    <button onClick={onClick} data-value1="a" data-value2="b" data-value3="c" />
  )
}

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

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

Он позволяет передавать столько значений, сколько вы хотите использовать в своей функции.

Если вы передаете значения в качестве свойств своим дочерним элементам, которые будут использоваться в onClick дочернего компонента, вы можете использовать этот подход и там, не создавая функцию-оболочку.

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

const App = () => {
  const [arrState, setArrState] = useState(arr)

  const deleteContent = (e) => {
    const id = e.currentTarget.getAttribute("data-id")
    const tempArr = [...arrState]
    const filteredArr = tempArr.filter((item) => item.id !== id)
    setArrState(filteredArr)
  }

  return (
    <div>
      {arrState.map((item) => (
        <React.Fragment key={item.id}>
          <p>{item.content}</p>
          <button onClick={deleteContent} data-id={item.id}>
            Delete
          </button>
        </React.Fragment>
      ))}
    </div>
  )
}
avatar
Romel Gomez
13 февраля 2020 в 21:19
-3

Используйте закрытие , это приведет к чистому решению:

<th onClick={this.handleSort(column)} >{column}</th>

Функция handleSort вернет функцию, для которой уже установлено значение столбца .

handleSort: function(value) { 
    return () => {
        console.log(value);
    }
}

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

Пример: https://stackblitz.com/edit/react-pass-parameters-example

avatar
Ganesh Koilada
9 февраля 2020 в 05:53
-2

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

<button onClick={() => this.props.onClickHandle("StackOverFlow")}>

Обязательно используйте () => В противном случае метод щелчка будет вызываться без события щелчка.

Примечание: Сбой проверяет методы по умолчанию

Пожалуйста, найдите следующий рабочий код в codeandbox для того же самого.

Реагировать на значение передачи с помощью метода

avatar
Charith Jayasanka
22 января 2020 в 07:04
11

Просто создайте функцию, подобную этой

  function methodName(params) {
    //the thing  you wanna do
  }

и позвоните в нужное вам место

 <Icon onClick = {() => { methodName(theParamsYouwantToPass);} }/>
Eduardo ML
6 августа 2021 в 15:32
1

Oc 😶😶😶😶😶😶

Matthias Pitscher
2 октября 2021 в 23:18
1

хахаха, это безумие, что это работает! Вам даже не нужны фигурные скобки и круглые скобки. Кто-нибудь знает, как React обрабатывает здесь анонимную функцию?

avatar
Al Amin Shaon
16 января 2020 в 12:25
-3

Требуется простое изменение:

Использование <th value={column} onClick={that.handleSort} >{column}</th>

вместо <th value={column} onClick={that.handleSort} >{column}</th>

avatar
Juan Lanus
13 ноября 2019 в 23:06
0

Было много соображений по поводу производительности, все в вакууме.
Проблема с этими обработчиками заключается в том, что вам нужно каррировать их, чтобы включить аргумент, который вы не можете указать в реквизитах.
Это означает, что компоненту нужен обработчик для каждого кликабельного элемента. Согласитесь, для некоторых кнопок это не проблема, верно?
Проблема возникает, когда вы обрабатываете табличные данные с десятками столбцов и тысячами строк. Здесь вы заметите влияние создания такого количества обработчиков.

Дело в том, что мне нужен только один.
Я устанавливаю обработчик на уровне таблицы (или UL или OL ...), и когда происходит щелчок, я могу сказать, какая ячейка была нажата, используя данные, доступные с тех пор в объекте события:

nativeEvent.target.tagName
nativeEvent.target.parentElement.tagName 
nativeEvent.target.parentElement.rowIndex
nativeEvent.target.cellIndex
nativeEvent.target.textContent

Я использую поля тэга, чтобы проверить, что щелчок произошел в допустимом элементе, например игнорирую щелчки в нижних колонтитулах.
RowIndex и cellIndex указывают точное местоположение ячейки, по которой щелкнули мышью.
Текстовое содержимое - это текст выбранной ячейки.

Таким образом, мне не нужно передавать данные ячейки обработчику, он может самообслуживать их.
Если мне нужно больше данных, данных, которые не должны отображаться, я могу использовать атрибут набора данных или скрытые элементы.
С помощью простой навигации по DOM все под рукой.
Это использовалось в HTML с тех пор, так как ПК было намного легче обмануть.

perfectionist1
24 февраля 2021 в 14:18
0

Вы поднимаете очень важный вопрос - соображения производительности!

avatar
Charitha Goonewardena
1 ноября 2019 в 12:28
2

Ответ на этот вопрос возник из ниоткуда, но я думаю, что .bind поможет. Найдите пример кода ниже.

const handleClick = (data) => {
    console.log(data)
}

<button onClick={handleClick.bind(null, { title: 'mytitle', id: '12345' })}>Login</button>
avatar
Umair Ahmed
30 августа 2019 в 14:11
4

Есть несколько способов передать параметр в обработчики событий, некоторые из них следующие.

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

<button onClick={() => this.handleClick(id)} />

приведенный выше пример эквивалентен вызову .bind, либо вы можете явно вызвать bind.

<button onClick={this.handleClick.bind(this, id)} />

Помимо этих двух подходов, вы также можете передавать аргументы функции, которая определена как функция карри.

handleClick = (id) => () => {
    console.log("Hello, your ticket number is", id)
};

<button onClick={this.handleClick(id)} />
avatar
Bankim Sutaria
27 апреля 2019 в 03:24
0

Вы можете использовать свой код следующим образом:

<th value={column} onClick={(e) => that.handleSort(e, column)} >{column}</th>

Здесь e для объекта события, если вы хотите использовать методы событий, такие как preventDefault(), в своей функции дескриптора или хотите получить целевое значение или имя, например e.target.name.

avatar
Anik Mazumder
10 марта 2019 в 06:53
2
class TableHeader extends Component {
  handleClick = (parameter,event) => {
console.log(parameter)
console.log(event)

  }

  render() {
    return (
      <button type="button" 
onClick={this.handleClick.bind(this,"dataOne")}>Send</button>
    );
  }
}
4b0
10 марта 2019 в 07:13
4

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

avatar
jhchnc
8 марта 2019 в 20:21
7

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

    handleEdit(event) {
        let value = event.target.value;
    }

    ...

    <button
        value={post.id}
        onClick={this.handleEdit} >Edit</button>

Я полагаю, вы также можете использовать атрибут data-.

Простой, семантический.

avatar
Romia Mukherjee
27 июля 2018 в 18:41
2

Есть очень простой путь.

 onClick={this.toggleStart('xyz')} . 
  toggleStart= (data) => (e) =>{
     console.log('value is'+data);  
 }
avatar
Juan David Arce
10 апреля 2018 в 20:57
3
1. You just have to use an arrow function in the Onclick event like this: 

<th value={column} onClick={() => that.handleSort(theValue)} >{column}</th>

2.Then bind this in the constructor method:
    this.handleSort = this.handleSort.bind(this);

3.And finally get the value in the function:
  handleSort(theValue){
     console.log(theValue);
}
avatar
Andysenclave
10 апреля 2018 в 04:06
3

Думаю, вам придется привязать метод к экземпляру класса React. Безопаснее использовать конструктор для привязки всех методов в React. В вашем случае, когда вы передаете параметр методу, первый параметр используется для привязки контекста this метода, поэтому вы не можете получить доступ к значению внутри метода.

avatar
Karan Singh
9 марта 2018 в 10:10
3

Ниже приведен пример передачи значения в событие onClick.

Я использовал синтаксис es6. помните, что в компоненте класса стрелочная функция не привязывается автоматически, поэтому явно привязывается в конструкторе.

class HeaderRows extends React.Component {

    constructor(props) {
        super(props);
        this.handleSort = this.handleSort.bind(this);
    }

    handleSort(value) {
        console.log(value);
    }

    render() {
        return(
            <tr>
                {this.props.defaultColumns.map( (column, index) =>
                    <th value={ column } 
                        key={ index } 
                        onClick={ () => this.handleSort(event.target.value) }>
                        { column }
                    </th>
                )}

                {this.props.externalColumns.map((column, index)  =>
                    <th value ={ column[0] }
                        key={ index }>
                        {column[0]}
                    </th>
                )}
            </tr>
         );
    }
}
avatar
nandu
24 февраля 2018 в 18:29
4

Реализация показа общего счетчика от объекта путем передачи счетчика в качестве параметра от основного компонента к подкомпонентам, как описано ниже.

Вот MainComponent.js

import React, { Component } from "react";

import SubComp from "./subcomponent";

class App extends Component {

  getTotalCount = (count) => {
    this.setState({
      total: this.state.total + count
    })
  };

  state = {
    total: 0
  };

  render() {
    const someData = [
      { name: "one", count: 200 },
      { name: "two", count: 100 },
      { name: "three", count: 50 }
    ];
    return (
      <div className="App">
        {someData.map((nameAndCount, i) => {
          return (
            <SubComp
              getTotal={this.getTotalCount}
              name={nameAndCount.name}
              count={nameAndCount.count}
              key={i}
            />
          );
        })}
        <h1>Total Count: {this.state.total}</h1>
      </div>
    );
  }
}

export default App;

А вот и SubComp.js

import React, { Component } from 'react';
export default class SubComp extends Component {

  calculateTotal = () =>{
    this.props.getTotal(this.props.count);
  }

  render() {
    return (
      <div>
        <p onClick={this.calculateTotal}> Name: {this.props.name} || Count: {this.props.count}</p>
      </div>
    )
  }
};

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

avatar
Louis Alonzo
12 февраля 2018 в 12:07
5

Вы можете просто сделать это, если используете ES6.

export default class Container extends Component {
  state = {
    data: [
        // ...
    ]
  }

  handleItemChange = (e, data) => {
      // here the data is available 
      // ....
  }
  render () {
     return (
        <div>
        {
           this.state.data.map((item, index) => (
              <div key={index}>
                  <Input onChange={(event) => this.handItemChange(event, 
                         item)} value={item.value}/>
              </div>
           ))
        }
        </div>
     );
   }
 }
avatar
Mansi Teharia
1 февраля 2018 в 08:40
0

Есть 3 способа справиться с этим: -

  1. Свяжите метод в конструкторе как: -

    export class HeaderRows extends Component {
       constructor() {
           super();
           this.handleSort = this.handleSort.bind(this);
       }
    }
    
  2. Используйте стрелку при создании как: -

    handleSort = () => {
        // some text here
    }
    
  3. Третий способ: -

    <th value={column} onClick={() => that.handleSort} >{column}</th>
    
avatar
SM Chinna
10 января 2018 в 12:30
2

Использование функции стрелки:

Необходимо установить этап 2:

npm install babel-preset-stage-2:

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value=0
        }
    }

    changeValue = (data) => (e) => {
        alert(data);  //10
        this.setState({ [value]: data })
    }

    render() {
        const data = 10;
        return (
            <div>
                <input type="button" onClick={this.changeValue(data)} />
            </div>
        );
    }
}
export default App; 
avatar
Po Rith
6 октября 2017 в 16:48
6

Альтернативная попытка ответить на вопрос OP, включая вызовы e.preventDefault ():

Визуализированная ссылка ( ES6 )

<a href="#link" onClick={(e) => this.handleSort(e, 'myParam')}>

Компонент Функция

handleSort = (e, param) => {
  e.preventDefault();
  console.log('Sorting by: ' + param)
}
avatar
Merugu Prashanth
12 сентября 2017 в 09:07
3

Я добавил код для передачи значения события onclick в метод двумя способами. 1. с использованием метода привязки 2. с помощью метода стрелки (=>). см. методы handlesort1 и handlesort

var HeaderRows  = React.createClass({
    getInitialState : function() {
      return ({
        defaultColumns : ["col1","col2","col2","col3","col4","col5" ],
        externalColumns : ["ecol1","ecol2","ecol2","ecol3","ecol4","ecol5" ],

      })
    },
    handleSort:  function(column,that) {
       console.log(column);
       alert(""+JSON.stringify(column));
    },
    handleSort1:  function(column) {
       console.log(column);
       alert(""+JSON.stringify(column));
    },
    render: function () {
        var that = this;
        return(
        <div>
            <div>Using bind method</div>
            {this.state.defaultColumns.map(function (column) {
                return (
                    <div value={column} style={{height : '40' }}onClick={that.handleSort.bind(that,column)} >{column}</div>
                );
            })}
            <div>Using Arrow method</div>

            {this.state.defaultColumns.map(function (column) {
                return (
                    <div value={column} style={{height : 40}} onClick={() => that.handleSort1(column)} >{column}</div>

                );
            })}
            {this.state.externalColumns.map(function (column) {
                // Multi dimension array - 0 is column name
                var externalColumnName = column;
                return (<div><span>{externalColumnName}</span></div>
                );
            })}

        </div>);
    }
});
avatar
SlimSim
19 августа 2017 в 18:03
4

Я написал компонент-оболочку, который можно повторно использовать для этой цели, который основан на принятых здесь ответах. Однако если все, что вам нужно сделать, это передать строку, просто добавьте атрибут данных и прочтите его из e.target.dataset (как предлагали некоторые другие). По умолчанию моя оболочка привязывается к любой опоре, которая является функцией и начинается с 'on', и автоматически передает опору данных обратно вызывающей стороне после всех других аргументов события. Хотя я не тестировал его на производительность, он даст вам возможность избежать создания класса самостоятельно, и его можно использовать следующим образом:

const DataButton = withData('button')

const DataInput = withData('input');

или для компонентов и функций

const DataInput = withData(SomeComponent);

или, если хотите,

const DataButton = withData(<button/>)

заявить, что за пределами вашего контейнера (рядом с импортом)

Вот использование в контейнере:

import withData from './withData';
const DataInput = withData('input');

export default class Container extends Component {
    state = {
         data: [
             // ...
         ]
    }

    handleItemChange = (e, data) => {
        // here the data is available 
        // ....
    }

    render () {
        return (
            <div>
                {
                    this.state.data.map((item, index) => (
                        <div key={index}>
                            <DataInput data={item} onChange={this.handleItemChange} value={item.value}/>
                        </div>
                    ))
                }
            </div>
        );
    }
}

Вот код оболочки withData.js:

import React, { Component } from 'react';

const defaultOptions = {
    events: undefined,
}

export default (Target, options) => {
    Target = React.isValidElement(Target) ? Target.type : Target;
    options = { ...defaultOptions, ...options }

    class WithData extends Component {
        constructor(props, context){
            super(props, context);
            this.handlers = getHandlers(options.events, this);        
        }

        render() {
            const { data, children, ...props } = this.props;
            return <Target {...props} {...this.handlers} >{children}</Target>;
        }

        static displayName = `withData(${Target.displayName || Target.name || 'Component'})`
    }

    return WithData;
}

function getHandlers(events, thisContext) {
    if(!events)
        events = Object.keys(thisContext.props).filter(prop => prop.startsWith('on') && typeof thisContext.props[prop] === 'function')
    else if (typeof events === 'string')
        events = [events];

    return events.reduce((result, eventType) => {
        result[eventType] = (...args) => thisContext.props[eventType](...args, thisContext.props.data);
        return result;
    }, {});
}
avatar
hannad rehman
13 августа 2017 в 19:47
23

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

class HtmlComponent extends React.Component {
  constructor() {
    super();
    this.state={
       name:'MrRehman',
    };
    this.handleClick= this.handleClick.bind(this);
  }

  handleClick(event) {
    const { param } = e.target.dataset;
    console.log(param);
    //do what you want to do with the parameter
  }

  render() {
    return (
      <div>
        <h3 data-param="value what you wanted to pass" onClick={this.handleClick}>
          {this.state.name}
        </h3>
      </div>
    );
  }
}

ОБНОВЛЕНИЕ

на случай, если вы хотите иметь дело с объектами, которые должны быть параметрами. вы можете использовать JSON.stringify(object), чтобы преобразовать его в строку и добавить в набор данных.

return (
   <div>
     <h3 data-param={JSON.stringify({name:'me'})} onClick={this.handleClick}>
        {this.state.name}
     </h3>
   </div>
);
SlimSim
19 августа 2017 в 18:04
2

это не работает, если переданные данные являются объектом

hannad rehman
21 августа 2017 в 09:21
0

используйте JSON.stringify, чтобы исправить проблему. @SlimSim. Это должно делать свое дело

KFE
3 ноября 2017 в 13:54
1

Если вам нужно использовать JSON.stringify для этой проблемы, вероятно, это неправильный метод. Процесс стрингификации занимает много памяти.

hannad rehman
3 ноября 2017 в 13:56
1

в большинстве случаев вы должны передавать только идентификатор в качестве параметров и получать сведения об объекте на основе этого идентификатора из исходного объекта. и почему и как требуется много памяти, я знаю, что JSON stringify работает медленно, но щелчок Fn является асинхронным, и он не будет иметь никакого эффекта или 0 для dom после создания

avatar
Sagiv b.g
1 августа 2017 в 22:48
191

Здесь есть хорошие ответы, и я согласен с @Austin Greco (второй вариант с отдельными компонентами)
Есть еще один способ, который мне нравится, каррирование.
Что вы можете сделать, так это создать функцию, которая принимает параметр (ваш параметр) и возвращает другую функцию, которая принимает другой параметр (в данном случае событие щелчка). тогда вы можете делать с ним все, что захотите.

ES5:

handleChange(param) { // param is the argument you passed to the function
    return function (e) { // e is the event object that returned

    };
}

ES6:

handleChange = param => e => {
    // param is the argument you passed to the function
    // e is the event object that returned
};

И вы будете использовать его так:

<input 
    type="text" 
    onChange={this.handleChange(someParam)} 
/>

Вот полный пример такого использования:

const someArr = ["A", "B", "C", "D"];

class App extends React.Component {
  state = {
    valueA: "",
    valueB: "some initial value",
    valueC: "",
    valueD: "blah blah"
  };

  handleChange = param => e => {
    const nextValue = e.target.value;
    this.setState({ ["value" + param]: nextValue });
  };

  render() {
    return (
      <div>
        {someArr.map(obj => {
          return (
            <div>
              <label>
                {`input ${obj}   `}
              </label>
              <input
                type="text"
                value={this.state["value" + obj]}
                onChange={this.handleChange(obj)}
              />
              <br />
              <br />
            </div>
          );
        })}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />  rootElement);
<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="root"></div>

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

Изменить:
Как предлагается в комментариях ниже, вы можете кэшировать / запоминать результат функции.

Вот наивная реализация:

let memo = {};

const someArr = ["A", "B", "C", "D"];

class App extends React.Component {
  state = {
    valueA: "",
    valueB: "some initial value",
    valueC: "",
    valueD: "blah blah"
  };

  handleChange = param => {
    const handler = e => {
      const nextValue = e.target.value;
      this.setState({ ["value" + param]: nextValue });
    }
    if (!memo[param]) {
      memo[param] = e => handler(e)
    }
    return memo[param]
  };

  render() {
    return (
      <div>
        {someArr.map(obj => {
          return (
            <div key={obj}>
              <label>
                {`input ${obj}   `}
              </label>
              <input
                type="text"
                value={this.state["value" + obj]}
                onChange={this.handleChange(obj)}
              />
              <br />
              <br />
            </div>
          );
        })}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />  rootElement);
<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="root" />
Tamb
4 августа 2017 в 16:28
14

Это должен быть принятый ответ. ДЕЙСТВИТЕЛЬНО легко реализовать, и вам не нужно создавать какие-либо другие компоненты или связывать по-другому. Спасибо!

travellingprog
26 августа 2017 в 01:38
33

Выглядит лучше, но с точки зрения производительности каррирование на самом деле не помогает, потому что, если вы вызываете handleChange дважды с одним и тем же параметром, вы получаете две функции, которые движок JS считает отдельными объектами, даже если они делают то же самое. . Так что вы все равно получите повторный рендер. Ради производительности вам нужно будет кэшировать результаты handleChange, чтобы получить преимущество в производительности. Нравится handleChange = param => cache[param] || (e => { // the function body })

llioor
20 декабря 2017 в 10:54
6

Это идеальный ответ, если вы выполните совет от @travellingprog

Sadok Mtir
26 января 2018 в 16:02
2

Может ли кто-нибудь дать ссылку или объяснить, как работает этот механизм кеширования? благодаря.

Hatem Alimam
17 февраля 2018 в 11:23
2

Красиво и аккуратно!

Jithesh Kt
8 марта 2018 в 02:19
0

Красивый. Одно сомнение, как в ES6 сделать параметр необязательным.?

Sagiv b.g
8 марта 2018 в 05:26
0

@JitheshKt, если по желанию вы имеете в виду значение по умолчанию, вы можете просто сделать это: handleChange = (param = 'something') => e => {

cutemachine
14 августа 2018 в 05:47
2

Да, карри - это то, что вам нужно. Я согласен с @Tamb, это должен быть принятый ответ. Вы можете узнать больше по теме в этой статье: Параметризованные обработчики событий

Sagiv b.g
5 октября 2018 в 18:27
0

@SadokMtir я добавил пример мемоизации

omeralper
20 февраля 2019 в 23:58
4

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

Sagiv b.g
21 февраля 2019 в 05:38
0

@omeralper упоминается в ответе

Syg
25 июня 2019 в 11:43
1

Вы настоящий MVP

Ari
9 июля 2019 в 00:47
1

@travellingprog Это то, что называется мемоизацией?

perfectionist1
24 февраля 2021 в 14:54
0

@JitheshKt, да, это возможно путем реализации условного статуса. Между прочим, этот ответ (Sagivb.g) был бы принятым ответом.

avatar
Reetesh Agrawal
9 марта 2017 в 17:49
3

У меня есть 3 предложения по этому поводу в JSX onClick Events -

  1. На самом деле нам не нужно использовать .bind () или функцию стрелки в нашем коде. Вы можете просто использовать в своем коде.

  2. Вы также можете переместить событие onClick с th (или ul) на tr (или li), чтобы повысить производительность. Обычно у вас будет n количество «слушателей событий» для вашего n li элемента.

    So finally code will look like this:
    <ul onClick={this.onItemClick}>
        {this.props.items.map(item =>
               <li key={item.id} data-itemid={item.id}>
                   ...
               </li>
          )}
    </ul>
    

    // И вы можете получить доступ к item.id в методе onItemClick, как показано ниже:

    onItemClick = (event) => {
       console.log(e.target.getAttribute("item.id"));
    }
    
  3. Я согласен с упомянутым выше подходом к созданию отдельного компонента React для ListItem и List. Этот код make выглядит хорошо, однако, если у вас есть 1000 li, будет создано 1000 прослушивателей событий. Убедитесь, что у вас не слишком много прослушивателей событий.

    import React from "react";
    import ListItem from "./ListItem";
    export default class List extends React.Component {
    
        /**
        * This List react component is generic component which take props as list of items and also provide onlick
        * callback name handleItemClick
        * @param {String} item - item object passed to caller
        */
        handleItemClick = (item) => {
            if (this.props.onItemClick) {
                this.props.onItemClick(item);
            }
        }
    
        /**
        * render method will take list of items as a props and include ListItem component
        * @returns {string} - return the list of items
        */
        render() {
            return (
                <div>
                  {this.props.items.map(item =>
                      <ListItem key={item.id} item={item} onItemClick={this.handleItemClick}/>
                  )}
                </div>
            );
        }
    
    }
    
    
    import React from "react";
    
    export default class ListItem extends React.Component {
        /**
        * This List react component is generic component which take props as item and also provide onlick
        * callback name handleItemClick
        * @param {String} item - item object passed to caller
        */
        handleItemClick = () => {
            if (this.props.item && this.props.onItemClick) {
                this.props.onItemClick(this.props.item);
            }
        }
        /**
        * render method will take item as a props and print in li
        * @returns {string} - return the list of items
        */
        render() {
            return (
                <li key={this.props.item.id} onClick={this.handleItemClick}>{this.props.item.text}</li>
            );
        }
    }
    
SlimSim
19 августа 2017 в 17:38
1

Это не работает, если данные, которые вам нужно передать, являются объектами. Атрибут будет работать только со строками. Также чтение из dom через атрибут get, вероятно, является более дорогостоящей операцией.

avatar
Santiago Ramirez
24 февраля 2017 в 20:46
32

Это мой подход, не уверен, насколько он плох, прокомментируйте, пожалуйста,

В интерактивном элементе

return (
    <th value={column} onClick={that.handleSort} data-column={column}>   {column}</th>
);

, а затем

handleSort(e){
    this.sortOn(e.currentTarget.getAttribute('data-column'));
}
kamranicus
15 марта 2017 в 05:00
0

Это подход, о котором я думал, он кажется немного взломанным, но позволяет избежать создания нового компонента. Я не уверен, что getAttribute лучше или хуже с точки зрения производительности по сравнению с выделением в отдельный компонент.

Stephane L
16 марта 2018 в 16:41
4

Думаю, это хорошее решение, потому что оно очень простое. Но он работает только со строковыми значениями, если вам нужен объект, он не работает.

png
20 апреля 2018 в 22:42
0

Для объекта вам нужно будет сделать encodeURIComponent(JSON.stringify(myObj)), а затем проанализировать его, JSON.parse(decodeURIComponent(myObj)). Что касается функций, я уверен, что это не будет работать без eval или new Function (), которых следует избегать. По этим причинам я не использую атрибуты данных для передачи данных в React / JS.

Santiago Ramirez
4 мая 2018 в 08:54
0

Хочу добавить, что использую не часто и только для мелких вещей. Но обычно я просто создаю компонент и передаю ему данные в качестве реквизита. Затем либо обработайте щелчок внутри этого нового компонента, либо передайте компоненту функцию onClick. Как объясняется в ответе Брэндона

gsziszi
21 октября 2018 в 15:24
1

доступ к набору данных можно получить напрямую в современных браузерах (включая IE11): e.currentTarget.dataset.column

Juan Lanus
13 ноября 2019 в 22:35
0

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

avatar
MiF
15 августа 2016 в 15:43
-5

Я думаю, .bind(this, arg1, arg2, ...) в карте React - плохой код, потому что он медленный! 10-50 из .bind(this) в методе одиночной визуализации - очень медленный код.
Я исправляю это так:
Метод рендеринга
<tbody onClick={this.handleClickRow}>
карта <tr data-id={listItem.id}>
Обработчик
var id = $(ev.target).closest('tr').data().id

Полный код ниже:

class MyGrid extends React.Component {
  // In real, I get this from props setted by connect in Redux
  state = {
    list: [
      {id:1, name:'Mike'},
      {id:2, name:'Bob'},
      {id:3, name:'Fred'}
    ],
    selectedItemId: 3
  }
  
  render () {
    const { list, selectedItemId }  = this.state
    const { handleClickRow } = this

    return (
      <table>
        <tbody onClick={handleClickRow}>
          {list.map(listItem =>(
            <tr key={listItem.id} data-id={listItem.id} className={selectedItemId===listItem.id ? 'selected' : ''}>
              <td>{listItem.id}</td>
              <td>{listItem.name}</td>
            </tr>
          ))}
        </tbody>
      </table>
    )
  }
  
  handleClickRow = (ev) => {
    const target = ev.target
    // You can use what you want to find TR with ID, I use jQuery
    const dataset = $(target).closest('tr').data()
    if (!dataset || !dataset.id) return
    this.setState({selectedItemId:+dataset.id})
    alert(dataset.id) // Have fun!
  }
}

ReactDOM.render(<MyGrid/>  document.getElementById("react-dom"))
table {
  border-collapse: collapse;
}

.selected td {
  background-color: rgba(0, 0, 255, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="react-dom"></div>
MiF
1 марта 2017 в 13:45
0

Пожалуйста, пишите комментарии к тому, что вам минус

kamranicus
15 марта 2017 в 05:04
2

Смешивание JQuery с React не рекомендуется для использования React, особенно если вы тестируете свой код модулем. Самый идиоматический ответ - разделить элементы списка на дочерние компоненты, потому что у них есть действия, которые предположительно влияют на состояние приложения.

MiF
15 марта 2017 в 15:17
0

Я знаю, но это только для простейшей демонстрации моего решения.

maaartinus
8 июля 2018 в 16:15
1

В вашем коде jQuery делает больше, чем просто демонстрирует ваше решение. Для обработчиков на самом <tr> вы можете опустить closest и вообще не нуждаться в jQuery, поэтому ваше решение будет таким же, как и другие, использующие data-*. Для обработчиков компонентов внутри <tr> вам понадобятся некоторые манипуляции с DOM ... это плохо, как уже было сказано, но такие обработчики не запрашивались.

MiF
15 июля 2018 в 15:35
0

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

avatar
Brandon
12 августа 2016 в 01:48
79

[[h / t to @ E.Sundin для со ссылкой на этот в комментарии]

Главный ответ (анонимные функции или привязка) будет работать, но он не самый эффективный, поскольку создает копию обработчика событий для каждого экземпляра, сгенерированного функцией map().

Это объяснение оптимального способа сделать это из ESLint-plugin-react:

Списки позиций

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

const List = props => (
      <ul>
        {props.items.map(item =>
          <li key={item.id} onClick={() => console.log(item.id)}>
            ...
          </li>
        )}
      </ul>
    );

Вместо того, чтобы делать это таким образом, перетащите повторяющийся раздел в отдельный компонент:

const List = props => (
      <ul>
        {props.items.map(item =>
          <ListItem 
            key={item.id} 
            item={item} 
            onItemClick={props.onItemClick} // assume this is passed down to List
           />
        )}
      </ul>
    );


const ListItem = props => {
  const _onClick = () => {
    console.log(props.item.id);
  }
    return (
      <li onClick={_onClick}>
        ...
      </li>
    );

});

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

aikeru
12 августа 2016 в 15:07
0

Вызывает ли React эти функции с помощью call / apply, а затем, под капотом, и избегает использования bind внутри?

Carlos Martinez
9 мая 2017 в 13:50
1

Есть ли способ сделать это с помощью компонента без сохранения состояния?

Brandon
9 мая 2017 в 14:46
2

@CarlosMartinez хороший глаз, я обновил пример - в первую очередь они должны были быть функциональными компонентами без сохранения состояния (SFC). Как правило, если компонент никогда не использует this.state, вы можете безопасно заменить его с помощью SFC.

Mattias Petter Johansson
19 мая 2017 в 21:04
4

Хм, я не понимаю, как это более производительно? Разве функция ListItem не будет вызываться каждый раз, и поэтому функция _onClick будет создаваться при каждой визуализации.

Brandon
19 мая 2017 в 21:27
1

Я далек от эксперта, но, насколько я понимаю, в «правильном» шаблоне есть только один экземпляр обработчика, и он передал опору для любого экземпляра компонента, который его вызывает. В примере связывания (т. Е. «Неправильный» шаблон) существует один экземпляр обработчика для каждого экземпляра компонента. Это что-то вроде памяти, эквивалентной написанию одной и той же функции тридцать раз, наоборот, записывая ее один раз и вызывая ее там, где это необходимо.

Ari
9 июля 2019 в 00:07
0

Разве это не просто отрисовка большего количества функций?

Taylan
8 ноября 2019 в 19:08
0

Возможно, вы хотели использовать props.onItemClick вместо console.log

Isaac Pak
14 февраля 2020 в 18:48
0

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

avatar
Brett DeWoody
20 мая 2016 в 05:36
5

Еще один вариант, не связанный с .bind или ES6, - это использование дочернего компонента с обработчиком для вызова родительского обработчика с необходимыми реквизитами. Вот пример (и ссылка на рабочий пример ниже):

var HeaderRows = React.createClass({
  handleSort:  function(value) {
     console.log(value);
  },
  render: function () {
      var that = this;
      return(
          <tr>
              {this.props.defaultColumns.map(function (column) {
                  return (
                      <TableHeader value={column} onClick={that.handleSort} >
                        {column}
                      </TableHeader>
                  );
              })}
              {this.props.externalColumns.map(function (column) {
                  // Multi dimension array - 0 is column name
                  var externalColumnName = column[0];
                  return ( <th>{externalColumnName}</th>
                  );
              })}
          </tr>);
      )
  }
});

// A child component to pass the props back to the parent handler
var TableHeader = React.createClass({
  propTypes: {
    value: React.PropTypes.string,
    onClick: React.PropTypes.func
  },
  render: function () {
    return (
      <th value={this.props.value} onClick={this._handleClick}
        {this.props.children}
      </th>
    )        
  },
  _handleClick: function () {
    if (this.props.onClick) {
      this.props.onClick(this.props.value);
    }
  }
});

Основная идея заключается в том, что родительский компонент передает функцию onClick дочернему компоненту. Дочерний компонент вызывает функцию onClick и может получить доступ к любому переданному ему props (и к event), что позволяет использовать любое значение event или другие свойства в родительской функции onClick.

Вот демонстрация CodePen, демонстрирующая этот метод в действии.

avatar
Vladimirs Matusevics
29 марта 2016 в 10:58
9
class extends React.Component {
    onClickDiv = (column) => {
        // do stuff
    }
    render() {
        return <div onClick={() => this.onClickDiv('123')} />
    }
}
Alex Shwarc
11 апреля 2017 в 17:02
3

То же самое - новый DOM на каждом рендере. Таким образом, React будет обновлять DOM каждый раз.

avatar
aikeru
2 марта 2016 в 13:42
137

В настоящее время с ES6, я думаю, мы могли бы использовать обновленный ответ.

return (
  <th value={column} onClick={()=>this.handleSort(column)} >{column}</th>
);

В основном (для тех, кто не знает), поскольку onClick ожидает переданную ему функцию, bind работает, потому что создает копию функции. Вместо этого мы можем передать выражение функции стрелки, которое просто вызывает нужную нам функцию и сохраняет this. Вам никогда не нужно связывать метод render в React, но если по какой-то причине вы теряете this в одном из ваших компонентных методов:

constructor(props) {
  super(props);
  this.myMethod = this.myMethod.bind(this);
}
Dan Abramov
2 марта 2016 в 13:46
11

Вам никогда не нужно связывать render(), потому что он вызывается React. Во всяком случае, вам нужно bind обработчиков событий, и только тогда, когда вы не используете стрелки.

aikeru
2 марта 2016 в 13:50
1

@DanAbramov Я думаю, что вы правы, но я включил его на всякий случай - дополнен примером, который неявно не поощряет привязку рендеринга.

Dan Abramov
2 марта 2016 в 13:55
4

Обратите внимание, что также лучше передать props в super(), иначе this.props будет неопределенным во время конструктора, что может запутать.

Petr Peller
2 апреля 2016 в 12:00
1

Этот подход рекомендуется в официальных документах React facebook.github.io/react/docs/reusable-components.html

Erez Cohen
19 апреля 2016 в 19:51
0

@DanAbramov - Можно ли добиться того же с помощью функции без сохранения состояния? (или вообще определять метод внутри функции без состояния - плохая практика?)

Dan Abramov
19 апреля 2016 в 23:04
3

Чего добиваться? Вы можете определить обработчик внутри функционального компонента и передать его. При каждом рендеринге это будет другая функция, поэтому , если вы определили с помощью профилировщика, что он вызывает проблемы с производительностью (и только если вы действительно в этом уверены!), Подумайте об использовании вместо этого класса.

me-me
24 мая 2018 в 04:33
0

В чем разница между этим решением и решением @krotov. Разве это не то же самое при создании новой функции каждый раз, когда происходит щелчок?

aikeru
24 мая 2018 в 17:14
1

@ me-me Да, и если вы включите передовой JavaScript в Babel, вам нужно просто объявить свойства в своем классе, например foo = () => {...}, а затем указать в render <button onClick={this.foo}... единственную причину для включения стрелочной функции, если вы используете babel таким образом, IMO, если вы хотите захватить некоторую переменную, которая существует только в области метода render () (события могут просто передаваться и не применяться).

me-me
25 мая 2018 в 15:43
2

Спасибо, Айкеру. А, хорошо, вы говорите, что если вы объявляете метод foo как стрелочную функцию в самом классе, а затем ссылаетесь на него, используя контекст this, он создается только один раз вместо того, чтобы объявлять его внутри onClick, где он создает одну и ту же функцию каждый раз, когда вы щелкаете кнопка. Это правильно, по моему мнению.

zuckerburg
31 мая 2019 в 09:55
0

Обратите внимание, что использование такой стрелочной функции для вызова обработчика и передачи ему переменной будет срабатывать при рендеринге, а не при щелчке, что отрицает любую точку с точки зрения того, что что-то происходит при щелчке, а не при рендеринге (что означает, какая логика вы делаю по клику, сразу увольняется)

aikeru
7 июня 2019 в 15:37
2

@zuckerburg onClick = {() => alert ('hi')} не будет немедленно предупреждать, как и onClick = {this.showAlert}, потому что в обоих случаях вы передаете (не вызываете) функцию.