Der Versuch, einen optimalen Weg zum Erstellen von Ereignishandlern in reaktionslosen Komponenten von React zu finden. Ich könnte so etwas machen:
const myComponent = (props) => {
const myHandler = (e) => props.dispatch(something());
return (
<button onClick={myHandler}>Click Me</button>
);
}
Der Nachteil hierbei ist, dass jedes Mal, wenn diese Komponente gerendert wird, eine neue "myHandler" -Funktion erstellt wird. Gibt es eine bessere Möglichkeit, Ereignishandler in zustandslosen Komponenten zu erstellen, die weiterhin auf die Komponenteneigenschaften zugreifen können?
javascript
reactjs
aStewartDesign
quelle
quelle
Antworten:
Das Anwenden von Handlern auf Elemente in Funktionskomponenten sollte im Allgemeinen folgendermaßen aussehen:
const f = props => <button onClick={props.onClick}></button>
Wenn Sie etwas viel Komplexeres tun müssen, ist dies ein Zeichen dafür, dass entweder a) die Komponente nicht zustandslos sein sollte (verwenden Sie eine Klasse oder Hooks) oder b) Sie den Handler in einer äußeren Stateful-Container-Komponente erstellen sollten.
Abgesehen davon und wenn Sie meinen ersten Punkt leicht untergraben, müssen Sie sich keine Gedanken über das Erstellen von Pfeilfunktionen machen, es sei denn, die Komponente befindet sich in einem besonders intensiv neu gerenderten Teil der App
render()
.quelle
useCallback(() => {}, [])
oderthis.onClick = this.onClick.bind(this)
erstellen würde, würde die Komponente bei jedem Rendern dieselbe Handlerreferenz erhalten, was bei der Verwendung vonReact.memo
oder hilfreich sein könnteshouldComponentUpdate
(dies ist jedoch nur bei intensiv neu gerenderten zahlreichen / komplexen Komponenten relevant).Mit der neuen Funktion "React Hooks" könnte dies ungefähr so aussehen:
const HelloWorld = ({ dispatch }) => { const handleClick = useCallback(() => { dispatch(something()) }) return <button onClick={handleClick} /> }
useCallback
Erstellt eine gespeicherte Funktion, dh, eine neue Funktion wird nicht bei jedem Renderzyklus neu generiert.https://reactjs.org/docs/hooks-reference.html#usecallback
Dies befindet sich jedoch noch im Vorschlagsstadium.
quelle
useCallback
- und Sie generieren immer noch bei jedem Rendern eine neue Pfeilfunktion (das an übergebene ArgumentuseCallback
).useCallback
Dies ist nur nützlich, wenn Rückrufe an optimierte untergeordnete Komponenten übergeben werden, die auf Referenzgleichheit beruhen, um unnötiges Rendern zu vermeiden. Wenn Sie den Rückruf nur auf ein HTML-Element wie die Schaltfläche anwenden, verwenden Sie ihn nichtuseCallback
.Wie wäre es auf diese Weise:
const myHandler = (e,props) => props.dispatch(something()); const myComponent = (props) => { return ( <button onClick={(e) => myHandler(e,props)}>Click Me</button> ); }
quelle
<button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>
? Ich meine, wickeln Sie es nicht in eine myHandler-Funk ein.Wenn der Handler auf sich ändernde Eigenschaften angewiesen ist, müssen Sie den Handler jedes Mal erstellen, da Ihnen eine statusbehaftete Instanz fehlt, auf der er zwischengespeichert werden kann. Eine andere Alternative, die funktionieren könnte, wäre, den Handler basierend auf den Eingabestützen auswendig zu lernen.
Paar Implementierungsoptionen lodash._memoize R.memoize fast-memoize
quelle
Lösung ein mapPropsToHandler und event.target.
Funktionen sind Objekte in js, daher ist es möglich, ihnen Eigenschaften zuzuweisen.
function onChange() { console.log(onChange.list) } function Input(props) { onChange.list = props.list; return <input onChange={onChange}/> }
Diese Funktion bindet eine Eigenschaft nur einmal an eine Funktion.
export function mapPropsToHandler(handler, props) { for (let property in props) { if (props.hasOwnProperty(property)) { if(!handler.hasOwnProperty(property)) { handler[property] = props[property]; } } } }
Ich bekomme meine Requisiten einfach so.
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) }
In diesem Beispiel wurden sowohl event.target als auch mapPropsToHandler kombiniert. Es ist besser, Funktionen nur an Handler anzuhängen, nicht an Zahlen oder Zeichenfolgen. Zahl und Zeichenfolgen können mit Hilfe von DOM-Attributen wie übergeben werden
eher als 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> ) }
Lösung zwei. Veranstaltungsdelegation
siehe Lösung eins. Wir können den Ereignishandler aus der Eingabe entfernen und ihn seinem übergeordneten Element zuweisen, das auch andere Eingaben enthält, und mithilfe der Hilfedelegationstechnik können wir die Funktionen event.traget und mapPropsToHandler erneut verwenden.
quelle
Hier ist meine einfache Liste der Lieblingsprodukte, die mit React- und Redux-Schrift in Typoskript implementiert wurde. Sie können alle erforderlichen Argumente im benutzerdefinierten Handler übergeben und ein neues
EventHandler
Argument zurückgeben, das das Ursprungsereignisargument akzeptiert. Es istMouseEvent
in diesem Beispiel.Isolierte Funktionen halten jsx sauberer und verhindern, dass mehrere Flusenregeln verletzt werden. Wie
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);
Hier ist das Javascript-Snippet, wenn Sie mit Typoskript nicht vertraut sind:
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);
quelle
Fügen Sie wie bei einer zustandslosen Komponente einfach eine Funktion hinzu -
function addName(){ console.log("name is added") }
und es heißt in der Rückgabe als
onChange={addName}
quelle
Wenn Sie nur wenige Funktionen in Ihren Requisiten haben, über die Sie sich Sorgen machen, können Sie dies tun:
let _dispatch = () => {}; const myHandler = (e) => _dispatch(something()); const myComponent = (props) => { if (!_dispatch) _dispatch = props.dispatch; return ( <button onClick={myHandler}>Click Me</button> ); }
Wenn es viel komplizierter wird, kehre ich normalerweise zu einer Klassenkomponente zurück.
quelle
Nach ständiger Anstrengung hat es endlich für mich geklappt.
//..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> );
Hinweis: Für Textfelder, die die Bindung "Name" -Attribut und "Eigenschaftsname" (z. B. model.name) enthalten, sollten nur "onInputTextChange" funktionieren. Die Logik "onInputTextChange" kann durch Ihren Code geändert werden.
quelle
Wie wäre es mit so etwas:
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> ); }
Aber das ist wirklich übertrieben, wenn Sie den onClick nicht wie im Beispiel an niedrigere / innere Komponenten übergeben müssen.
quelle