前言

前些天在订阅的公众号中看到了以前阮一峰老师写过的一篇文章,「如何做到 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. getDomNode() {
  4. return this.refs.root; // 获取 Dom Node
  5. }
  6. render() {
  7. return (
  8. <div ref="root">just a demo</div>
  9. );
  10. }
  11. }

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

  1. import React from 'react';
  2. class Demo extends React.Compoent {
  3. getRootNode() {
  4. return this.refs.root; // 获取根节点 Dom Node
  5. }
  6. getLeafNode() {
  7. return this.refs.leaf; // 获取叶节点 Dom Node
  8. }
  9. render() {
  10. return (
  11. <div ref="root">
  12. <div ref="leaf">just a demo</div>
  13. </div>
  14. );
  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. }
  10. class Demo extends React.Compoent {
  11. getDomNode() {
  12. return this.refs.root; // 获取 Dom Node
  13. }
  14. getSubNode() {
  15. return ReactDOM.findDOMNode(this.refs.sub); // 获取子组件根节点
  16. }
  17. render() {
  18. return (
  19. <div ref="root">
  20. <Sub ref="sub" />
  21. </div>
  22. );
  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. }
  13. class Demo extends React.Compoent {
  14. getDomNode() {
  15. return this.rootNode; // 获取 Dom Node
  16. }
  17. getSubNode() {
  18. return this.sub.getDomNode(); // 获取子组件根节点
  19. }
  20. render() {
  21. return (
  22. <div ref={(c) => this.rootNode = c}>
  23. <Sub ref={(c) => this.sub = c} />
  24. </div>
  25. );
  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. addItemFromBottom() {
  12. this.setState({
  13. list: this.state.list.concat([4]),
  14. });
  15. }
  16. addItemFromTop() {
  17. this.setState({
  18. list: [0].concat(this.state.list),
  19. });
  20. }
  21. deleteItem() {
  22. const newList = [...this.state.list];
  23. newList.pop();
  24. this.setState({
  25. list: newList,
  26. });
  27. }
  28. render() {
  29. return (
  30. <div>
  31. {this.state.list.map((item) => <div>{item}</div>)}
  32. <button onClick={this.addItemFromBottom}>尾部插入 Dom 元素</button>
  33. <button onClick={this.addItemFromTop}>头部插入 Dom 元素</button>
  34. <button onClick={this.deleteItem}>删除 Dom 元素</button>
  35. </div>
  36. );
  37. }
  38. }

三、事件的监听

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. }
  15. }

这里有一个小细节就是 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. componentDidMount() {
  7. doSomethingAfterRender(); // 在组件渲染完成后执行一些操作,如远程获取数据,检测 DOM 变化等。
  8. }
  9. render() {
  10. return (
  11. <div>just a demo</div>
  12. );
  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. componentDidMount() {
  7. this.rootNode.scrollLeft = 10; // 渲染后将外层的滚动调至 10px
  8. }
  9. render() {
  10. return (
  11. <div
  12. ref={(c) => this.rootNode = c}
  13. style={{ width: '100px', overflow: 'auto' }}
  14. >
  15. <div style={{ width: '1000px' }}>just a demo</div>
  16. </div>
  17. );
  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. getLink() {
  12. alert(this.state.link);
  13. }
  14. editLink() {
  15. this.setState({
  16. link: '//www.tmall.com',
  17. });
  18. }
  19. render() {
  20. return (
  21. <div>
  22. <a href={this.state.link}>跳转链接</a>
  23. <button onClick={this.getLink}>获取链接</button>
  24. <button onClick={this.editLink}>修改链接</button>
  25. </div>
  26. );
  27. }
  28. }

七、addClass/removeClass/toggleClass

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

  1. .fn-show {
  2. display: block;
  3. }
  4. .fn-hide {
  5. display: none;
  6. }
  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. changeShow() {
  12. this.setState({
  13. show: !this.state.show,
  14. });
  15. }
  16. render() {
  17. return (
  18. <div>
  19. <a
  20. href="//www.taobao.com"
  21. className={classnames({
  22. 'fn-show': this.state.show,
  23. 'fn-hide': !this.state.show,
  24. })}
  25. >
  26. 跳转链接
  27. </a>
  28. <button onClick={this.changeShow}>改变现实状态</button>
  29. </div>
  30. );
  31. }
  32. }

八、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. handleClick() {
  11. this.setState({
  12. background: 'black',
  13. });
  14. }
  15. render() {
  16. return (
  17. <div
  18. style={{
  19. background: this.state.background,
  20. }}
  21. >
  22. just a demo
  23. <button>change Background Color</button>
  24. </div>
  25. );
  26. }
  27. }

九、数据存储

比起 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

如何在React中做到jQuery-free相关推荐

  1. 如何在React中使用Typescript

    TypeScript can be very helpful to React developers. TypeScript对React开发人员可能非常有帮助. In this video, Ben ...

  2. boost::unorder_map如何插入元素_「React」如何在React中优雅的实现动画

    最简单的动画组件实现 动画的本质,无非就是一个状态样式到另一个状态样式的过渡.最简单的动画组件,我们只需要指定两个状态的样式(进入的样式,离开的样式),以及一个开关(控制状态),即可完成. codep ...

  3. react 中渲染html_如何在React中识别和解决浪费的渲染

    react 中渲染html by Nayeem Reza 通过Nayeem Reza 如何在React中识别和解决浪费的渲染 (How to identify and resolve wasted r ...

  4. 如何在React中从其父组件更改子组件的状态

    by Johny Thomas 约翰尼·托马斯(Johny Thomas) 如何在React中从其父组件更改子组件的状态 (How to change the state of a child com ...

  5. 如何在React中使用功能组件

    Have you wondered how to create a component in React? 您是否想过如何在React中创建组件? To answer, it is as simple ...

  6. 如何在React中使用gRPC-web

    by Mohak Puri 由Mohak Puri 如何在React中使用gRPC-web (How to use gRPC-web with React) For the past few mont ...

  7. php 循环 post,如何在php中使用jQuery递归调用POST循环请求

    如何在php中使用jQuery递归调用POST循环请求 发布时间:2021-01-28 17:37:18 来源:亿速云 阅读:79 作者:Leah 这期内容当中小编将会给大家带来有关如何在php中使用 ...

  8. react网格生成_如何在React中构建实时可编辑数据网格

    react网格生成 by Peter Mbanugo 彼得·姆巴努戈(Peter Mbanugo) 如何在React中构建实时可编辑数据网格 (How to Build a Real-time Edi ...

  9. html中加入echarts,如何在react中使用echarts

    如何在react中echarts 1.安装echarts包npm install echarts --save 2.react使用echartsconst echarts = require('ech ...

最新文章

  1. 快速排序原理及代码模板
  2. requestLayout 无效
  3. python报错输出到日志_Python日志记录和子进程输出和错误流
  4. oracle分区表学习及应用
  5. Dubbo源码分析:ProxyFactory
  6. 使用bat快速创建cocos2d-x模板
  7. vijos-1003等价表达式
  8. Sql Server 分区
  9. [洛谷1681]最大正方形II
  10. COGS 265 线段覆盖
  11. CodeForces 453A Little Pony and Expected Maximum
  12. Hadoop环境搭建学习之Hive(4)
  13. SmartPhone和PPC手机的区别
  14. ChromeOS 体验
  15. 牛刀杀鸡-开源社区API之抢楼大作战
  16. GIS基础制图之地形图
  17. win10无法打开 巫师3 等steam游戏
  18. 测试版ios15怎么信任软件,苹果ios15信任的描述文件在哪?苹果ios15授权信任怎么设置?...
  19. 隔离幻想游戏的木马广告
  20. 程序员千万别去外包公司!

热门文章

  1. GDCM:gdcm::Writer的测试程序
  2. C++如何使用puff()的示例
  3. DCMTK:使用dcmsr API创建示例结构化报告
  4. C++将带ui界面的qt工程封装为动态库dll
  5. VTK:相互作用之Assembly
  6. OpenCV PCA与指定的保留差异量的实例(附完整代码)
  7. OpenCV色彩校正模型MCC
  8. C++静态多态与动态多态
  9. snmp linux arm,Net-SNMP的交叉编译 for ARM64
  10. Minimum supported Gradle version is 6.5. Current version is 6.1.1. If using the gradle wrapper, try