3.React事件处理

前言

这是一篇介绍React框架中给JSX对象绑定操作事件的文章,在阅读之前你必须了解react的组件相关知识

JSX中响应事件

UI界面开发不单单是样式的开发,还涉及一些用户交互事件的接收和处理。在React中是可以给JSX对象添加事件处理函数的。事件处理函数可以是自定义函数,这些处理函数将在浏览器响应用户的交互(如点击、悬停、表单输入框获得焦点等)时触发。

传统的JQuery等JS框架必须编写命令式代码来选择DOM元素,然后将事件处理函数附加到它们上面。React组件中事件处理是声明式的,这些处理事件的函数是UI组件结构的一部。对于咱们前端程序员来说,不必再去追踪分配事件的程序代码,不必去理清事件和元素对应关系,也算是劳动上的减负。

React中给组件元素绑定点击事件并声明事件处理程序的代码如下:

function MyButton({children}) {
    function handleClick() {
        console.log("你点击了我!");
    }
    return (
        <button onClick={handleClick}>
          {children}
        </button>
    );
}

上述代码中,现在一个组件中定义了一个handleClick的函数,然后通过onClickprops属性传递给了<button>子组件。

所以绑定响应事件的本质上就是,将一个处理函数通过元素属性(props)的方式传递给了子组件元素。当然,这个过程中,对于原生的JSX对象属性名是有点讲究的。内置组件(<button><div>)仅支持浏览器事件名称,例如onClick。但是,当咱们构建自己的组件时,则可以按个人喜好命名事件处理函数的prop

当然在同一个JSX组件元素上可以同时绑定多个事件处理程序用于响应不同的事件:

function MyInput() {
    const handleChange = () => {
        console.log('changed');
    };

    const handleBlur = () => {
        console.log('blured');
    };

    return <input onChange={handleChange} onBlur={handleBlur} />;
}

这里有必要强调一下,绑定事件传递给对应props属性的是函数,而不是函数返回值。

// 传递一个函数,点击事件触发的时候运行函数(正确)
<button onClick={handleClick}>按钮</button>
// 调用一个函数,渲染的时候运行函数,然后传递函数返回值(错误)
<button onClick={handleClick()}>按钮</button>

但是如果咱们的目标是:事件触发时将当前的一个静态的值传递给另一个接收参数的函数,那么这种写法就有了些局限。

解决这个问题也不是很麻烦,咱们只要使用内联函数给它包一层,包成标准的不接受参数的函数就行。

function MyButton({children}) {
    return (
        <button onClick={() => {console.log("clicked");}}>
          {children}
        </button>
    );
}

或者

function MyButton({children}) {
    return (
        <button onClick={function(){console.log("clicked");}}>
          {children}
        </button>
    );
}

以上所有方式都是等效的。当函数体较短时,内联事件处理函数也会很方便。

自定义事件处理函数

前面提到,对于咱们自己定义的组件时,则可以按个人喜好命名事件处理函数的prop

function Button({ onAction, children }) {
  return (
    <button onClick={onAction}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`正在播放 ${movieName}`);
  }

  return (
    <Button onAction={handlePlayClick}>
      播放 "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onAction={() => alert('正在上传!')}>
      上传图片
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="电影名" />
      <UploadButton />
    </div>
  );
}

上述代码中Button组件就定义了一个onActionprop用于接收父组件传递过来的处理函数。

子组件向父组件传值

前面介绍的props传递参数,基本上都是父组件传递给子组件的,如果子组件想传递信息给父组件呢?其实就可以通过这种传递回调函数作为props属性参数的方式,将父组件的处理函数传递给子组件,然后由子组件将自己的信息传递给父组件。

function MyButton({index, children, onAction}) {
    return (
        <button onClick={function(){onAction(index);}}>
          {children}
        </button>
    );
}
function ButtonGroup() {
    const names = ['按钮1', '按钮2', '按钮3', '按钮4', '按钮5'];
    function handleClick(key) {
        console.log('这是第'+key+'个按钮');
    }
    const listItems = names.map((name, index) =>
      <MyButton key={index} index={index} onAction={handleClick}>{name}</MyButton>
    );
    return (
        <div>
        {listItems}
        </div>
    );
}

这样当按钮被点击的时候,作为父组件的ButtonGroup就可以知道是第几个按钮被点击了。

父组件直接分配不同任务给子组件

咱们也可以在父组件中定义子组件的事件处理函数,然后在不同的子组件响应事件下进行不同的处理函数运行。为此,咱们将组件从父组件接收的prop属性参数作为事件处理函数。

function Button({ onClick, children }) {
  return (
    <button onClick={function(){onClick(children)}}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  function playMovie(movieName) {
      console.log(`正在播放 ${movieName}`);
  }
  function uploadFile() {
      console.log('正在上传');
  }
  return (
    <div>
      <Button onClick={playMovie} >藏龙卧虎</Button>
      <Button onClick={uploadFile} >上传图片</Button>
    </div>
  );
}

这所以推荐大家这么做,是因为这样设计可以提高我们的代码复用率。比如,播放功能可以扩展,不仅仅是通过按钮点击,也可以通过键盘上的快捷键。上传文件功能不单单的点击按钮触发,也可以是拖拽上传。

合成事件对象使用

前面在说到“内联函数”时,我说将需要参数的函数使用内联函数包一层,包成标准的不接受参数的函数就可以作为触发事件处理函数使用。但是,事件处理函数并不是没有参数的形式,只不过在前面的代码中,我们将那个唯一的参数给忽略了。

当我们使用原生的addEventListener()函数将事件处理函数附加到DOM元素时,回调函数会接收到一个事件参数。React中的事件处理函数也会接收一个事件参数,只不过它不是标准的事件示例,而是被称为SyntheticEvent(直译过来就叫合成事件),是原生事件实例的一个简单包装器。

感兴趣的朋友可以写出类似的代码打印出来看看,其实这个对象中包裹的内容与原生的事件参数类似,有点击的坐标什么的,我也看不太懂。React之所以要包这一层主要的原因就是不同浏览器的事件参数不太一样,包了一层给做了个格式和信息的统一。

function MyButton({children}) {
    return (
        <button onClick={(e) => {console.log(e);}}>
          {children}
        </button>
    );
}

合成事件对象除了提供了那些用于查看的信息,还有两个比较有的函数: e.stopPropagation()e.preventDefault()。下面咱们分别来介绍这两个函数的用途。

e.stopPropagation()函数

事件处理函数处理捕获组件自身绑定的事件外,还能捕获任何来自它子组件的事件。从事件发生的地方开始,然后沿着组件树向上传播。

例如:

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('你点击了 toolbar !');
    }}>
      <button onClick={() => alert('正在播放!')}>
        播放电影
      </button>
      <button onClick={() => alert('正在上传!')}>
        上传图片
      </button>
    </div>
  );
}

在上面这个组件中,如果你点击任一按钮,它自身的onClick将首先执行,然后父级<div>onClick会接着执行。因此会出现两条消息弹窗。这样的过程被称为事件传播,在React中所有的事件都会传播,除了onScroll,它仅适用于你附加到的JSX标签。

要避免“事件传播”的发生,就需要使用e.stopPropagation()函数。当然,这里需要说清楚事件传播并不是Bug而是逻辑,避免事件传播是特例。如果咱们要阻止一个事件传播到其父组件,只需要在绑定事件中调用一个e.stopPropagation()函数即可,如下:

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('你点击了 toolbar !');
    }}>
      <button onClick={(e) => {
          alert('正在播放!');
          e.stopPropagation();
      }}>
        播放电影
      </button>
      <button onClick={(e) => {
          alert('正在上传!');
          e.stopPropagation();
      }}>
        上传图片
      </button>
    </div>
  );
}

这样咱们再次点击按钮时,将只显示一个来自<button>alert弹窗,而不会再次运行父组件上绑定的点击事件,即阻止第二次弹窗。

e.stopPropagation()函数用于阻止触发绑定在外层标签上的事件处理函数。

e.preventDefault()函数

某些浏览器事件具有与事件相关联的默认行为。例如,点击<form>表单内部的按钮会触发表单提交事件,默认情况下将重新加载整个页面:

export default function Signup() {
  return (
    <form onSubmit={() => alert('提交表单!')}>
      <input />
      <button>发送</button>
    </form>
  );
}

当我们想要阻止这种默认行为时,可以调用合成事件对象中的e.preventDefault()函数。

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert('提交表单!');
    }}>
      <input />
      <button>发送</button>
    </form>
  );
}

所以当有些时候遇到一些需要规避的浏览器默认行为时,可以调用e.preventDefault()函数来实现需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值