如何在React中做到jQuery-free

前言

前些天在订阅的公众号中看到了以前阮一峰老师写过的一篇文章,「如何做到
jQuery-free?」。这篇文章讨论的问题,在今天来看仍不过时,其中的一些点的讨论主要是面向新内核现代浏览器的标准 DOM
API,很可惜的是在目前的开发环境下,我们仍然无法完全抛弃 IE,大部分情况下我们至少还要兼容到 IE
8,这一点使我们无法充分践行文章中提到的一些点,而本文也正是首次启发,顺着阮老师文章的思路来讨论如何在 React 中实战
IE8-compatible 的 jQuery-free。

首先我们仍要说的是,jQuery 是现在最流行的 JavaScript 工具库。在 W3techs 的统计中,目前全世界 70.6%
的网站在使用他,而 React 甚至还不到 0.1%,但 React 一个值得注意的趋势是,他在目前顶级流量网站中的使用率是最高的,比例达到了
16%。这一趋势也表明了目前整个前端界的技术趋势,但 70.6% 的数字也在告诉我们,jQuery 在 JS
库中的王者地位,即使使用了React,也可能因为各种各样的原因,还要和 jQuery 来配合使用。但 React
本身的体积已经让我们对任何一个重库产生了不适反应,为了兼容 IE8,我们仍然需要使用 1.x 的 jQuery
版本,但当时设计上的缺陷使得我们无法像 lodash 那样按需获取。而 React 和 jsx 的强大,又使得我们不需要了 jQuery
的大部分功能。从这个角度来看,他臃肿的体积让开发者更加难以忍受,jQuery-free 势在必行。

下面就顺着阮老师当年的思路,来讨论如何使用 React 自带的强大功能,和一些良心第三方库屏蔽兼容性,来取代 jQuery 的主要功能,做到 jQuery-free。

(注:React 15.x 版本已经不再兼容 IE8,因此本文讨论的 React 仍是 0.14.x 的版本,同时为了易于理解,本文也基本上以 ES6 class 的方式来声明组件,而不采用 pure function。)

一、选取 DOM 元素

在 jQuery 中,我们已经熟悉了使用 sizzle 选择器来完成 DOM 元素的选取。而在 React 中,我们可以使用 ref 来更有针对性的获取元素。


  1. import React from 'react'; 
  2. class Demo extends React.Compoent { 
  3.  
  4.     getDomNode() { 
  5.         return this.refs.root; // 获取 Dom Node 
  6.     } 
  7.     render() { 
  8.         return ( 
  9.             <div ref="root">just a demo</div> 
  10.         ); 
  11.     } 

这是最简单的获取 node 的方式,如果有多层结构嵌套呢?没有关系。


  1. import React from 'react'; 
  2. class Demo extends React.Compoent { 
  3.  
  4.     getRootNode() { 
  5.         return this.refs.root; // 获取根节点 Dom Node 
  6.     } 
  7.     getLeafNode() { 
  8.         return this.refs.leaf; // 获取叶节点 Dom Node 
  9.     } 
  10.     render() { 
  11.         return ( 
  12.             <div ref="root"> 
  13.                 <div ref="leaf">just a demo</div> 
  14.             </div> 
  15.         ); 
  16.     } 

如果是组件和组件嵌套呢?也没关系,父组件仍然可以拿到子组件的根节点。


  1. import React from 'react'; 
  2. import ReactDOM from 'react-dom'; 
  3. class Sub extends React.Compoent { 
  4.     render() { 
  5.         return ( 
  6.             <div>a sub component</div> 
  7.         ); 
  8.     } 
  9. class Demo extends React.Compoent { 
  10.  
  11.     getDomNode() { 
  12.         return this.refs.root; // 获取 Dom Node 
  13.     } 
  14.      
  15.     getSubNode() { 
  16.         return ReactDOM.findDOMNode(this.refs.sub); // 获取子组件根节点 
  17.     } 
  18.     render() { 
  19.         return ( 
  20.             <div ref="root"> 
  21.                 <Sub ref="sub" /> 
  22.             </div> 
  23.         ); 
  24.     } 

上面使用了比较易懂的 API 来解释 Ref 的用法,但里面包含了一些现在 React 不太推荐和即将废弃的方法,如果用 React 推荐的写法,我们可以这样写。


  1. import React from 'react'; 
  2. import ReactDOM from 'react-dom'; 
  3. class Sub extends React.Compoent { 
  4.     getDomNode() { 
  5.         return this.rootNode; 
  6.     } 
  7.     render() { 
  8.         return ( 
  9.             <div ref={(c) => this.rootNode = c}>a sub component</div> 
  10.         ); 
  11.     } 
  12. class Demo extends React.Compoent { 
  13.  
  14.     getDomNode() { 
  15.         return this.rootNode; // 获取 Dom Node 
  16.     } 
  17.      
  18.     getSubNode() { 
  19.         return this.sub.getDomNode(); // 获取子组件根节点 
  20.     } 
  21.     render() { 
  22.         return ( 
  23.             <div ref={(c) => this.rootNode = c}> 
  24.                 <Sub ref={(c) => this.sub = c} /> 
  25.             </div> 
  26.         ); 
  27.     } 

有人可能会问,那子组件怎么拿父组件的 Dom Node 呢,从 React
的单向数据流角度出发,遇到这种情况我们应该通过回调通知给父组件,再由父组件自行判断如何修改 Node,其实父组件拿子组件的 Node
情况也很少,大多数情况下我们是通过 props 传递变化给子组件,获取子组件
Node,更多的情况下是为了避开大量重新渲染去修改一些Node的属性(比如 scrollLeft)。

二、DOM 操作

jQuery 中提供了丰富的操作方法,但一个个操作 DOM 元素有的时候真的很烦人并且容易出错。React 通过数据驱动的思想,通过改变 view 对应的数据,轻松实现 DOM 的增删操作。


  1. class Demo extends React.Compoent { 
  2.     constructor(props) { 
  3.         super(props); 
  4.         this.state = { 
  5.             list: [1, 2, 3], 
  6.         }; 
  7.         this.addItemFromBottom = this.addItemFromBottom.bind(this); 
  8.         this.addItemFromTop = this.addItemFromTop.bind(this); 
  9.         this.deleteItem = this.deleteItem.bind(this); 
  10.     } 
  11.      
  12.     addItemFromBottom() { 
  13.         this.setState({ 
  14.             list: this.state.list.concat([4]), 
  15.         }); 
  16.     } 
  17.      
  18.     addItemFromTop() { 
  19.         this.setState({ 
  20.             list: [0].concat(this.state.list), 
  21.         }); 
  22.     } 
  23.      
  24.     deleteItem() { 
  25.         const newList = [...this.state.list]; 
  26.         newList.pop(); 
  27.         this.setState({ 
  28.             list: newList, 
  29.         }); 
  30.     } 
  31.      
  32.     render() { 
  33.         return ( 
  34.             <div> 
  35.                 {this.state.list.map((item) => <div>{item}</div>)} 
  36.                 <button onClick={this.addItemFromBottom}>尾部插入 Dom 元素</button> 
  37.                 <button onClick={this.addItemFromTop}>头部插入 Dom 元素</button> 
  38.                 <button onClick={this.deleteItem}>删除 Dom 元素</button> 
  39.             </div> 
  40.         ); 
  41.     } 

三、事件的监听

React 通过根节点代理的方式,实现了一套很优雅的事件监听方案,在组件 unmount 时也不需要自己去处理内存回收相关的问题,非常的方便。


  1. import React from 'react'; 
  2. class Demo extends React.Component { 
  3.     constructor(props) { 
  4.         super(props); 
  5.         this.handleClick = this.handleClick.bind(this); 
  6.     } 
  7.     handleClick() { 
  8.         alert('我是弹窗'); 
  9.     } 
  10.     render() { 
  11.         return ( 
  12.             <div onClick={this.handleClick}>点击我弹出弹框</div> 
  13.         ); 
  14.     } 

这里有一个小细节就是 bind 的时机,bind 是为了保持相应函数的上下文,虽然也可以在 onClick 那里 bind,但这里选择在
constructor 里 bind 是因为前者会在每次 render 的时候都进行一次 bind,返回一个新函数,是比较消耗性能的做法。

但 React 的事件监听,毕竟只能监听至 root component,而我们在很多时候要去监听 window/document
上的事件,如果 resize、scroll,还有一些 React 处理不好的事件,比如
scroll,这些都需要我们自己来解决。事件监听为了屏蔽差异性需要做很多的工作,这里像大家推荐一个第三方库来完成这部分的工作,add-dom-event-listener,用法和原生的稍有区别,是因为这个库并不旨在做
polyfill,但用法还是很简单。


  1. var addEventListener = require('add-dom-event-listener'); 
  2. var handler = addEventListener(document.body, 'click', function(e){ 
  3.   console.log(e.target); // works for ie 
  4.   console.log(e.nativeEvent); // native dom event 
  5. }); 
  6. handler.remove(); // detach event listener 

另一个选择是 bean,达到了 IE6+ 级别的兼容性。

四、事件的触发

和事件监听一样,无论是 Dom 事件还是自定义事件,都有很优秀的第三方库帮我们去处理,如果是 DOM 事件,推荐 bean,如果是自定义事件的话,推荐 PubSubJS。

五、document.ready

React 作为一个 view 层框架,通常情况下页面只有一个用于渲染 React 页面组件的根节点 div,因此
document.ready,只需把脚本放在这个 div 后面执行即可。而对于渲染完成后的回调,我们可以使用 React 提供的
componentDidMount 生命周期。


  1. import React from 'react'; 
  2. class Demo extends React.Component { 
  3.     constructor(props) { 
  4.         super(props); 
  5.     } 
  6.      
  7.     componentDidMount() { 
  8.         doSomethingAfterRender(); // 在组件渲染完成后执行一些操作,如远程获取数据,检测 DOM 变化等。 
  9.     } 
  10.     render() { 
  11.         return ( 
  12.             <div>just a demo</div> 
  13.         ); 
  14.     } 

六、attr 方法

jQuery 使用 attr 方法,获取 Dom 元素的属性。在 React 中也可以配合 Ref 直接读取 DOM 元素的属性。


  1. import React from 'react'; 
  2. class Demo extends React.Component { 
  3.     constructor(props) { 
  4.         super(props); 
  5.     } 
  6.      
  7.     componentDidMount() { 
  8.         this.rootNode.scrollLeft = 10; // 渲染后将外层的滚动调至 10px 
  9.     } 
  10.     render() { 
  11.         return ( 
  12.             <div  
  13.                 ref={(c) => this.rootNode = c}  
  14.                 style={{ width: '100px', overflow: 'auto' }} 
  15.             >  
  16.                 <div style={{ width: '1000px' }}>just a demo</div> 
  17.             </div> 
  18.         ); 
  19.     } 

但是,在大部分的情况下,我们完全不需要做,因为 React 的单向数据流和数据驱动渲染,我们可以不通过 DOM,轻松拿到和修改大部分我们需要的 DOM 属性。


  1. import React from 'react'; 
  2. class Demo extends React.Component { 
  3.     constructor(props) { 
  4.         super(props); 
  5.         this.state = { 
  6.             link: '//www.taobao.com', 
  7.         }; 
  8.         this.getLink = this.getLink.bind(this); 
  9.         this.editLink = this.editLink.bind(this); 
  10.     } 
  11.      
  12.     getLink() { 
  13.         alert(this.state.link); 
  14.     } 
  15.      
  16.     editLink() { 
  17.         this.setState({ 
  18.             link: '//www.tmall.com', 
  19.         }); 
  20.     } 
  21.      
  22.     render() { 
  23.         return ( 
  24.             <div> 
  25.                 <a href={this.state.link}>跳转链接</a> 
  26.                 <button onClick={this.getLink}>获取链接</button> 
  27.                 <button onClick={this.editLink}>修改链接</button> 
  28.             </div> 
  29.         ); 
  30.     } 
  31.      

七、addClass/removeClass/toggleClass

在 jQuery 的时代,我们通常靠获取 Dom 元素后,再 addClass/removeClass 来改变外观。在 React 中通过数据驱动和第三库classnames 修改样式从未如此轻松。


  1. .fn-show { 
  2.     display: block; 
  3. .fn-hide { 
  4.     display: none; 
  5. }

  1. import React from 'react'; 
  2. import classnames from 'classnames'; 
  3. class Demo extends React.Component { 
  4.     constructor(props) { 
  5.         super(props); 
  6.         this.state = { 
  7.             show: true, 
  8.         }; 
  9.         this.changeShow = this.changeShow.bind(this); 
  10.     } 
  11.      
  12.     changeShow() { 
  13.         this.setState({ 
  14.             show: !this.state.show,  
  15.         }); 
  16.     } 
  17.      
  18.     render() { 
  19.         return ( 
  20.             <div> 
  21.                 <a  
  22.                     href="//www.taobao.com"  
  23.                     className={classnames({ 
  24.                         'fn-show': this.state.show, 
  25.                         'fn-hide': !this.state.show, 
  26.                     })} 
  27.                 > 
  28.                     跳转链接 
  29.                 </a> 
  30.                 <button onClick={this.changeShow}>改变现实状态</button> 
  31.             </div> 
  32.         ); 
  33.     } 
  34.      

八、css

jQuery 的 css 方法用于设置 DOM 元素的 style 属性,在 React 中,我们可以直接设置 DOM 的 style 属性,如果想改变,和上面的 class 一样,用数据去驱动。


  1. import React from 'react'; 
  2. class Demo extends React.Component { 
  3.     constructor() { 
  4.         super(props); 
  5.         this.state = { 
  6.             backgorund: 'white', 
  7.         }; 
  8.         this.handleClick = this.handleClick.bind(this); 
  9.     } 
  10.      
  11.     handleClick() { 
  12.         this.setState({ 
  13.             background: 'black', 
  14.         }); 
  15.     } 
  16.      
  17.     render() { 
  18.         return ( 
  19.             <div  
  20.                 style={{ 
  21.                     background: this.state.background, 
  22.                 }} 
  23.             > 
  24.                 just a demo 
  25.                 <button>change Background Color</button> 
  26.             </div> 
  27.         ); 
  28.     } 

九、数据存储

比起 jQuery,React 反而是更擅长管理数据,我们没有必要像 jQuery 时那样将数据放进 Dom 元素的属性里,而是利用 state 或者 内部变量(this.xxx) 来保存,在整个生命周期,我们都可以拿到这些数据进行比较和修改。

十、Ajax

Ajax 确实是在处理兼容性问题上一块令人比较头疼的地方,要兼容各种形式的 Xhr 不说,还有 jsonp 这个不属于 ajax
的功能也要同时考虑,好在已经有了很好的第三方库帮我们解决了这个问题,这里向大家推荐 natty-fetch,一个兼容 IE8 的fetch
库,在 API 设计上向 fetch 标准靠近,而又保留了和 jQuery 类似的接口,熟悉 $.ajax 应该可以很快的上手。

十一、动画

React 在动画方面提供了一个插件 ReactCSSTransitionGroup,和它的低级版本 ReactTransitionGroup,注意这里的低级并不是退化版本,而是更加基础的暴露更多 API 的版本。

这个插件的灵感来自于 Angular 的 ng-animate,在设计思路上也基本保持一致。通过指定 Transition 的类名,比如
example ,在元素进场和退场的时候分别加上对应的类名,以实现 CSS3 动画。例如本例中,进场会添加 example-enter 和
example-enter-active到对应的元素 ,而在退场 example-leave 和 example-leave-active
类名。当然你也可以指定不同的进场退场类名。而对应入场,React 也区分了两种类型,一种是 ReactCSSTransitionGroup
第一次渲染时(appear),而另一种是 ReactCSSTransitionGroup
已经渲染完成后,有新的元素插入进来(enter),这两种进场可以使用 prop
进行单独配置,禁止或者修改超时时长。具体的例子,在上面给出的链接中有详细的例子和说明,因此本文不再赘述。

但这个插件最多只提供了做动画的方案,如果我想在动画进行的过程中做一些其他事情呢?他就无能为力了,这时候就轮到
ReactTransitionGroup 出场了。ReactTransitionGroup
为他包裹的动画元素提供了六种新的生命周期:componentWillAppear(callback),
componentDidAppear(), componentWillEnter(callback),
componentDidEnter(),componentWillLeave(callback), componentDidLeave()。这些
hook 可以帮助我们完成一些随着动画进行需要做的其他事。

但官方提供的插件有一个不足点,动画只是在进场和出场时进行的,如果我的组件不是
mount/unmount,而只是隐藏和显示怎么办?这里推荐一个第三方库:rc-animate,从 API 设计上他基本上是延续了
ReactCSSTransitionGroup 的思路,但是通过引入 showProp这一属性,使他可以 handle
组件显示隐藏这一情况下的出入场动画(只要将组件关于 show/hide 的属性传给 showProp 即可),同时这个库也提供自己的
hook,来实现 appear/enter/leave 时的回调。

如果你说我并不满足只是进场和出场动画,我要实现类似鼠标拖动时的实时动画,我需要的是一个 js
动画库,这里向大家推荐一个第三方库:react-motion , react-motion
一个很大的特点是,有别以往使用贝塞尔曲线来定义动画节奏,引入了刚度和阻尼这些弹簧系数来定义动画,按照作者的说法,与其纠结动画时长和很难掌握的贝塞尔表示法,通过不断调整刚度和阻尼来调试出最想要的弹性效果才是最合理的。Readme
里提供了一系列的很炫的动画效果,比如这个 draggable list。Motion 通过指定
defaultStyle、style,传回给子组件正在变化中的 style,从而实现 js 动画。


  1. <Motion defaultStyle={{x: 0}} style={{x: spring(10)}}> 
  2.   {interpolatingStyle => <div style={interpolatingStyle} />} 
  3. </Motion> 

总结

本文的灵感来源于阮老师两年前的文章,这篇的实战意义更大于对未来技术的展望,希望能够给各位正在使用 React 开发系统的同学们一点启发。

作者:紅白

来源:51CTO

时间: 2024-10-03 14:41:08

如何在React中做到jQuery-free的相关文章

如何在SharePoint中利用Jquery Chosen创建新的人员选择器

基于SharePoint平台开发时,人员选择器使用频率是非常高的,但是原生的人员选择器使用太麻烦,而且非常笨拙,非常不友好,特别是对呆在政府部门的老爷们,要让他们手动输入人员,简直就是痴心妄想.总之一句话,越简单越好. 为了让客户满意,必须要对人员选择器进行改造,原生的PeopleEditor彻底抛弃.只能另辟蹊径,寻找适合的JQuery插件,创建新的人员选择器,分析了一下需求,可以归纳新的人员选择器必须支持如下情况: 支持人员的多选,比如像会议.通知需要对多人进行发送,当然也要支持删除. 对于

如何在jsp中使用jquery的ajax功能

$(document).ready(function() { $("a").click(function() { $("#decimal").val('这是一个例子'); $("#inputtext").val('控制text'); }); /* $("a").click(function() { alert("Hello world!"); }); */ }); function convertToDec

如何在React Native中写一个自定义模块

前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指两层含义:一是 Node.js 开放式模块登记和管理系统,另一种是 Node.js 默认的模块管理器,是一个命令行软件,用来安装和管理 node 模块.本文旨在探讨如何在 React Native 中写一个自定义的 npm 模块(类似于插件),并上传到 npm 上供他人使用. npm 使用介绍 np

如何在 React Native 中写一个自定义模块

前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指两层含义:一是 Node.js 开放式模块登记和管理系统,另一种是 Node.js 默认的模块管理器,是一个命令行软件,用来安装和管理 node 模块.本文旨在探讨如何在 React Native 中写一个自定义的 npm 模块(类似于插件),并上传到 npm 上供他人使用. npm 使用介绍 np

如何在PHP中判断一个网页请求是ajax请求还是普通请求

如何在PHP中判断一个网页请求是ajax请求还是普通请求?你可以通过传递参数的方法来 实现,例如使用如下网址请求:  /path/to/pkphp.com/script.php?ajax 在PHP脚本中使用如下方法判断: if(isset($_GET['ajax'])) { -这是一个ajax请求,然后- } else { -这不是一个ajax请求,然后- } 通过传递_GET参数的方法简单实现了网页请求的判断.但是如果需要这样的功能,这个 方法可能就有弊端,功能需求如下: 1.通过ajax请求

sql-急!!!如何在jsp中显示一个图片集,可以左右滑动的

问题描述 急!!!如何在jsp中显示一个图片集,可以左右滑动的 我现在找到一个插件fotorama,但是不知道怎么在jsp中引用,有大神会用吗? 解决方案 图片集是javascript做的网上有现成的你可以搜Javascript图片集插件就可以了:数据库里一般存放的是图片的路径的. 如果回答对你有帮助请采纳 解决方案二: 用一张表单独存这个图片信息,然后再页面遍历,可以用li标签,左右滑动的效果,可以自己写js算,也可以网上搜现成的 解决方案三: 同意楼上意见.... 解决方案四: 真的不要用数

传值-js中的值如何在jsp中获取

问题描述 js中的值如何在jsp中获取 解决方案 success回调里面直接组合添加到你的链接里面去就好了,还是原来你的页面上有添加评论的html,你需要给链接添加上ajax返回的shareid还是什么 解决方案二: 你是要在success里获得shareId后,把这个shareId放到的href里去吗? 如果是,很简单,你在js里用jquery拿到,然后设置的href属性. 解决方案三: request.getParameter("shareId") 解决方案四: 你 shareId

javascript-求教啊!如何在js中操作本窗口的菜单栏等的隐藏和本窗口的大小?

问题描述 求教啊!如何在js中操作本窗口的菜单栏等的隐藏和本窗口的大小? 在一个jsp页面中,我需要用js控制当前窗口的菜单栏.工具栏等为隐藏状态,同时还要控制当前窗口的大小,请问如何解决? 解决方案 采用jquery的animate操作菜单栏的隐藏及缩小放大该窗口,例子: if(isAllTable && !isAllOcx){ $(".right_content").animate({height:'234px'}); $("#cameraTabConte

节点内容-如何在javascript中获得节点对应的内容

问题描述 如何在javascript中获得节点对应的内容 <script type="text/javascript"> function city() { var arr=[["选择城市"],["海淀","东城","朝阳区"],["武汉","新洲","黄冈"], ["广州","珠海","