React无状态组件中的事件处理程序


82

试图找出在React无状态组件中创建事件处理程序的最佳方法。我可以做这样的事情:

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

Answers:


61

将处理程序应用于功能组件中的元素通常应如下所示:

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

如果您需要做更复杂的事情,则表明a)组件不应无状态(使用类或钩子),或b)您应在外部有状态容器组件中创建处理程序。

顺便说一句,并且稍微破坏了我的第一点,除非组件位于应用程序中特别密集的重新渲染部分,否则无需担心在中创建箭头功能render()


2
如何避免每次呈现无状态组件时都创建函数?
zero_cool

1
上面的代码示例仅显示了通过引用应用的处理程序,在该组件的呈现过程中未创建新的处理程序功能。如果外部组件使用useCallback(() => {}, [])或创建了处理程序this.onClick = this.onClick.bind(this),则该组件也将在每个渲染器中获得相同的处理程序引用,这可能有助于使用React.memoshouldComponentUpdate(但这仅与密集重新渲染的众多/复杂组件有关)。
杰德·理查兹

46

使用新的React钩子功能,它看起来可能像这样:

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

1
在上面的示例中,使用useCallback-并没有提高效率,并且您仍在每个渲染器中生成一个新的箭头函数(将arg传递给useCallback)。useCallback仅在将回调传递给依赖于引用相等性的优化子组件以防止不必要的渲染时才有用。如果您只是将回调应用于按钮之类的HTML元素,则不要使用useCallback
杰德·理查兹

1
@JedRichards尽管在每个渲染器上都创建了一个新的箭头功能,但不需要更新DOM,这可以节省时间
herman

3
@herman根本没有区别(除了性能上的小损失),这就是为什么我们在此评论下的答案有点令人怀疑:)任何没有依赖项数组的钩子都将在每次更新后运行(已讨论)在useEffect文档的开头附近)。就像我提到的那样,如果想要稳定/存储对计划传递给密集/昂贵地重新呈现的子组件的回调函数的引用,则只需要使用useCallback,并且引用相等性很重要。任何其他用法,每次都只需在render中创建一个新函数即可。
杰德·理查兹

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

@aStewartDesign针对此问题有任何解决方案或更新?听到这个消息真的很高兴,因为我面临着同样的问题
Kim

4
有一个具有myHandler实现的父常规组件,然后将其简单地传递给子组件
Raja Rao

我想到目前为止,没有更好的方法(2018年7月),如果有人发现一些很棒的东西,请让我知道
a_m_dev

为什么不<button onClick={(e) => props.dispatch(e,props.whatever)}>Click Me</button>呢?我的意思是,不要将其包装在myHandler函数中。
西蒙·弗兰岑

6

如果处理程序依赖于更改的属性,那么由于缺少要在其上进行缓存的有状态实例,因此每次都必须创建处理程序。另一个可行的替代方法是根据输入的道具来记住处理程序。

几个实现选项 lodash._memoize R.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>
    )
}

解决方案二。事件委托

请参阅解决方案一。我们可以从输入中删除事件处理程序,并将其放置到拥有其他输入的父级中,通过帮助委托技术,我们可以再次使用event.traget和mapPropsToHandler函数。


不好的做法!函数仅应满足其目的,它意味着对某些参数执行逻辑而不保留属性,仅因为javascript允许许多创造性的方式来完成相同的事情,并不意味着您应该让自己使用任何可行的方法。
BeyondTheSea

4

这是我的最喜欢的产品列表,它以打字稿的React和Redux编写实现。您可以在自定义处理程序中传递所需的所有参数,然后返回一个EventHandler接受origin事件参数的新参数。这是MouseEvent在这个例子中。

隔离的函数使jsx更加整洁,并防止违反一些规则。例如jsx-no-bindjsx-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);

如果您不熟悉打字稿,请使用以下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);


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

但是,如果您不需要将onClick传递给较低/内部组件,那么实际上这是矫over过正。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.