前言

浏览器原生的 addEventListener 允许我们进行事件监听,React 同样提供了一套事件监听的机制:使用 on* 属性。

例如监听按钮的 click 事件:

  1. function handleClick() {
  2. console.log('Click');
  3. }
  4. <button onClick={handleClick}>btn</button>

注意:若不经处理,on* 事件监听只能用在 HTML 的标签上,不能用在组件标签上。

与原生浏览器事件相比

相同

React 事件与原生浏览器事件拥有同样的接口,同样支持事件的冒泡机制:使用 stopPropagation()preventDefault() 来中断。

举个栗子:

  1. function handleClick(e) {
  2. console.log(e.target.innerHTML);
  3. }
  4. <button onClick={handleClick}>btn</button>

这样每次点击按钮,就会打印按钮的 innerHTML

不同

与原生浏览器事件不同的是,在 JSX 内使用驼峰式写法(例如 onClick),HTML 事件使用全小写(例如 onclick)。并且 HTML 通过写 JavaScript 代码字符串直接写入了 HTML 属性内,React 则更像是事件委派的机制。

this

this 一直是一个令 JavaScript 初学者头疼的东西,如果你对 this 一点不了解的话推荐你看一下 MDN 对 this 的讲解。简单讲,this 就是指向方法的调用者。

使用 class component 并且绑定事件时,不可避免的要手动实现 this 的绑定。

列举几种绑定 this 的方式:

  • bindbind 方法返回一个函数,函数的 this 指向 bind 的第一个参数。缺点是每次调用 onClick 事件时都会生成一个新的函数。
  1. class Demo extends React.Component {
  2. handleClick() {
  3. console.log('hello');
  4. }
  5. render() {
  6. return (
  7. <button onClick={this.handleClick.bind(this)}>btn</button>
  8. )
  9. }
  10. }
  • 构造器内声明。在构造器内绑定 this,这样的好处是仅绑定一次,不存在无用的重复绑定,推荐使用这种方式。
  1. class Demo extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.handleClick = this.handleClick.bind(this);
  5. }
  6. handleClick() {
  7. console.log('hello');
  8. }
  9. render() {
  10. return (
  11. <button onClick={this.handleClick}>btn</button>
  12. )
  13. }
  14. }
  • 箭头函数。箭头函数没有 this,所以需要继承定义箭头函数所在的作用域的 this。
  1. class Demo extends React.Component {
  2. // 我更倾向于构造器内声明,这种写法严格讲应该是类的实例属性而不是方法
  3. handleClick = () => {
  4. console.log('hello');
  5. }
  6. render() {
  7. return (
  8. <button onClick={this.handleClick}>btn</button>
  9. )
  10. }
  11. }
  12. // 或者
  13. class Demo extends React.Component {
  14. handleClick() {
  15. console.log('hello');
  16. }
  17. render() {
  18. return (
  19. // 不推荐 每次调用 onClick 事件时都会生成一个新的函数
  20. <button onClick={() => this.handleClick()}>btn</button>
  21. )
  22. }
  23. }

实战

我们利用事件实现可交互的天气查询。

加入当前地点

接口内已经有了当前查询的地点,我们取出渲染即可:

  1. class App extends React.Component {
  2. state = {
  3. city: null,
  4. realTimeData: [],
  5. weatherDetailsData: [],
  6. indexesData: [],
  7. }
  8. componentDidMount() {
  9. fetch('/app/weather/listWeather?cityIds=101240101')
  10. .then(res => res.json())
  11. .then((res) => {
  12. if (res.code === '200' && res.value.length) {
  13. const { city, realtime, weatherDetailsInfo, indexes } = res.value[0];
  14. const { weather3HoursDetailsInfos } = weatherDetailsInfo;
  15. this.setState({
  16. city,
  17. realTimeData: realtime,
  18. weatherDetailsData: weather3HoursDetailsInfos,
  19. indexesData: indexes,
  20. });
  21. }
  22. });
  23. }
  24. render() {
  25. const { city, realTimeData, weatherDetailsData, indexesData } = this.state;
  26. return (
  27. <div className="app">
  28. <div className="city">{city}</div>
  29. <RealTime data={realTimeData}/>
  30. <WeatherDetails data={weatherDetailsData} />
  31. <Indexes data={indexesData}/>
  32. </div>
  33. );
  34. }
  35. }

下一步,我们希望点击当前的地点,弹出输入框,并根据输入框输入的城市更新天气信息。

首先写一个 City 组件,将展示的内容一并放入组件内:

  1. class City extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. visible: false,
  6. }
  7. this.handleVisible = this.handleVisible.bind(this);
  8. }
  9. handleVisible() {
  10. this.setState({
  11. visible: true,
  12. });
  13. }
  14. render() {
  15. const city = this.props.data;
  16. const { visible } = this.state;
  17. return (
  18. <div className="city">
  19. <div onClick={this.handleVisible}>{city}</div>
  20. {
  21. visible && (
  22. <div className="dialog">
  23. <input type="text" placeholder="搜索市" />
  24. <button>查询</button>
  25. </div>
  26. )
  27. }
  28. </div>
  29. )
  30. }
  31. }

这样我们就实现了一个点击就出现的弹框。

事件监听 - 图1

然后我们点击查询按钮时,需要先获取输入框输入的内容,然后根据内容更新天气信息。

我们可以通过 state 来控制输入框输入,每输入一个字符 state 就做出相应变化(通过监听 onChange 事件)。即 state === 输入内容

  1. class City extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. visible: false,
  6. input: '',
  7. }
  8. this.handleVisible = this.handleVisible.bind(this);
  9. this.handleChange = this.handleChange.bind(this);
  10. }
  11. handleVisible() {
  12. this.setState({
  13. visible: true,
  14. });
  15. }
  16. handleChange(e) {
  17. this.setState({
  18. input: e.target.value,
  19. });
  20. }
  21. render() {
  22. const city = this.props.data;
  23. const { visible, input } = this.state;
  24. return (
  25. <div className="city">
  26. <div onClick={this.handleVisible}>{city}</div>
  27. {
  28. visible && (
  29. <div className="dialog">
  30. <input
  31. type="text"
  32. placeholder="搜索市"
  33. value={input}
  34. onChange={this.handleChange}
  35. />
  36. <button>查询</button>
  37. </div>
  38. )
  39. }
  40. </div>
  41. )
  42. }
  43. }

就差最后一步了,点击查询按钮获取到输入框内输入的城市的天气。

由于我们接口传入的是城市的 id,我找到了一份市区 id 对应的json数据用于查询输入城市对应的 id:

  1. import { cities } from './static/city.json';
  2. class City extends React.Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. visible: false,
  7. input: '',
  8. }
  9. this.handleVisible = this.handleVisible.bind(this);
  10. this.handleChange = this.handleChange.bind(this);
  11. this.handleSearch = this.handleSearch.bind(this);
  12. }
  13. handleVisible() {
  14. this.setState({
  15. visible: true,
  16. });
  17. }
  18. handleChange(e) {
  19. this.setState({
  20. input: e.target.value,
  21. });
  22. }
  23. handleSearch() {
  24. const { input } = this.state;
  25. if (input) {
  26. const result = cities.find(item => item.city === input);
  27. if (result) {
  28. // 更新天气并关闭当前弹框
  29. // todo 更新天气
  30. this.setState({
  31. visible: false,
  32. input: '', // 关闭弹框后清空输入
  33. });
  34. } else {
  35. alert('没有查询到相应城市')
  36. }
  37. } else {
  38. alert('没有输入要查询的城市')
  39. }
  40. }
  41. render() {
  42. const city = this.props.data;
  43. const { visible, input } = this.state;
  44. return (
  45. <div className="city">
  46. <div onClick={this.handleVisible}>{city}</div>
  47. {
  48. visible && (
  49. <div className="dialog">
  50. <input
  51. type="text"
  52. placeholder="搜索市"
  53. value={input}
  54. onChange={this.handleChange}
  55. />
  56. <button onClick={this.handleSearch}>查询</button>
  57. </div>
  58. )
  59. }
  60. </div>
  61. )
  62. }
  63. }

到这里就差最后一步,点击查询后将城市 id 传给父组件,最简单的,我们可以通过类似回调的方式传递:

  1. // 首先我们给组件 City 增加一个 props onCityChange
  2. <City data={city} onCityChange={this.handleCityChange}/>
  3. handleCityChange = id => {
  4. // 在这里更新天气数据
  5. }
  6. // 在组件 City 内部,我们只需要调用 props.onCityChange 并传入城市 id 等数据即可
  7. this.props.onCityChange(result.cityid)

我们更新一下父组件请求接口数据的地方:

  1. // 将请求数据抽离,可复用
  2. getData(id) {
  3. fetch(`/app/weather/listWeather?cityIds=${id}`)
  4. .then(res => res.json())
  5. .then((res) => {
  6. if (res.code === '200' && res.value.length) {
  7. const { city, realtime, weatherDetailsInfo, indexes } = res.value[0];
  8. const { weather3HoursDetailsInfos } = weatherDetailsInfo;
  9. this.setState({
  10. city,
  11. realTimeData: realtime,
  12. weatherDetailsData: weather3HoursDetailsInfos,
  13. indexesData: indexes,
  14. });
  15. }
  16. });
  17. }
  18. componentDidMount() {
  19. // 默认请求北京的天气,当然可以拓展 例如利用定位获取当前位置,或者要求用户输入位置
  20. this.getData('101010100');
  21. }
  22. handleCityChange = cityid => {
  23. this.getData(cityid);
  24. }

好啦到这里我们的实战就完成啦。实战源码

我们的实战仍然有优化的空间,例如将各个组件分别写入不同的文件内,最后在 App.js 内统一引入各个组件。

希望这些内容能对大家有所帮助。