Tratando de encontrar una forma óptima de crear controladores de eventos en los componentes sin estado de React. Podría hacer algo como esto:
const myComponent = (props) => {
const myHandler = (e) => props.dispatch(something());
return (
<button onClick={myHandler}>Click Me</button>
);
}
El inconveniente aquí es que cada vez que se procesa este componente, se crea una nueva función "myHandler". ¿Existe una mejor manera de crear controladores de eventos en componentes sin estado que aún pueden acceder a las propiedades del componente?
javascript
reactjs
aStewartDesign
fuente
fuente
Respuestas:
La aplicación de controladores a elementos en componentes de funciones generalmente debería verse así:
const f = props => <button onClick={props.onClick}></button>
Si necesita hacer algo mucho más complejo, es una señal de que a) el componente no debe ser sin estado (use una clase o ganchos), ob) debe crear el controlador en un componente contenedor externo con estado.
Como acotación al margen, y socavando ligeramente mi primer punto, a menos que el componente esté en una parte de la aplicación que se haya renderizado de manera particularmente intensa, no hay necesidad de preocuparse por crear funciones de flecha en
render()
.fuente
useCallback(() => {}, [])
othis.onClick = this.onClick.bind(this)
, entonces el componente también obtendría la misma referencia de controlador en cada renderizado, lo que podría ayudar con el uso deReact.memo
oshouldComponentUpdate
(pero esto solo es relevante con componentes numerosos / complejos re-renderizados intensivamente).Usando la nueva función React hooks, podría verse así:
const HelloWorld = ({ dispatch }) => { const handleClick = useCallback(() => { dispatch(something()) }) return <button onClick={handleClick} /> }
useCallback
crea una función memorizada, lo que significa que una nueva función no se regenerará en cada ciclo de renderizado.https://reactjs.org/docs/hooks-reference.html#usecallback
Sin embargo, esto aún se encuentra en la etapa de propuesta.
fuente
useCallback
, y todavía está generando una nueva función de flecha en cada render (se pasa el argumento auseCallback
).useCallback
solo es útil cuando se pasan devoluciones de llamada a componentes secundarios optimizados que dependen de la igualdad de referencias para evitar representaciones innecesarias. Si solo está aplicando la devolución de llamada a un elemento HTML como un botón, no lo useuseCallback
.¿Qué tal de esta manera?
const myHandler = (e,props) => props.dispatch(something()); const myComponent = (props) => { return ( <button onClick={(e) => myHandler(e,props)}>Click Me</button> ); }
fuente
<button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>
? Quiero decir, no lo envuelva en una función myHandler.Si el controlador se basa en propiedades que cambian, tendrá que crear el controlador cada vez, ya que carece de una instancia con estado en la que almacenarlo en caché. Otra alternativa que puede funcionar sería memorizar el controlador en función de los accesorios de entrada.
Opciones de implementación de pareja lodash._memoize R.memoize fast-memoize
fuente
solución uno mapPropsToHandler y event.target.
Las funciones son objetos en js, por lo que es posible adjuntarles propiedades.
function onChange() { console.log(onChange.list) } function Input(props) { onChange.list = props.list; return <input onChange={onChange}/> }
esta función solo vincula una vez una propiedad a una función.
export function mapPropsToHandler(handler, props) { for (let property in props) { if (props.hasOwnProperty(property)) { if(!handler.hasOwnProperty(property)) { handler[property] = props[property]; } } } }
Recibo mis accesorios así.
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) }
este ejemplo combinó tanto event.target como mapPropsToHandler. es mejor adjuntar funciones a los controladores solo, no números o cadenas. El número y las cadenas se pueden pasar con la ayuda del atributo DOM como
en lugar de 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> ) }
solución dos. delegación de eventos
ver la solución uno. podemos eliminar el controlador de eventos de la entrada y ponerlo en su padre que también contiene otras entradas y, mediante la técnica de delegación de ayuda, podemos usar la función event.traget y mapPropsToHandler nuevamente.
fuente
Aquí está mi sencilla lista de productos favoritos implementada con react y redux escribiendo en mecanografiado. Puede pasar todos los argumentos que necesita en el controlador personalizado y devolver un
EventHandler
argumento nuevo que acepte el evento de origen. EstáMouseEvent
en este ejemplo.Las funciones aisladas mantienen jsx más limpio y evitan que se rompan varias reglas de pelusa. Tales como
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);
Aquí está el fragmento de JavaScript si no está familiarizado con el mecanografiado:
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);
fuente
Al igual que para un componente sin estado, simplemente agregue una función:
function addName(){ console.log("name is added") }
y se llama a la vuelta como
onChange={addName}
fuente
Si solo tiene algunas funciones en sus accesorios que le preocupan, puede hacer esto:
let _dispatch = () => {}; const myHandler = (e) => _dispatch(something()); const myComponent = (props) => { if (!_dispatch) _dispatch = props.dispatch; return ( <button onClick={myHandler}>Click Me</button> ); }
Si se vuelve mucho más complicado, normalmente vuelvo a tener un componente de clase.
fuente
Después de un esfuerzo continuo finalmente funcionó para mí.
//..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> );
Nota: para el cuadro de texto archivado, el atributo "nombre" y el "nombre de la propiedad" (por ejemplo: model.name) deben ser iguales, entonces solo funcionará "onInputTextChange". La lógica "onInputTextChange" puede ser modificada por su código.
fuente
Qué tal algo como esto:
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> ); }
pero realmente esto es excesivo si no necesita pasar el onClick a componentes inferiores / internos, como en el ejemplo.
fuente