使用react实现select_使用 Hooks 优化 React 组件

奇技指南

本文内容主要是我之前分享的文字版,若想看重点的话可以看之前的Slide: https://ppt.baomitu.com/d/75fc979a

本文作者奇舞团前端开发工程师李喆明。

需求描述

由于作者所在的业务是资讯内容类业务,因而在业务中会经常碰到如下场景:有一个内容列表,列表中需要按照一定的规则插入广告。除了获取广告数据,广告展现和点击后需要有打点上报逻辑。正常来说会这么写:

import React from 'react';

export default class extends React.Component {

  state = {newsData: [], adData: []};

  constructor() { this.getNewsData(); }

  getNewsData() {

    const newsData = [...];

    this.setState({newsData});

    this.getAdData(newsData.length / 2); //根据新闻数和插入规则换算广告请求数

  }

  getAdData() {

    const adData = [...];

    this.setState({adData});

  }

  render() {

    const {newsData, adData} = this.state;

    const comps = [];

    for(let i = 0; i < newsData.length; i++) {

      // 根据插入规则判断当前新闻卡片后是否要插入广告

      comps.push(<NewsCard {...newsData[i]} key={`news-${i}`} />);

      if(i % 2) { comps.push(<AdCard {...adData[i/2]} key={`ad-${i}`} />); }

    }

    return (<div>{comps}</div>);

  }

}

class AdCard extends React.Component {

  componentDidMount() {

    observe(this.dom, () => {});

  }

  onClick = () => {};

  onMouseUp = () => {};

  onMouseDown = () => {};

  getDOM = dom => this.dom = dom;

  render() {

    return <div

      ref={this.getDOM}

      onMouseUp={this.onMouseUp}

      onMouseDown={this.onMouseDown}

      onClick={this.onClick}

    >{this.props.title}</div>

  }

}

逻辑非常的简单,getNewsData() 拿到资讯列表数据之后计算需要请求的广告数调用 getAdData()请求广告数据,最后根据插入规则将资讯和内容渲染到列表中。广告使用自定义组件渲染,使用 Intersection Observe API 实现广告曝光打点,监听 DOM 对应的点击时间实现广告点击打点。

cf47d712779df7faf4e3edc4412a5480.png如果说只有一个组件是这样的还好说,但是从上图可以看出,我们有大量的内容+广告混排场景。整体的逻辑和刚才说的都是一样的,唯一的区别是不同的列表对应不一样的显现形式。在这种情况下如何设计一个既能将通用逻辑提取,又能满足各个模块的自定义需求的通用模块就成了我们必须考虑的事情了。

React 组件设计模式

在具体讨论方案之前,我们先简单的了解一下常见的 React 组件设计模式。基本上分为以下几种方案:
  • Context 模式
  • 组合组件
  • 继承模式
  • 容器组件和展示组件

  • Render Props

  • Hoc 高阶组件

其中 Context 模式多用来在多层嵌套组件中进行跨组件的数据传递,针对我们当前组件层级不多的情况用处不是非常大,这里就不多表。我们来看看剩下的几个模式各自有什么优缺点,最终来评估下是否能应用到我们的场景中。

组合组件

组合组件是通过模块化组件构建应用的模式,它是 React 模块化开发的基础。除去普通的按照正常的业务功能进行模块拆分,还有就是将配置和逻辑进行解耦的组合组件方式。例如下面的组合方式就是利用类似 Vue 的 slot 方式将配置通过子组件的形式与组件进行组合,是的组件配置更优雅。

<Modal>

  <Modal.Title>Modal TitleModal.Title>

  <Modal.Content>Modal ContentModal.Content>

  <Modal.Footer> <button>OKbutton> Modal.Footer>

Modal>

又如下面的下拉选择组件,通过将 和 进行组合,即达到了组件化配置的目的,又达到了通用方法的复用。同时将点击操作在 组件中直接传递下去方便了点击后直接修改选择状态。

export default function(props) {

  return React.Children.map(props.children, child =>

    React.cloneElement(child, {onClick() { console.log('click') }}

  ));

}

<Select>

  <Option>Click Me!Option>

  <Option>Click Me!Option>

Select>

继承模式

继承模式是使用类继承的方式对组件代码进行复用。在面向对象编程模式中,继承是一种非常简单且通用的代码抽象复用方式。如果大部分逻辑相同,只是一些细节不一致,只要简单的将不一致的地方抽成成员方法,继承的时候复写该成员方法即可达到简单的组件复用。

不过我们知道 JS 中的继承本质上还是通过原型链实现的语法糖,所以在一些场景使用上没有其它语言的继承那么方便,例如无法直接实现多继承,多继承后的跨层级方法调用比较麻烦,适合简单的逻辑复用。另外通过继承方式会将父类中的所有方法都继承过来,不小心的话非常容易继承到不需要的功能。

容器组件和展示组件

展示组件和容器组件是将数据逻辑和渲染逻辑进行拆分从而降低组件复杂度的模式。使用容器组件可以把最开始的代码改写成如下的形式。这样做最大的好处是渲染层可以抽离成无状态组件,它不需要关心数据的获取逻辑,直接通过 props获取数据渲染即可,针对展示组件能实现很好的复用。

class NewsList extends React.Component {

  state = {newsData: [], adData: []};

  constructor() { this.getNewsData(); }

  getNewsData() { this.getAdData(newsData.length / 2) }

  getAdData() {}

  render() { return <List news={this.state.newsData} ad={this.state.adData} /> }

}

function List({news, ad}) {

  const {newsData, adData} = this.state;

  const comps = [];

  for(let i = 0; i < newsData.length; i++) {

    comps.push(<NewsCard {...newsData[i]} key={`news-${i}`} />);

    if(i % 2) { comps.push(<AdCard {...adData[i/2]} key={`ad-${i}`} />); }

  }

  return (<div>{comps}</div>);

}

但是我们也可以看到即使我们把渲染逻辑拆分出去了,本身组件的数据逻辑还是非常的复杂,没有做到很好的拆分。同时容器组件和展示组件存在耦合关系,所以无法很好的对逻辑组件进行复用。

Render Props

术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

它的本质实际上是通过一个函数 prop 将数据传递到其它组件的方式,所以按照这个逻辑我们又可以将刚才的代码简单的改写一下。

class NewsList extends React.Component {

  state = {newsData: [], adData: []};

  constructor() { this.getNewsData(); }

  getNewsData() { this.getAdData(newsData.length / 2) }

  getAdData() {}

  render() { return this.props.render(this.state) }

}

function List({news, ad}) {

  const {newsData, adData} = this.state;

  const comps = [];

  for(let i = 0; i < newsData.length; i++) {

    comps.push(<NewsCard {...newsData[i]} key={`news-${i}`} />);

    if(i % 2) { comps.push(<AdCard {...adData[i/2]} key={`ad-${i}`} />); }

  }

  return (<div>{comps}</div>);

}

<NewsList render={({newsData, adData}) => <List news={newsData} ad={adData} />

可以看到,通过一个函数调用我们将数据逻辑和渲染逻辑进行解耦,解决了之前数据逻辑无法复用的问题。不过通过函数回调的形式将数据传入,如果想要把逻辑拆分(例如资讯数据获取与广告数据获取逻辑拆分)会变得比较麻烦,让我想起了被 callback 支配的恐惧。

同时由于render 的值为一个匿名函数,每次渲染 的时候都会重新生成,而这个匿名函数执行的时候会返回一个 组件,这个本质上每次执行也是一个“新”的组件。所以 Render Props 使用不当的话会非常容易造成不必要的重复渲染。

HoC 组件

React 里还有一种使用比较广泛的组件模式就是 HoC 高阶组件设计模式。它是一种基于 React 的组合特性而形成的设计模式,它的本质是参数为组件,返回值为新组件的函数。我们来看看刚才的代码使用 HoC 组件修改后会变成什么样子。

function withNews(Comp) {

  return class extends React.Component {

    state = {newsData: []};

    constructor() { this.getNewsData(); }

    render() { return <Comp {...this.props} news={this.state.newsData} /> }

  }

}

function withAd(Comp) {

  return class extends React.Component {

    state = {adData: []};

    componentWillReceiveProps(nextProps) {

      if(this.props.news.length) { this.getAdData(); }

    }

    render() { return <Comp {...this.props} ad={this.state.adData} /> }

  }

}

const ListWithNewsAndAd = withAd(withNews(List));

可以看到这次改动最激动的地方在于我们第一次把数据逻辑进行了拆分,这也是高阶组件的魅力,它不局限于 UI 复用,使得代码复用更加自由(当然 Render Props 也是可以实现的)。

当然这种模式也并不是完美的,它也有它的缺点。我们可以看到它的本质是通过 props 在高阶组件中将多个数据传入到子组件中,非常类似 mixin 的形式。所以它也会有 mixin 的缺点,那就是属性名冲突的问题。由于不同的高阶组件由不同的开发者开发,内部会传递什么样的属性名到子组件中就成了未知数。同时多层组件的嵌套导致组件层级过多,在性能和调试上都会带来问题。

初版实现

了解完这些设计模式之后,我们再回头来看看我们的需求。通过观察了解不同的组件中的共同部分之后,我们可以将这种类型的组件抽象为如下描述“在一个内容列表中按照一定规则插入一定数量的和内容一致的一定样式的广告组件”。在这段描述中存在着三个不定因素:
  • 一定规则:不同的组件插入广告的逻辑是不一样的
  • 一定数量:不同的组件由于资讯内容的不同,插入逻辑的不同导致需要的广告数量也是不一样的
  • 一定样式:不同的组件由于资讯内容样式不同所以广告的样式自然也不相同
除却以上三个因素之外,广告其它的逻辑广告数据的获取以及广告的曝光和点击打点等都是通用的。最后我们将广告组件的逻辑顺着之前了解的设计模式抽离成三个部分:
  • 广告数据的获取:
  • 广告模块的渲染: Base 模块

  • 广告模块的插入:由具体业务处理

import React from 'react';

import Mediav from '@q/mediav';

export default class extends React.Component {

  state = {newsData: []};

  constructor() { this.getNewsData(); }

  render() {

    const comps = [];

    for(let i = 0; i < newsData.length; i++) {

      comps.push(<NewsCard {...newsData[i]} key={`news-${i}`} />);

      if(i % 2) { comps.push(<AdCard key={`ad-${i}`} />); }

    }

    return (<Mediav.Provider id="xxx">{comps}</Mediav.Provider>);

  }

}

class AdCard extends Mediav.Item {

  render() {

    if(!this.props.type) { return null; }

    const {title} = this.props;

    return (<div ref={this.getDOM} onClick={this.onClick}

      onMouseUp={this.onMouseUp} onMouseDown={this.onMouseDown}

    >{title}</div>);

  }

}

通过容器组件 对数据获取逻辑进行封装,通过遍历子组件找到 组件的示例个数来告知需要请求的广告数量。请求到广告后通过 Props 注入的形式传入到渲染组件中。而渲染组件 继承自 ,一方面能告诉容器组件它是广告组件的插槽,同时还能抽离广告曝光打点和点击打点等通用逻辑进行复用。在用户自定义的 组件中,我们可以自定义不同模块的广告组件的渲染样式,最终完成了一套广告组件的渲染。

不过这样实现还是有一些不足的地方。广告曝光检测需要依赖原生 DOM,而 Ref 使用forwardRef() 在组件间传递稍微有点复杂,所以最后采用了继承模式进行公共方法的抽离。子组件继承后自行绑定父类的一些方法即可,在这点上理解起来有点晦涩,看起来总像是绑定了一些“不存在”的方法。

React Hooks

针对上面提出的问题,有没有什么方法可以解决呢?最终我想到了 Hooks 的方案,通过使用 Hooks 改写后能完美的解决这个问题。我们先简单的了解下什么是 Hooks,它允许我们在不编写 class 的情况下使用 state 和 React 生命周期等相关特性。

const {useState, useEffect} = React;

function App() {

  const [count, setCount] = useState(0);

  useEffect(() => {

    const interval = setInterval(() => setCount(count + 1), 1000);

    return () => clearInterval(interval);

  });

  return <span>{count}</span>;

}

ReactDOM.render(<App />, document.getElementById('app'));

可以看到,它使用 useState 提供了 state,使用useEffect来做一些需要在声明周期中执行的方法。使用 useEffect代理了原来生命周期的概念后,让代码理解起来更加简单。

当然这不是 Hooks 厉害的地方,它最厉害的地方是支持自定义 Hooks,通过自定义 Hooks 你能对逻辑进行统一的封装。针对一个数据获取的逻辑,我们需要定义state,然后在初始化的时候去获取数据,当 id 发生变化后我们需要重新获取数据。

class User extends React.Component {

  state = {

    user: {}

  }

  constructor(...args) {

    super(...args);

    const {name} = this.props;

    this.getUserInfo(name)

  }

  componentWillReceiveProps(nextProps) {

    if(this.props.name === nextProps.name) {

      return;

    }

    this.getUserInfo(nextProps.name);

  }

  async getUserInfo(name) {

    const user = await fetch(url, {name});

    this.setState({user});

  }

  render() {

    return <div>{this.state.user.name}</div>

  }

}

可以看到我们获取用户信息的这个逻辑要实现需要在组件的各种地方写逻辑,代码一多之后非常容易造成需要各种跳行来查看某个数据逻辑的流程。而通过自定义 Hooks 我们能够将实现这个业务逻辑的代码全部整合到一处,最终达到业务逻辑的复用。

function useUserInfo(name) {

  const [user, setUser] = useState({});

  useEffect(() => {

    fetch(url, {name}).then(user => setUser(user));

  }, [name]);

  return user;

}

function User({name}) {

  const user = useUserInfo(name);

  return <div>{user.name}</div>

}

我们可以从下面的视频中一窥 Hooks 的魅力,同颜色的表示是同一个业务逻辑,最终同颜色的代码都被归置到一处实现了逻辑的解耦。

12fc879a554761a3e3fdbdfac962a449.pngvia: https://twitter.com/prchdk/status/1056960391543062528

使用 Hooks 改进

那 Hooks 是否能应用于我们的业务场景中呢?通过我们之前的分析我们知道,实际上我们的目的就是为了抽离出广告数据获取以及广告的曝光和点击打点这两个通用的业务逻辑出来。所以 Hooks 针对逻辑的封装正好可以为我们所用。

import {useState, useEffect, useRef} from 'react';

import {useFetchMediav, useMediavEvent} from '@q/mediav';

function App() {

  const [newsData, setNewsData] = useState([]);

  const [adData] = useFetchMediav({id: "xxx", length: newsData.length / 2});

  useEffect(() => {

    const newsData = [...];

    setNewsData(newsData);

  }, []);

  const comps = [];

  for(let i = 0; i < newsData.length; i++) {

    comps.push(<NewsCard {...newsData[i]} key={`news-${i}`} />);

    if(i % 2) { comps.push(<AdCard data={adData[Math.floor(i/2)]} key={`ad-${i}`} />); }

  }

  return (<div>{comp}</div>);

}

function AdCard({data}) {

  const ref = useRef(null);

  const bind = useMediavEvent(ref, data);

  return (<div className="gg" ref={ref} {...bind}>{data.title}</div>);

}

使用useFetchMediav() 获取广告数据,通过 props 传入到 组件中,通过 useMediavEvent() 获取打点相关的方法,并绑定到对应的元素上。使用 Hooks 修改之后的代码不仅复用性提高了,整体代码的逻辑也变的更加可阅读起来。

后记

当然 Hooks 本身也不是没有缺点。为了在无状态的函数组件中创造去有状态的 Hooks,势必是需要通过副作用将每个 Hooks 缓存在组件中的。而我们没有指定 id 之类的东西,React 是如何区分每一个 Hooks 的呢?答案就是通过调用顺序。内部通过数组(链表?)根据调用顺序依次记录。为了遵守这个规则,Hooks 要求我们不能在if等会动态执行的地方进行 Hooks 的定义,因为这样有可能会导致 Hooks 执行顺序发生变化。其次useEffect() 合并了多个生命周期,某些 Effect 需要在哪些生命周期执行以及如何控制其仅在这些生命周期执行,这些都对开发者带来了更大的挑战。稍微处理不当的话,很可能会造成页面的性能问题。

参考资料

  • React Today and Tomorrow and 90% Cleaner React With Hooks
  • 你想知道的React组件设计模式这里都有(上)
  • 你想知道的React组件设计模式这里都有(下)

关注我们

界世的你当不

只做你的肩膀

a32b5ff3cb2d02282518bed15d436d25.png4d8b71e7b52d9be3aba8a09e72d0f8ca.png

 360官方技术公众号 

技术干货|一手资讯|精彩活动

空·

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/336608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows内存修改初篇

​ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <windows.h>BOOL FindFirst(DWORD dwValue);//对目标进程空间进行查找 BOOL FindNext(DWORD dwValue);//对目标空间进行2&#xff0c;3&#xff0c;4。。查找//查找数据的地址列表 DWORD g_arList…

斐波那契实现阶乘js_功能与命令式编程。 Java 8中的斐波那契,素数和阶乘

斐波那契实现阶乘js有多种编程风格/范例&#xff0c;但是两个著名的风格是Imperative和Functional 。 命令式编程是最主要的范例&#xff0c;因为几乎所有主流语言&#xff08;C &#xff0c;Java&#xff0c;C&#xff03;&#xff09;都在推广它。 但是在最近几年中&#xf…

cacti不能实时刷新流量图_介绍一种编码帧内刷新算法

0.引言本文主要介绍一种帧内刷新算法&#xff0c;解决I帧太大带来的延迟问题&#xff0c;可以在调优时&#xff0c;值得借鉴。帧内刷新技术避免 I 帧尖峰带来的带宽压力&#xff0c;可以有效地降低视频通信中的缓冲区延迟。帧内刷新算法是一种视频错误恢复的方法&#xff0c;通…

python kotlin_Java和Python中类似Kotlin的生成器,续:附加参数

python kotlin介绍 在今天的文章中&#xff0c;我们将继续上周的文章&#xff0c;内容涉及使用Java和Python创建类似Kotlin的构建器&#xff0c;并扩展了构建器API以采用一些可选参数以提高灵活性。 我们继续我们HTML示例&#xff0c;尝试添加标记属性&#xff0c;例如class&am…

按钮自动居中布局_CSS布局技巧

css实现左右布局和居中布局显示是前端进行页面设计的基础&#xff0c;也是全面了解并学习css一个很好的切入点&#xff0c;因为其中会涉及到对许多css基础点的认知。实现css入门&#xff0c;理解左右布局的实现方式是必经之路&#xff0c;同时也能使我们在项目中涉及前端编码的…

jooq_jOOQ星期二:拉斐尔·温特豪德(Rafael Winterhalter)正在与字节好友合作字节码...

jooq欢迎来到jOOQ Tuesdays系列。 在本系列文章中&#xff0c;我们每隔一个月的第三个星期二发布一篇文章&#xff0c;从jOOQ的角度采访我们发现该行业令人兴奋的人。 这包括从事SQL&#xff0c;Java&#xff0c;开放源代码以及各种其他相关主题的人员。 我们很高兴在第七版中…

多方法接口回调_啊?Java反射遇到接口

本文适合有点Java反射基础的同学&#xff0c;在Java反射调用方法时遇到接口参数是一件很蛋疼的事情。在反射调用方法时需要传参数&#xff0c;像传递基本数据类型进去用就完事&#xff0c;传个对象进去怎么整都没关系&#xff0c;因为你在外部有对象的引用&#xff0c;但 如果需…

0与1世界的初级编程篇之C语言

C语言是一门面向过程的计算机编程语言&#xff0c;与C、Java等面向对象编程语言有所不同。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言描述问题比汇编语言迅速&#xff0c;工作量小…

swarm部署集群_WildFly Swarm –将Java EE应用程序部署为独立的Jar

swarm部署集群WildFly Swarm提供了一个简单的解决方案&#xff0c;用于将Java EE应用程序部署为独立的Jar文件。 这使得部署应用程序非常容易&#xff0c;尤其是REST或Web服务。 Swarm在这方面与Spring Boot非常相似&#xff0c;因为它可以快速开发Web&#xff08;.War&#xf…

QT 手动创建信号函数 与 槽函数

自定义信号槽必须要有Q_OBJCT 自己通过关键字signals创建信号函数,只声明信号函数即可&#xff0c;系统会自动定义它。 利用 public slots: 声明一个槽函数&#xff0c;槽函数不会自动生成&#xff0c;并且去定义它。

九九乘法表代码口述_利用随机函数实现座次表的随机排座

昨天听完讲座&#xff0c;晚上忍不住写了份学后感&#xff0c;今天有老师在文章下留言问如何实现随机排座&#xff0c;上午在快速理赔中心处理交通事故&#xff0c;处理完后将车开去4S店维修&#xff0c;回来后给娃做完中饭就开始写代码实现这个功能&#xff0c;因为有C功底&am…

jep使用_JEP 277“增强弃用”非常好。 但这是一个更好的选择

jep使用维护API很难。 我们正在维护非常复杂的jOOQ API。 但是就语义版本而言&#xff0c;我们遵循相对宽松的规则 。 当您阅读Brian Goetz和其他人关于在JDK中保持向后兼容性的评论时&#xff0c;我只能对他们的工作表示敬意。 显然&#xff0c;我们都希望最终移除Vector &a…

python 港股交易数据_GitHub - 116pythonZS/futuquant: 富途量化平台 API

FutuQuant - 富途量化投资平台 (Futu Quant Trading API)简介​FutuQuant开源项目可以满足使用富途牛牛软件进行量化投资的需求, 提供包括Python接口、Json接口的行情及交易的API。安装pip install futuquant注: 本API同时兼容Python2和Python3, 推荐安装anaconda环境&#xff…

LeetCode 16.01 交换两数

原题链接 交换 a,b两个数&#xff0c;不开辟额外控件 解析&#xff1a; 设a甲 b乙 aa^b; 转换 a甲^乙 b乙 ba^b; 转换 b甲^乙^乙 因为 乙^乙0 甲^0甲 &#xff0c;所以b甲 aa^b; 转换 a甲^乙^甲 同上所述&#xff0c;所以 a乙 至此…

古巴比伦乘法_古巴平台中的通用过滤器–类固醇上的excel过滤器

古巴比伦乘法正如我上次承诺的那样&#xff0c;我计划浏览该平台的某些功能&#xff0c;这些功能我认为非常有价值。 所以我将在这里做一些系列。 从明显的用户界面&#xff0c;过滤&#xff0c;安全性到一些高级功能&#xff08;如Web Portal&#xff0c;可扩展性&#xff0c;…

excel运行没反应_Excel数据很少文件却很大,问题出在哪里呢?两种方法轻松解决...

经常和Excel打交道的小伙伴可能会有这样的困惑&#xff0c;一个Excel工作簿中的数据明明很少&#xff0c;文件所占的空间却很大。打开这种Excel工作簿后&#xff0c;电脑CPU占用率瞬间飙升&#xff0c;甚至遇见工作簿没有响应的情况出现。遇见这样的工作簿让人窝火&#xff0c;…

约瑟夫环问题题解

按照1-8顺寻存储&#xff0c;起始位置为3&#xff0c;数到4的人出列。 #include<iostream> using namespace std; typedef struct node {int num;struct node* next; }Node; int main() {int n 8, k 3, m 4;Node*h (Node*)malloc(sizeof(Node));h->num 1;h->n…

java lambda::_基准测试:Java 8 Lambda和流如何使您的代码慢5倍

java lambda::与长期的实现相比&#xff0c;Java 8 lambda和流的性能如何&#xff1f; Lambda表达式和流在Java 8中受到了热烈的欢迎。这些是迄今为止很激动人心的功能&#xff0c;很长一段时间以来&#xff0c;它们就已经应用到Java中了。 新的语言功能使我们可以在代码中采用…

如何在java中实现小数点自增_java编个计算器怎么在加入小数点

展开全部我做的可以运行&#xff0c;你看看吧&#xff01;import java.awt.*;import java.awt.event.*;import java.lang.*;import javax.swing.*;public class Counter extends Frame{//声明三个面板的布局GridLayout gl1,gl2,gl3;Panel p0,p1,p2,p3;JTextField tf1;TextField…

数组中一个属出现奇数次,其他数都出现偶数次.寻找出这个出现奇数次的数

#include<iostream> #include<vector> using namespace std; int singleNumber(vector<int>& nums) {int eo 0;for (auto b : nums)eo ^ b;return eo; }