React Stateless Bileşenlerde Olay İşleyicileri


84

React stateless bileşenlerinde olay işleyicileri oluşturmanın en uygun yolunu bulmaya çalışıyorum. Bunun gibi bir şey yapabilirim:

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

Buradaki dezavantaj, bu bileşen her işlendiğinde yeni bir "myHandler" işlevinin yaratılmasıdır. Durum bilgisi olmayan bileşenlerde, bileşen özelliklerine yine de erişebilen olay işleyicileri oluşturmanın daha iyi bir yolu var mı?


useCallback - const memoizedCallback = useCallback (() => {doSomething (a, b);}, [a, b],); Hatırlatılmış bir geri aramayı döndürür.
Shaik Md N Rasool

Yanıtlar:


62

İşleyicileri işlev bileşenlerindeki öğelere uygulamak genellikle şu şekilde görünmelidir:

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

Çok daha karmaşık bir şey yapmanız gerekiyorsa, bu, ya a) bileşenin durumsuz olmaması gerektiğinin (bir sınıf veya kancalar kullanın) ya da b) işleyiciyi bir dış durum bilgisi olan konteyner bileşeninde yaratmanız gerektiğinin bir işaretidir.

Bir kenara ve ilk noktamı biraz zayıflatarak, bileşen uygulamanın özellikle yoğun bir şekilde yeniden oluşturulmuş bir bölümünde olmadığı sürece ok işlevlerini oluşturma konusunda endişelenmenize gerek yok render().


2
bu, durum bilgisiz bileşen her işlendiğinde bir işlev oluşturmaktan nasıl kaçınır?
zero_cool

1
Yukarıdaki kod örneği, referansla uygulanan bir işleyiciyi gösterir, bu bileşenin oluşturulmasında yeni bir eylemci işlevi oluşturulmaz. Dış bileşen useCallback(() => {}, [])veya kullanarak işleyiciyi oluşturduysa, this.onClick = this.onClick.bind(this)bileşen her renderda aynı işleyici referansını alıyor olacak ve bu React.memoda veya kullanımına yardımcı olabilir shouldComponentUpdate(ancak bu yalnızca yoğun bir şekilde yeniden oluşturulmuş çok sayıda / karmaşık bileşenle ilgilidir).
Jed Richards

48

Yeni React kancaları özelliğini kullanarak aşağıdaki gibi görünebilir:

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

useCallback hafızaya alınmış bir işlev oluşturur, bu da her oluşturma döngüsünde yeni bir işlevin yeniden oluşturulmayacağı anlamına gelir.

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

Ancak bu hala teklif aşamasındadır.


7
React Hooks, React 16.8'de yayınlandı ve artık React'in resmi bir parçası. Yani bu cevap mükemmel çalışıyor.
cutemachine

3
Eslint-plugin-react-hooks paketinin bir parçası olarak önerilen geniş kapsamlı-deps kuralının şunu söylediğine dikkat etmek gerekirse: "React Hook useCallback, yalnızca bir bağımsız değişkenle çağrıldığında hiçbir şey yapmaz.", Bu nedenle, evet, bu durumda boş bir dizi olmalıdır. ikinci bir argüman olarak geçti.
olegzhermal

1
Yukarıdaki örneğinizde, kullanımda verimlilik elde edilmez useCallback- ve yine de her işlemede yeni bir ok işlevi oluşturuyorsunuz (argüman iletildi useCallback). useCallbackyalnızca geri aramaları, gereksiz işlemeleri önlemek için referans eşitliğine dayanan optimize edilmiş alt bileşenlere geçirirken kullanışlıdır. Geri aramayı yalnızca düğme gibi bir HTML öğesine uyguluyorsanız, kullanmayın useCallback.
Jed Richards

1
@JedRichards, her işlemede yeni bir ok işlevi oluşturulmasına rağmen, DOM'un güncellenmesine gerek yoktur, bu da zamandan tasarruf sağlar
herman

3
@herman Hiç bir fark yok (küçük bir performans cezası dışında), bu yüzden yorum yaptığımız bu cevap biraz şüpheli :) Bağımlılık dizisi olmayan herhangi bir kanca her güncellemeden sonra çalışacaktır (tartışılmıştır) useEffect belgelerinin başlangıcına yakın). Bahsettiğim gibi, yoğun / pahalı bir şekilde yeniden oluşturulmuş bir alt bileşene geçmeyi planladığınız bir geri arama işlevine kararlı / hatırlanmış bir ref istiyorsanız, useCallback'i kullanmak isteyeceksiniz ve referans eşitliği önemlidir. Başka herhangi bir kullanım, her seferinde render işleminde yeni bir işlev oluşturun.
Jed Richards

16

Peki ya bu şekilde:

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

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

15
Güzel düşünce! Ne yazık ki bu, her render çağrısında yeni bir işlev oluşturma sorununu aşmıyor
aStewartDesign

@aStewartBu sorun için herhangi bir çözüm veya güncelleme tasarlıyor musunuz? bunu duyduğuma gerçekten sevindim, çünkü aynı sorunla karşı karşıyayım
Kim

4
myHandler uygulamasına sahip olan bir ana normal bileşene sahip olun ve daha sonra bunu alt bileşene iletin
Raja Rao

Şimdiye kadar bunun daha iyi bir yolu yok sanırım (Temmuz 2018), herhangi biri harika bir şey bulursa, lütfen bana bildirin
a_m_dev

neden olmasın <button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>? Yani, onu myHandler işleviyle sarmayın.
Simon Franzen

6

İşleyici değişen özelliklere dayanıyorsa, onu önbelleğe alacağınız durum bilgisi içeren bir örneğiniz olmadığı için her seferinde işleyiciyi oluşturmanız gerekecektir. İşe yarayabilecek başka bir alternatif, işleyiciyi girdi props'larına göre belleğe almak olacaktır.

Çift uygulama seçenekleri lodash._memoize R.memoize fast-memoize


4

çözüm bir mapPropsToHandler ve event.target.

fonksiyonlar js'deki nesnelerdir, bu yüzden onlara özellikler eklemek mümkündür.

function onChange() { console.log(onChange.list) }

function Input(props) {
    onChange.list = props.list;
    return <input onChange={onChange}/>
}

bu işlev, bir özelliği bir işleve yalnızca bir kez bağlar.

export function mapPropsToHandler(handler, props) {
    for (let property in props) {
        if (props.hasOwnProperty(property)) {
            if(!handler.hasOwnProperty(property)) {
                 handler[property] = props[property];
            }
        }
    }
}

Malzemelerimi aynen böyle alıyorum.

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

bu örnek hem event.target hem de mapPropsToHandler'ı birleştirdi. İşlevleri yalnızca sayılara veya dizelere değil işleyicilere eklemek daha iyidir. sayı ve dizeler DOM özelliği yardımıyla aktarılabilir.

<select data-id={id}/>

mapPropsToHandler yerine

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

çözüm iki. olay delegasyonu

birinci çözüme bakın. olay işleyicisini girdiden kaldırabilir ve diğer girdileri de tutan ebeveynine koyabiliriz ve yardım delegasyon tekniği ile event.traget ve mapPropsToHandler fonksiyonunu tekrar kullanabiliriz.


Kötü uygulama! Bir işlev yalnızca amacına hizmet etmelidir, özellikleri tutmamak için bazı parametreler üzerinde mantık yürütmek içindir, çünkü javascript aynı şeyi yapmak için birçok yaratıcı yola izin verir, işe yarayan her şeyi kullanmanıza izin vermeniz gerektiği anlamına gelmez.
BeyondTheSea

4

İşte typcript'te react ve redux yazımı ile uygulanan basit favori ürünler listem. Özel işleyicide ihtiyacınız olan tüm bağımsız değişkenleri iletebilir ve EventHandlerorijinal olay bağımsız değişkenini kabul eden yeni bir tane döndürebilirsiniz . Bu var MouseEventbu örnekte.

İzole edilmiş işlevler jsx'i daha temiz tutar ve birkaç linting kuralını ihlal etmesini önler. Bu şekilde 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);

Yazı tipine aşina değilseniz, işte javascript pasajı:

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

Durum bilgisi olmayan bir bileşen gibi, sadece bir işlev ekleyin -

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

ve dönüşte şöyle denir onChange={addName}


1

Endişelendiğiniz sahne içinde yalnızca birkaç işleviniz varsa, bunu yapabilirsiniz:

let _dispatch = () => {};

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

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

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

Çok daha karmaşık hale gelirse, genellikle bir sınıf bileşenine geri dönüyorum.


1

Sürekli çabadan sonra nihayet benim için çalıştı.

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

Not: metin kutusu dosyalanmış bağlama için "ad" özniteliği ve "özellik adı" (örneğin: model.name) aynı olmalıdır, bu durumda yalnızca "onInputTextChange" çalışacaktır. "onInputTextChange" mantığı kodunuz tarafından değiştirilebilir.


0

Bunun gibi bir şeye ne dersiniz:

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

ancak, örnekteki gibi, onClick'i daha düşük / iç bileşenlere geçirmeniz gerekmiyorsa bu gerçekten abartılıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.