Пытаемся найти оптимальный способ создания обработчиков событий в компонентах React без сохранения состояния. Я мог бы сделать что-то вроде этого:
const myComponent = (props) => {
const myHandler = (e) => props.dispatch(something());
return (
<button onClick={myHandler}>Click Me</button>
);
}
Недостатком здесь является то, что каждый раз, когда этот компонент отображается, создается новая функция myHandler. Есть ли лучший способ создать обработчики событий в компонентах без состояния, которые по-прежнему могут обращаться к свойствам компонентов?
javascript
reactjs
аСтюартДизайн
источник
источник
Ответы:
Применение обработчиков к элементам в функциональных компонентах обычно должно выглядеть так:
const f = props => <button onClick={props.onClick}></button>
Если вам нужно сделать что-то гораздо более сложное, это признак того, что либо а) компонент не должен быть без состояния (используйте класс или хуки), либо б) вы должны создавать обработчик во внешнем компоненте-контейнере с сохранением состояния.
В стороне и немного подрывая мою первую мысль, если компонент не находится в особенно интенсивно повторно отрисовываемой части приложения, нет необходимости беспокоиться о создании стрелочных функций в
render()
.источник
useCallback(() => {}, [])
илиthis.onClick = this.onClick.bind(this)
, тогда компонент будет получать одну и ту же ссылку на обработчик при каждом рендеринге, что может помочь с использованиемReact.memo
илиshouldComponentUpdate
(но это актуально только для интенсивно повторно отрисованных многочисленных / сложных компонентов).Используя новую функцию перехватчиков React, это может выглядеть примерно так:
const HelloWorld = ({ dispatch }) => { const handleClick = useCallback(() => { dispatch(something()) }) return <button onClick={handleClick} /> }
useCallback
создает мемоизированную функцию, то есть новая функция не будет регенерироваться в каждом цикле рендеринга.https://reactjs.org/docs/hooks-reference.html#usecallback
Однако это все еще на стадии предложения.
источник
useCallback
- и вы все еще генерируете новую стрелочную функцию при каждом рендеринге (переданный аргументuseCallback
).useCallback
полезен только при передаче обратных вызовов оптимизированным дочерним компонентам, которые полагаются на равенство ссылок для предотвращения ненужных отрисовок. Если вы просто применяете обратный вызов к элементу HTML, например кнопке, не используйтеuseCallback
.Как насчет этого:
const myHandler = (e,props) => props.dispatch(something()); const myComponent = (props) => { return ( <button onClick={(e) => myHandler(e,props)}>Click Me</button> ); }
источник
<button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>
? Я имею в виду, не оборачивайте его в функцию myHandler.Если обработчик полагается на изменяющиеся свойства, вам придется каждый раз создавать обработчик, поскольку у вас нет экземпляра с отслеживанием состояния, на котором он мог бы кэшироваться. Другой альтернативой, которая может сработать, может быть мемоизация обработчика на основе входных свойств.
Пара вариантов реализации lodash._memoize R.memoize fast-memoize
источник
решение 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, например
а не 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.
источник
Вот мой простой список любимых продуктов, реализованный с использованием реакции и сокращения в машинописном тексте. Вы можете передать все необходимые аргументы в настраиваемый обработчик и вернуть новый,
EventHandler
который принимает аргумент исходного события. ЭтоMouseEvent
в этом примере.Изолированные функции сохраняют jsx чище и предотвращают нарушение нескольких правил линтинга. Такие , как
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);
источник
Как и для компонента без состояния, просто добавьте функцию -
function addName(){ console.log("name is added") }
и он называется в возврате как
onChange={addName}
источник
Если в вашем реквизите есть только несколько функций, которые вас беспокоят, вы можете сделать это:
let _dispatch = () => {}; const myHandler = (e) => _dispatch(something()); const myComponent = (props) => { if (!_dispatch) _dispatch = props.dispatch; return ( <button onClick={myHandler}>Click Me</button> ); }
Если все становится намного сложнее, я обычно просто возвращаюсь к компоненту класса.
источник
После постоянных усилий у меня наконец-то сработало.
//..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 может быть изменена вашим кодом.
источник
Как насчет чего-то вроде этого:
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 нижним / внутренним компонентам, как в примере.
источник