रिएक्ट स्टेटलेस कंपोनेंट्स में इवेंट हैंडलर


84

रिएक्ट स्टेटलेस घटकों में ईवेंट हैंडलर बनाने के लिए एक इष्टतम तरीका जानने की कोशिश कर रहा है। मैं ऐसा कुछ कर सकता था:

const myComponent = (props) => {
    const myHandler = (e) => props.dispatch(something());
    return (
        <button onClick={myHandler}>Click Me</button>
    );
}

यहाँ दोष यह है कि हर बार इस घटक का प्रतिपादन किया जाता है, एक नया "myHandler" फ़ंक्शन बनाया जाता है। क्या स्टेटलेस घटकों में ईवेंट हैंडलर बनाने का एक बेहतर तरीका है जो अभी भी घटक गुणों तक पहुंच सकता है?


useCallback - कॉन्स्ट मेमॉइज्ड कॉलबैक = useCallback () => {doSomething (b, b);}, [a, b]); एक ज्ञापन कॉलबैक लौटाता है।
शैक एमडी एन रसूल

जवाबों:


62

समारोह घटकों में तत्वों को हैंडलर लागू करना आम तौर पर इस तरह दिखना चाहिए:

const f = props => <button onClick={props.onClick}></button>

यदि आपको कुछ और अधिक जटिल करने की आवश्यकता है, तो यह संकेत है कि या तो एक) घटक स्टेटलेस नहीं होना चाहिए (एक वर्ग, या हुक का उपयोग करें), या बी) आपको बाहरी स्टेटफुल कंटेनर घटक में हैंडलर बनाना चाहिए।

एक तरफ के रूप में, और मेरे पहले बिंदु को थोड़ा कम करके, जब तक कि घटक ऐप के विशेष रूप से गहन रूप से फिर से प्रस्तुत किए गए हिस्से में न हो, तो तीर के कार्यों को बनाने के बारे में चिंता करने की कोई आवश्यकता नहीं है render()


2
हर बार स्टेटलेस कंपोनेंट को प्रस्तुत करने से यह कैसे बनता है?
जीरो_कोल

1
ऊपर दिए गए कोड का उदाहरण केवल एक हैंडलर को संदर्भ द्वारा लागू किया जा रहा है, उस घटक के रेंडर पर कोई नया हैंडलर फ़ंक्शन नहीं बनाया गया है। बाहरी घटक का उपयोग हैंडलर बनाई थी, तो useCallback(() => {}, [])या this.onClick = this.onClick.bind(this), तो घटक ही हैंडलर संदर्भ प्रत्येक भी प्रस्तुत करना जो उपयोग करने के साथ मदद कर सकता है हो रही हो जाएगा React.memoया shouldComponentUpdate(लेकिन यह गहराई फिर से प्रदान की गई कई / जटिल घटकों के साथ ही प्रासंगिक है)।
जेड रिचर्ड्स

48

नए रिएक्ट हुक की सुविधा का उपयोग करके यह कुछ इस तरह दिख सकता है:

const HelloWorld = ({ dispatch }) => {
  const handleClick = useCallback(() => {
    dispatch(something())
  })
  return <button onClick={handleClick} />
}

useCallback एक ज्ञापन फ़ंक्शन बनाता है, जिसका अर्थ है कि प्रत्येक रेंडर चक्र पर एक नया फ़ंक्शन पुनर्जीवित नहीं किया जाएगा।

https://reactjs.org/docs/hooks-reference.html#usecallback

हालांकि, यह अभी भी प्रस्ताव के स्तर पर है।


7
रिएक्ट 16.8 में रिएक्ट हुक जारी किए गए हैं और अब रिएक्ट का आधिकारिक हिस्सा हैं। तो यह जवाब पूरी तरह से काम करता है।
कटेमाचिन

3
बस ध्यान दें कि एस्केल्ट-प्लगइन-रिएक्शन-हुक पैकेज के हिस्से के रूप में अनुशंसित एग्ज़ॉस्ट-डिप्स नियम कहता है: "रिएक्ट हुक यूज़कॉलबैक केवल एक तर्क के साथ कॉल करने पर कुछ नहीं करता है।", इसलिए, हाँ, इस मामले में एक खाली सरणी होनी चाहिए। दूसरे तर्क के रूप में पारित किया गया।
ऑलीगर्मल

1
आपके उदाहरण में ऊपर कोई दक्षता नहीं मिली है useCallback- और आप अभी भी एक नया एरो फंक्शन पैदा कर रहे हैं, जो हर रेंडर (arg pass useCallback) को दिया जाता है । useCallbackकेवल उपयोगी है जब अनुकूलित बाल घटकों को कॉलबैक पारित करना जो अनावश्यक रेंडरर्स को रोकने के लिए संदर्भ समानता पर भरोसा करते हैं। यदि आप कॉलबैक को किसी HTML तत्व जैसे बटन पर लागू कर रहे हैं तो उपयोग न करें useCallback
जेड रिचर्ड्स

1
@JedRichards हालांकि प्रत्येक रेंडर पर एक नया एरो फंक्शन बनाया जाता है, इसके लिए DOM को अपडेट करने की आवश्यकता नहीं होती है जिससे समय की बचत होनी चाहिए
herman

3
@ हर्मन में कोई अंतर नहीं है (एक छोटे से प्रदर्शन के दंड से अलग), यही वजह है कि हम जिस टिप्पणी के तहत टिप्पणी कर रहे हैं वह थोड़ा संदेहास्पद है :) किसी भी हुक जिसमें निर्भरता नहीं होती है वह हर अपडेट के बाद चलेगा (यह चर्चा की गई है उपयोग की शुरुआत के पास डॉक्स डॉक्स)। जैसा कि मैंने उल्लेख किया है, यदि आप एक कॉलबैक फ़ंक्शन के लिए एक स्थिर / ज्ञापन रेफरी चाहते हैं, तो आप बहुत अधिक उपयोग कर सकते हैं। कोई अन्य उपयोग, बस हर बार रेंडर करने में एक नया फ़ंक्शन बनाएं।
जेड रिचर्ड्स

16

इस तरह से कैसे:

const myHandler = (e,props) => props.dispatch(something());

const myComponent = (props) => {
 return (
    <button onClick={(e) => myHandler(e,props)}>Click Me</button>
  );
}

15
अच्छा विचार! अफसोस की बात है, यह हर रेंडर कॉल को एक नया फ़ंक्शन बनाने के मुद्दे के आसपास नहीं मिलता है: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/…
aStewewDesign

@aStewartDesign इस मुद्दे के लिए कोई समाधान या अद्यतन? वास्तव में यह सुनकर खुशी हुई, क्योंकि मैं उसी समस्या का सामना कर रहा हूं
किम

4
माता-पिता के पास एक नियमित घटक है, जिसमें myHandler का कार्यान्वयन है और फिर इसे उप-घटक के पास भेजते हैं
राजा राव

मुझे लगता है कि कोई बेहतर तरीका नहीं है कि यह एक अब तक (जुलाई 2018), अगर किसी को कोई चीज़ अच्छी
लगती है

क्यों नहीं <button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>? मेरा मतलब है, एक myHandler दुर्गंध में इसे लपेटो मत।
शमौन फ्रेंजन

6

यदि हैंडलर उन गुणों पर निर्भर करता है जो बदलते हैं, तो आपको हर बार हैंडलर बनाना होगा क्योंकि आपके पास एक स्टेटफुल इंस्टेंस की कमी है, जिस पर उसे कैश करना है। एक अन्य विकल्प जो काम कर सकता है वह इनपुट प्रॉपर के आधार पर हैंडलर को याद करना होगा।

युगल कार्यान्वयन विकल्प दर्ज करें। Rememize R.memoize फास्ट-मेमोइज़ करें


4

समाधान एक मैपप्रॉप्सटॉन्डलर और ईवेंट। टारगेट।

कार्य 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 को संयोजित किया। संचालकों को केवल संख्या या तार नहीं करने के लिए फ़ंक्शंस संलग्न करना बेहतर है। डोम विशेषता की मदद से संख्या और तारों को पास किया जा सकता है

<select data-id={id}/>

बजाय मैपप्रॉप्सटहैंडलर के बजाय

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>
    )
}

समाधान दो। ईवेंट प्रतिनिधिमंडल

समाधान एक देखें। हम ईवेंट हैंडलर को इनपुट से हटा सकते हैं और इसे उसके अभिभावक के पास रख सकते हैं जो अन्य इनपुट भी रखता है और डेलिगेशन तकनीक की मदद से हम ईवेंट.टैगेट और मैपप्रॉप्सटहैंडलर फ़ंक्शन का फिर से उपयोग कर सकते हैं।


बुरा अभ्यास! एक फ़ंक्शन को केवल अपने उद्देश्य की सेवा करनी चाहिए, यह कुछ मापदंडों पर तर्क को निष्पादित करने के लिए है गुणों को न रखने के लिए, सिर्फ इसलिए कि जावास्क्रिप्ट बहुत सारे रचनात्मक तरीके से अनुमति देता है एक ही काम करने का मतलब यह नहीं है कि आपको अपने आप को जो कुछ भी काम करने का उपयोग करना चाहिए।
BeyondTheSea

4

यहां मेरी सरल पसंदीदा उत्पादों की सूची है जिसे टाइपस्क्रिप्ट में प्रतिक्रिया और रिडक्स लेखन के साथ लागू किया गया है। आप कस्टम हैंडलर में आवश्यक सभी तर्क पास कर सकते हैं और एक नया रिटर्न दे सकते हैं 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}>&times;</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);

यहाँ जावास्क्रिप्ट स्निपेट है यदि आप टाइपस्क्रिप्ट से परिचित नहीं हैं:

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}>&times;</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);

2

जैसे एक स्टेटलेस कंपोनेंट के लिए, बस एक फंक्शन जोड़ें -

function addName(){
   console.log("name is added")
}

और इसे रिटर्न में कहा जाता है onChange={addName}


1

यदि आपके पास केवल कुछ ही कार्य हैं जो आपको इस बात की चिंता करते हैं कि आप ऐसा कर सकते हैं:

let _dispatch = () => {};

const myHandler = (e) => _dispatch(something());

const myComponent = (props) => {
    if (!_dispatch)
        _dispatch = props.dispatch;

    return (
        <button onClick={myHandler}>Click Me</button>
    );
}

यदि यह बहुत अधिक जटिल हो जाता है, तो मैं आमतौर पर एक वर्ग घटक होने पर वापस जाता हूं।


1

लगातार प्रयास के बाद आखिरकार मेरे लिए काम किया।

//..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>
);

ध्यान दें: पाठ बॉक्स के लिए बाध्यकारी "नाम" विशेषता और "संपत्ति का नाम" दर्ज किया गया (उदाहरण के लिए: model.name) एक ही होना चाहिए तभी "onInputTextChange" काम करेगा। "onInputTextChange" तर्क को आपके कोड द्वारा संशोधित किया जा सकता है।


0

इस जैसे किसी और के बारे में क्या राय है:

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>
  );
}

लेकिन वास्तव में यह ओवरकिल है अगर आपको उदाहरण के लिए, निचले / आंतरिक घटकों पर ऑनक्लिक पास करने की आवश्यकता नहीं है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.