React Stateless 구성 요소의 이벤트 처리기


82

React stateless 구성 요소에서 이벤트 핸들러를 만드는 최적의 방법을 찾으려고합니다. 다음과 같이 할 수 있습니다.

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

여기서 단점은이 컴포넌트가 렌더링 될 때마다 새로운 "myHandler"함수가 생성된다는 것입니다. 구성 요소 속성에 계속 액세스 할 수있는 상태 비 저장 구성 요소에서 이벤트 처리기를 만드는 더 좋은 방법이 있습니까?


useCallback-const memoizedCallback = useCallback (() => {doSomething (a, b);}, [a, b],); 메모 된 콜백을 반환합니다.
Shaik Md N Rasool

답변:


61

함수 구성 요소의 요소에 핸들러를 적용하는 것은 일반적으로 다음과 같아야합니다.

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

훨씬 더 복잡한 작업을 수행해야하는 경우 a) 구성 요소가 상태 비 저장 (클래스 또는 후크 사용)이 아니어야하거나 b) 외부 상태 저장 컨테이너 구성 요소에 핸들러를 생성해야한다는 신호입니다.

제쳐두고, 내 첫 번째 요점을 약간 훼손하는 것으로, 구성 요소가 앱의 특히 집중적으로 다시 렌더링되는 부분에 있지 않는 한 .NET에서 화살표 함수를 만드는 것에 대해 걱정할 필요가 없습니다 render().


2
상태 비 저장 구성 요소가 렌더링 될 때마다 함수 생성을 어떻게 방지합니까?
zero_cool 19 년

1
위의 코드 예제는 참조에 의해 적용되는 핸들러를 보여줍니다. 해당 컴포넌트의 렌더링시 새로운 핸들러 함수가 생성되지 않습니다. 외부 구성 요소를 사용하여 핸들러를 만든 경우 useCallback(() => {}, [])또는 this.onClick = this.onClick.bind(this)다음 구성 요소가 각각의 사용에 도움을 줄 수있는 너무 렌더링 같은 핸들러 참조 얻는 것 React.memo또는 shouldComponentUpdate(하지만이 집중적으로 다시 렌더링 수많은 / 복잡한 구성 요소에만 관련이 있습니다).
제드 리차드

46

새로운 React hooks 기능을 사용하면 다음과 같이 보일 수 있습니다.

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

useCallback 메모 함수를 생성합니다. 즉, 각 렌더링주기에서 새 함수가 재생성되지 않습니다.

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

그러나 이것은 아직 제안 단계에 있습니다.


7
React Hooks는 React 16.8에서 출시되었으며 이제 React의 공식 부분입니다. 따라서이 답변은 완벽하게 작동합니다.
cutemachine

3
eslint-plugin-react-hooks 패키지의 일부로 권장되는 전체 딥 규칙에 "React Hook useCallback은 하나의 인수 만 사용하여 호출 할 때 아무 작업도 수행하지 않습니다."라고되어 있습니다.이 경우 빈 배열은 다음과 같아야합니다. 두 번째 인수로 전달됩니다.
olegzhermal 2019

1
위의 예에서는 useCallback- 를 사용하여 얻는 효율성이 없으며 렌더링 할 때마다 새 화살표 함수를 생성하고 있습니다 (에 전달 된 인수 useCallback). useCallback불필요한 렌더링을 방지하기 위해 참조 동등성에 의존하는 최적화 된 하위 구성 요소에 콜백을 전달할 때만 유용합니다. 버튼과 같은 HTML 요소에 콜백을 적용하는 경우 useCallback.
Jed Richards

1
새로운 화살표 기능은 각 렌더링에 생성되지만 @JedRichards는 DOM은 시간을 절약해야하는 업데이트 할 필요가 없습니다
허먼

3
@herman 전혀 차이가 없습니다 (작은 성능 저하를 제외하면). 그래서 우리가 주석을 달고있는이 답변이 약간 의심스러운 이유입니다. :) 종속성 배열이없는 모든 후크는 모든 업데이트 후에 실행됩니다. useEffect 문서 시작 부분). 앞서 언급했듯이 집중적으로 / 비용이 많이 드는 하위 구성 요소에 전달할 계획 인 콜백 함수에 대한 안정적인 / 기억 된 참조를 원할 경우에만 useCallback을 사용하고 싶을 것이며 참조 평등이 중요합니다. 다른 용도는 매번 렌더링 할 때 새 함수를 만듭니다.
Jed Richards

16

이 방법은 어떻습니까?

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

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

14
좋은 생각! 슬프게도,이 모든 호출을 렌더링 새로운 기능을 작성하는 문제를 주위하지 않습니다 github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/...
aStewartDesign

@aStewart이 문제에 대한 해결책이나 업데이트를 설계 하시겠습니까? 저도 같은 문제에 직면하고 있습니다 때문에, 그것을 듣고 정말 다행

4
myHandler의 구현이있는 부모 정규 구성 요소가 있고 하위 구성 요소에 전달하기 만하면됩니다.
Raja Rao

나는 어떤 사람이 어떤 것을 쿨를 발견하는 경우, 지금까지이 일 (2018 년 7 월), PLS 알려 것이 더 좋은 방법이없는 것 같아요
a_m_dev

왜 안돼 <button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>? 내 말은, myHandler func로 감싸지 마십시오.
Simon Franzen

6

처리기가 변경되는 속성에 의존하는 경우 캐시 할 상태 저장 인스턴스가 없기 때문에 처리기를 매번 만들어야합니다. 작동 할 수있는 또 다른 대안은 입력 소품을 기반으로 핸들러를 메모하는 것입니다.

몇 가지 구현 옵션 lodash._memoize R.memoize fast-memoize


4

해결책 하나의 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>
    )
}

솔루션 2입니다. 이벤트 위임

솔루션 1을 참조하십시오. 입력에서 이벤트 핸들러를 제거하고 다른 입력도 보유하는 상위에 놓을 수 있으며 도움말 위임 기술을 통해 event.traget 및 mapPropsToHandler 함수를 다시 사용할 수 있습니다.


나쁜 연습! 함수는 그 목적에 부합해야하며, 속성을 유지하지 않도록 일부 매개 변수에 로직을 실행하는 것을 의미합니다. 자바 스크립트가 동일한 작업을 수행하는 많은 창의적인 방법을 허용한다고해서 작동하는 모든 것을 사용하도록 허용해야하는 것은 아닙니다.
BeyondTheSea

4

다음은 typescript에서 react 및 redux 작성으로 구현 된 내가 가장 좋아하는 제품 목록입니다. 커스텀 핸들러에서 필요한 모든 인수를 전달하고 EventHandler원본 이벤트 인수를 허용 하는 new 를 반환 할 수 있습니다 . 이것의MouseEvent 예에서.

격리 된 함수는 jsx를 더 깔끔하게 유지하고 여러 linting 규칙을 위반하지 않도록합니다. 이와 같이 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);

typescript에 익숙하지 않은 경우 다음은 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}>&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>
);

참고 : 텍스트 상자 필드 바인딩의 경우 "name"속성 및 "property name"(예 : 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>
  );
}

그러나 예제에서와 같이 onClick을 하위 / 내부 구성 요소에 전달할 필요가 없다면 이것은 과잉입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.