Я хочу прочитать свойства значения события 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?
Используйте функцию стрелки:
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}
/>
))}
Старый простой способ (ES5)
Используйте .bind
, чтобы передать требуемый параметр:
return (
<th value={column} onClick={that.handleSort.bind(that, column)}>{column}</th>
);
bind
...
В настоящее время, с 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);
}
render()
потому что он вызывается React. Во всяком случае, вам нужно bind
обработчики событий, и только тогда, когда вы не используете стрелки.
Здесь есть хорошие ответы, и я согласен с @Austin Greco (второй вариант с отдельными компонентами)
Есть еще один способ, который мне нравится, карри.
Что вы можете сделать, это создать функцию, которая принимает параметр (ваш параметр) и возвращает другую функцию, которая принимает другой параметр (в данном случае событие click). тогда вы можете делать с ним все, что захотите.
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" />
handleChange = param => cache[param] || (e => { // the function body })
[[h/t to @E.Sundin для ссылка на это в комментарии]
Верхний ответ (анонимные функции или привязка) будет работать, но он не самый результативный, поскольку он создает копию обработчика события для каждого экземпляра, сгенерированного функцией map()
.
Это объяснение оптимального способа сделать это из ESLint-plugin-react:
Списки элементов
Общим примером использования bind в рендеринге является рендеринг списка, отдельный обратный вызов для элемента списка:
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>
);
});
Это ускорит рендеринг, поскольку это позволит избежать необходимости создавать новые функции (через вызовы связывания) на каждом рендере.
Это мой подход, не уверенный, насколько это плохо, прокомментируйте
В интерактивном элементе
return (
<th value={column} onClick={that.handleSort} data-column={column}> {column}</th>
);
а затем
handleSort(e){
this.sortOn(e.currentTarget.getAttribute('data-column'));
}
этот пример может немного отличаться от вашего. но я могу заверить вас, что это лучшее решение для этой проблемы. Я искал несколько дней решение, которое не имеет проблемы с производительностью. и, наконец, придумал это.
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>
);
Еще одна опция, не включающая .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, показывающая этот метод в действии.
class extends React.Component {
onClickDiv = (column) => {
// do stuff
}
render() {
return <div onClick={() => this.onClickDiv('123')} />
}
}
Я добавил код для значения события onclick, переданного методу двумя способами. 1. используя метод bind 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>);
}
});
Я написал компонент оболочки, который можно использовать повторно для этой цели, основываясь на принятых ответах здесь. Если все, что вам нужно сделать, это передать строку, однако, просто добавьте атрибут data и прочитайте его с 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;
}, {});
}
Это очень простой способ.
onClick={this.toggleStart('xyz')} .
toggleStart= (data) => (e) =>{
console.log('value is'+data);
}
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);
}
Я предполагаю, что вам придется привязать метод к экземпляру класса Reacts. Безопаснее использовать конструктор для связывания всех методов в React. В вашем случае, когда вы передаете параметр методу, первый параметр используется для привязки этого контекста метода, поэтому вы не можете получить доступ к значению внутри метода.
Ниже приведен пример, который передает значение на событие 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>
);
}
}
Реализация показа общего количества от объекта путем передачи количества в качестве параметра от основного к вспомогательным компонентам, как описано ниже.
Вот 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>
)
}
};
Попробуйте реализовать выше, и вы получите точный сценарий того, как параметры передачи работают вactjs для любого метода DOM.
Вы можете просто сделать это, если вы используете 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>
);
}
}
Есть 3 способа справиться с этим:
Привязать метод в конструкторе как: -
export class HeaderRows extends Component {
constructor() {
super();
this.handleSort = this.handleSort.bind(this);
}
}
Используйте функцию стрелки, создавая ее как: -
handleSort = () => {
// some text here
}
Третий способ заключается в следующем:
<th value={column} onClick={() => that.handleSort} >{column}</th>
Используя функцию стрелки:
Вы должны установить этап 2:
npm установить 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;
Выполнение альтернативной попытки ответить на вопрос OP, включая вызовы e.preventDefault():
Ссылка (ES6)
<a href="#link" onClick={(e) => this.handleSort(e, 'myParam')}>
Функция компонента
handleSort = (e, param) => {
e.preventDefault();
console.log('Sorting by: ' + param)
}
У меня ниже 3 предложений на JSX onClick Events -
На самом деле нам не нужно использовать функцию .bind() или Arrow в нашем коде. Вы можете просто использовать в своем коде.
Вы также можете переместить событие 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"));
}
Я согласен с упомянутым выше подходом для создания отдельного компонента React для ListItem и List. Этот код выглядит хорошо, но если у вас 1000 пользователей, тогда будет создано 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>
);
}
}
Я думаю, .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>
Я бы порекомендовал curry over bind в этом случае. Например,
return (
<th value={column} onClick={this.handleSort.curry(column)} {column}</th>
);
Function.prototype.curry()
.