Обработчики событий в режиме бездействия
Попытка выяснить оптимальный способ создания обработчиков событий в компонентах бездействий "Реагировать". Я мог бы сделать что-то вроде этого:
const myComponent = (props) => {
const myHandler = (e) => props.dispatch(something());
return (
<button onClick={myHandler}>Click Me</button>
);
}
Недостатком здесь является то, что каждый раз, когда этот компонент визуализируется, создается новая функция "myHandler". Есть ли лучший способ создания обработчиков событий в компонентах без состояния, которые все еще могут получить доступ к свойствам компонента?
Ответы
Ответ 1
Применение обработчиков к элементам в компонентах функций обычно должно выглядеть так:
const f = props => <button onClick={props.onClick}></button>
Если вам нужно сделать что-то гораздо более сложное, это признак того, что либо a) компонент не должен быть без состояния (используйте класс или хуки), либо b) вы должны создавать обработчик во внешнем компоненте контейнера с состоянием.
Кроме того, и немного подрываю мою первую мысль, если только компонент не находится в особенно интенсивно перерисовываемой части приложения, нет необходимости беспокоиться о создании функций стрелок в render()
.
Ответ 2
Используя новую функцию React-хуков, она может выглядеть примерно так:
const HelloWorld = ({ dispatch }) => {
const handleClick = useCallback(() => {
dispatch(something())
})
return <button onClick={handleClick} />
}
useCallback
создает запомненную функцию, то есть новая функция не будет создаваться повторно в каждом цикле рендеринга.
https://reactjs.org/docs/hooks-reference.html#usecallback
Тем не менее, это все еще на стадии предложения.
Ответ 3
Как насчет этого способа:
const myHandler = (e,props) => props.dispatch(something());
const myComponent = (props) => {
return (
<button onClick={(e) => myHandler(e,props)}>Click Me</button>
);
}
Ответ 4
Если обработчик полагается на изменяющиеся свойства, вам придется создавать обработчик каждый раз, так как у вас нет экземпляра с состоянием для его кэширования. Другой альтернативой, которая может сработать, будет запоминание обработчика на основе входных реквизитов.
Пара вариантов реализации lodash._memoize R.memoize fast-memoize
Ответ 5
решение one mapPropsToHandler и event.target.
функции являются объектами в js, поэтому можно прикрепить их свойства.
function onChange() { console.log(onChange.list) }
function Input(props) {
onChange.list = props.list;
return <input onChange={onChange}/>
}
эта функция только связывает свойство с функцией.
export function mapPropsToHandler(handler, props) {
for (let property in props) {
if (props.hasOwnProperty(property)) {
if(!handler.hasOwnProperty(property)) {
handler[property] = props[property];
}
}
}
}
Я получаю свои реквизиты именно так.
export function InputCell({query_name, search, loader}) {
mapPropsToHandler(onChange, {list, query_name, search, loader});
return (
<input onChange={onChange}/>
);
}
function onChange() {
let {query_name, search, loader} = onChange;
console.log(search)
}
в этом примере объединены как event.target, так и mapPropsToHandler. его лучше привязывать функции к обработчикам только не числам или строкам. число и строки могут быть переданы с помощью атрибута DOM, например
<select data-id={id}/>
а не mapPropsToHandler
import React, {PropTypes} from "react";
import swagger from "../../../swagger/index";
import {sync} from "../../../functions/sync";
import {getToken} from "../../../redux/helpers";
import {mapPropsToHandler} from "../../../functions/mapPropsToHandler";
function edit(event) {
let {translator} = edit;
const id = event.target.attributes.getNamedItem('data-id').value;
sync(function*() {
yield (new swagger.BillingApi())
.billingListStatusIdPut(id, getToken(), {
payloadData: {"admin_status": translator(event.target.value)}
});
});
}
export default function ChangeBillingStatus({translator, status, id}) {
mapPropsToHandler(edit, {translator});
return (
<select key={Math.random()} className="form-control input-sm" name="status" defaultValue={status}
onChange={edit} data-id={id}>
<option data-tokens="accepted" value="accepted">{translator('accepted')}</option>
<option data-tokens="pending" value="pending">{translator('pending')}</option>
<option data-tokens="rejected" value="rejected">{translator('rejected')}</option>
</select>
)
}
решение два. делегирование
см. решение. мы можем удалить обработчик событий из ввода и поместить его в родительский элемент, который также содержит другие входы, а также с помощью метода делегирования помощи мы можем снова использовать функции event.traget и mapPropsToHandler.
Ответ 6
Вот мой простой список избранных продуктов, реализованный с помощью реакции и редукции в машинописном тексте. Вы можете передать все аргументы в пользовательский обработчик и вернуть новый EventHandler
который принимает аргумент события origin. Это MouseEvent
в этом примере.
Изолированные функции сохраняют jsx cleaner и предотвращают нарушение правил литья. Например, jsx-no-bind
, jsx-no-lambda
.
import * as React from 'react';
import { DispatchProp, Dispatch, connect } from 'react-redux';
import { removeFavorite } from './../../actions/favorite';
interface ListItemProps {
prod: Product;
handleRemoveFavoriteClick: React.EventHandler<React.MouseEvent<HTMLButtonElement>>;
}
const ListItem: React.StatelessComponent<ListItemProps> = (props) => {
const {
prod,
handleRemoveFavoriteClick
} = props;
return (
<li>
<a href={prod.url} target="_blank">
{prod.title}
</a>
<button type="button" onClick={handleRemoveFavoriteClick}>×</button>
</li>
);
};
const handleRemoveFavoriteClick = (prod: Product, dispatch: Dispatch<any>) =>
(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
dispatch(removeFavorite(prod));
};
interface FavoriteListProps {
prods: Product[];
}
const FavoriteList: React.StatelessComponent<FavoriteListProps & DispatchProp<any>> = (props) => {
const {
prods,
dispatch
} = props;
return (
<ul>
{prods.map((prod, index) => <ListItem prod={prod} key={index} handleRemoveFavoriteClick={handleRemoveFavoriteClick(prod, dispatch)} />)}
</ul>
);
};
export default connect()(FavoriteList);
Вот фрагмент javascript, если вы не знакомы с машинописными текстами:
import * as React from 'react';
import { DispatchProp, Dispatch, connect } from 'react-redux';
import { removeFavorite } from './../../actions/favorite';
const ListItem = (props) => {
const {
prod,
handleRemoveFavoriteClick
} = props;
return (
<li>
<a href={prod.url} target="_blank">
{prod.title}
</a>
<button type="button" onClick={handleRemoveFavoriteClick}>×</button>
</li>
);
};
const handleRemoveFavoriteClick = (prod, dispatch) =>
(e) => {
e.preventDefault();
dispatch(removeFavorite(prod));
};
const FavoriteList = (props) => {
const {
prods,
dispatch
} = props;
return (
<ul>
{prods.map((prod, index) => <ListItem prod={prod} key={index} handleRemoveFavoriteClick={handleRemoveFavoriteClick(prod, dispatch)} />)}
</ul>
);
};
export default connect()(FavoriteList);
Ответ 7
Решение 1. Извлеките декларацию обработчика из компонента:
const myHandler = dispatch => e => dispatch(something(e))
const myComponent = ({ dispatch }) => (
<button onClick={myHandler(dispatch)}>Click Me</button>
)
Решение 2. Определите обработчик в родительском компоненте контейнера и передайте его в качестве опоры для компонента без гражданства.
Решение 3. Определите обработчик возле вашего компонента без гражданства, используя withHandlers
или withReducer
HOC, если вы используете recompose.
Ответ 8
Если у вас есть только несколько функций в ваших реквизитах, о которых вы беспокоитесь, вы можете это сделать:
let _dispatch = () => {};
const myHandler = (e) => _dispatch(something());
const myComponent = (props) => {
if (!_dispatch)
_dispatch = props.dispatch;
return (
<button onClick={myHandler}>Click Me</button>
);
}
Если это становится намного сложнее, я обычно возвращаюсь к классу.
Ответ 9
После постоянных усилий, наконец, работала для меня.
//..src/components/atoms/TestForm/index.tsx
import * as React from 'react';
export interface TestProps {
name?: string;
}
export interface TestFormProps {
model: TestProps;
inputTextType?:string;
errorCommon?: string;
onInputTextChange: React.ChangeEventHandler<HTMLInputElement>;
onInputButtonClick: React.MouseEventHandler<HTMLInputElement>;
onButtonClick: React.MouseEventHandler<HTMLButtonElement>;
}
export const TestForm: React.SFC<TestFormProps> = (props) => {
const {model, inputTextType, onInputTextChange, onInputButtonClick, onButtonClick, errorCommon} = props;
return (
<div>
<form>
<table>
<tr>
<td>
<div className="alert alert-danger">{errorCommon}</div>
</td>
</tr>
<tr>
<td>
<input
name="name"
type={inputTextType}
className="form-control"
value={model.name}
onChange={onInputTextChange}/>
</td>
</tr>
<tr>
<td>
<input
type="button"
className="form-control"
value="Input Button Click"
onClick={onInputButtonClick} />
</td>
</tr>
<tr>
<td>
<button
type="submit"
value='Click'
className="btn btn-primary"
onClick={onButtonClick}>
Button Click
</button>
</td>
</tr>
</table>
</form>
</div>
);
}
TestForm.defaultProps ={
inputTextType: "text"
}
//========================================================//
//..src/components/atoms/index.tsx
export * from './TestForm';
//========================================================//
//../src/components/testpage/index.tsx
import * as React from 'react';
import { TestForm, TestProps } from '@c2/component-library';
export default class extends React.Component<{}, {model: TestProps, errorCommon: string}> {
state = {
model: {
name: ""
},
errorCommon: ""
};
onInputTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const field = event.target.name;
const model = this.state.model;
model[field] = event.target.value;
return this.setState({model: model});
};
onInputButtonClick = (event: React.MouseEvent<HTMLInputElement>) => {
event.preventDefault();
if(this.validation())
{
alert("Hello "+ this.state.model.name + " from InputButtonClick.");
}
};
onButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if(this.validation())
{
alert("Hello "+ this.state.model.name+ " from ButtonClick.");
}
};
validation = () => {
this.setState({
errorCommon: ""
});
var errorCommonMsg = "";
if(!this.state.model.name || !this.state.model.name.length) {
errorCommonMsg+= "Name: *";
}
if(errorCommonMsg.length){
this.setState({ errorCommon: errorCommonMsg });
return false;
}
return true;
};
render() {
return (
<TestForm model={this.state.model}
onInputTextChange={this.onInputTextChange}
onInputButtonClick={this.onInputButtonClick}
onButtonClick={this.onButtonClick}
errorCommon={this.state.errorCommon} />
);
}
}
//========================================================//
//../src/components/home2/index.tsx
import * as React from 'react';
import TestPage from '../TestPage/index';
export const Home2: React.SFC = () => (
<div>
<h1>Home Page Test</h1>
<TestPage />
</div>
);
Примечание: для текстового поля атрибут привязки "name" и "имя свойства" (например: model.name) должны быть одинаковыми, тогда будет работать только "onInputTextChange". Логика "onInputTextChange" может быть изменена вашим кодом.
Ответ 10
Как насчет чего-то вроде этого:
let __memo = null;
const myHandler = props => {
if (!__memo) __memo = e => props.dispatch(something());
return __memo;
}
const myComponent = props => {
return (
<button onClick={myHandler(props)}>Click Me</button>
);
}
но на самом деле это излишне, если вам не нужно передавать onClick нижним/внутренним компонентам, как в примере.
Ответ 11
Как и для компонента без состояния, просто добавьте функцию -
function addName(){
console.log("name is added")
}
и он вызывается в onChange={addName}
как onChange={addName}