当前位置: 首页 > 科技观察

React前言如何实现jQuery-free

时间:2023-03-12 04:21:54 科技观察

前几天在订阅的公众号看到阮一峰老师写的一篇文章《如何实现jQuery-free?》。本文所讨论的问题在今天仍然没有过时。一些讨论主要是关于具有新内核的现代浏览器的标准DOMAPI。遗憾的是,在目前的发展环境下,我们仍然不能完全抛弃它。IE,大多数情况下我们至少要兼容IE8,这让我们无法完全实现文章中提到的一些观点,而这篇文章也是受到***的启发,沿袭了阮老师文章的思路讨论如何在React中练习IE8兼容的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。(注:React15.x版本不再兼容IE8,所以本文讨论的React依然是0.14.x版本,同时为了便于理解,本文基本以ES6的形式声明组件class而不是纯函数。)1.选择DOM元素在jQuery中,我们已经很熟悉使用sizzle选择器来完成DOM元素的选择。在React中,我们可以使用ref来更具体地获取元素。importReactfrom'react';classDemoextendsReact.Compoent{getDomNode(){returnthis.refs.root;//获取DomNode}render(){return(justademo

);}}这个最简单如果有多层嵌套结构,获取节点的方式?没关系。importReactfrom'react';classDemoextendsReact.Compoent{getRootNode(){returnthis.refs.root;//获取根节点DomNode}getLeafNode(){returnthis.refs.leaf;//获取叶子节点DomNode}render(){return(justademo
);}}组件和组件嵌套怎么办?没关系,父组件还是可以拿到子组件的根节点的。importReactfrom'react';importReactDOMfrom'react-dom';classSubextendsReact.Compoent{render(){return(
asubcomponent
);}}classDemoextendsReact.Compoent{getDomNode(){returnthis.refs.root;//GetDomNode}getSubNode(){returnReactDOM.findDOMNode(this.refs.sub);//获取子组件根节点}render(){return(
);}}上面使用了一个比较通俗易懂的API来解释Ref的用法,但是其中包含了一些React不推荐的方法,很快就会被丢弃。如果我们使用React推荐的写法,我们可以这样写。importReactfrom'react';importReactDOMfrom'react-dom';classSubextendsReact.Component{getDomNode(){returnthis.rootNode;}render(){return(this.rootNode=c}>asubcomponent);}}classDemoextendsReact.Compoent{getDomNode(){returnthis.rootNode;//获取DomNode}getSubNode(){returnthis.sub.getDomNode();//获取子组件根节点}render(){return(this.rootNode=c}>this.sub=c}/>);}}有人会问,如何获取子组件As对于父组件的DomNode,从React单向数据流的角度来说,遇到这种情况应该通过回调的方式通知父组件,然后让父组件决定如何修改Node。实际上,父组件获取子组件的Node状态的情况很少,大多数情况下,我们都是通过props将变化传递给子组件,从而获取子组件的Node。更多情况下是为了避免大量重新渲染去修改一些Node属性(比如scrollLeft)。2、DOM操作jQuery提供了丰富的操作方法,但是有时候一个一个地操作DOM元素真的很烦人而且容易出错。React采用数据驱动的思想,通过改变视图对应的数据,轻松实现对DOM操作的增删改查。classDemo扩展React.Compoent{constructor(props){super(props);this.state={list:[1,2,3],};this.addItemFromBottom=this.addItemFromBottom.bind(this);this.addItemFromTop=这。addItemFromTop.bind(this);this.deleteItem=this.deleteItem.bind(this);}addItemFromBottom(){this.setState({list:this.state.list.concat([4]),});}addItemFromTop(){this.setState({list:[0].concat(this.state.list),});}deleteItem(){constnewList=[...this.state.list];newList.pop();this.setState({list:newList,});}render(){return(
{this.state.list.map((item)=>
{item}
)}在末尾插入Dom元素在头部插入Dom元素删除Dom元素
);}}3.事件监听React通过根节点代理的方式实现了一个非常优雅的事件监听方案。组件卸载时,不需要自己处理内存回收相关问题,非常方便。importReactfrom'react';classDemoextendsReact.Component{constructor(props){super(props);this.handleClick=this.handleClick.bind(this);}handleClick(){alert('我是弹窗');}render(){return(点我弹出弹框);}}这里有一个小细节就是bind的时机,bind是维护context的对应的函数,虽然也可以在onClick中有bind,但是这里我们选择在构造函数中进行bind,因为前者每次渲染都会执行一次bind,返回一个新的函数,比较耗性能。但是React的事件监听毕竟只能监听到根组件,我们经常要监听window/document上的事件,比如resize,scroll,还有一些React处理不好的事件,比如scroll,allof这需要我们自己解决。事件监听为了屏蔽差异需要做很多工作。在这里,推荐一个第三方库来完成这部分工作,add-dom-event-listener,用法和原来的略有不同,因为这个库不是为了我在做polyfill而设计的,而是用法还是很简单的。varaddEventListener=require('add-dom-event-listener');varhandler=addEventListener(document.body,'click',function(e){console.log(e.target);//worksforieconsole.log(e.nativeEvent);//nativedomevent});handler.remove();//detacheventlistener另一种选择是bean,它实现了IE6+级别的兼容。4、事件触发与事件监听相同。无论是Dom事件还是自定义事件,都有优秀的第三方库来帮助我们处理。如果是DOM事件,我们推荐bean。如果是自定义事件,我们推荐PubSubJS。5.document.readyReact是一个视图层框架。通常,页面只有一个根节点div用于渲染React页面组件。所以document.ready只需要将脚本放到这个div后面就可以执行了。对于渲染后的回调,我们可以使用React提供的componentDidMount生命周期。importReactfrom'react';classDemoextendsReact.Component{constructor(props){super(props);}componentDidMount(){doSomethingAfterRender();//在组件渲染完成后进行一些操作,比如远程获取数据,检测DOM变化,etc.}render(){return(
justademo
);}}6.attr方法jQuery使用attr方法获取Dom元素的属性。在React中,还可以使用Ref直接读取DOM元素的属性。importReactfrom'react';classDemoextendsReact.Component{constructor(props){super(props);}componentDidMount(){this.rootNode.scrollLeft=10;//渲染后调整外层滚动为10px}render(){return(this.rootNode=c}style={{width:'100px',overflow:'auto'}}>justademo);}}然而,在大多数情况下,我们根本不需要这样做,因为React的单向数据流和数据驱动渲染,我们可以轻松获取和修改大部分无需通过DOM我们需要的DOM属性。importReactfrom'react';classDemoextendsReact.Component{constructor(props){super(props);this.state={link:'//www.taobao.com',};this.getLink=this.getLink.bind(this);this.editLink=this.editLink.bind(this);}getLink(){alert(this.state.link);}editLink(){this.setState({link:'//www.tmall.com',});}render(){return(
跳转链接获取链接编辑链接
);}}VII.addClass/removeClass/toggleClass在jQuery时代,我们通常在获取Dom元素后通过addClass/removeClass来改变外观。通过数据驱动和第三方类名在React中修改样式从未如此简单。.fn-show{display:block;}.fn-hide{display:none;}importReactfrom'react';importclassnamesfrom'classnames';classDemoextendsReact.Component{constructor(props){super(props);this.state={show:true,};this.changeShow=this.changeShow.bind(this);}changeShow(){this.setState({show:!this.state.show,});}render(){return(
跳转链接改变现实状态
);}}8、cssjQuery的css方法用于设置DOM元素的style属性。在React中,我们可以直接设置DOM的样式属性。如果要改,就用数据来驱动,就像上面的类一样。importReactfrom'react';classDemoextendsReact.Component{constructor(){super(props);this.state={backgorund:'white',};this.handleClick=this.handleClick.bind(this);}handleClick(){this.setState({background:'black',});}render(){return(justademo);}}九、数据存储与jQuery相比,React更擅长管理数据。我们不需要像jQuery那样把数据放入Dom元素的属性中,而是使用状态或者内部变量(this.xxx)来保存,在整个生命周期中,我们可以获取这些数据进行比较和修改。10、AjaxAjax在处理兼容性问题上确实让人头疼。不说兼容各种形式的Xhr,jsonp这个不属于ajax的功能也要同时考虑。幸运的是,已经有很好的Natty-fetch的第三方库帮助我们解决了这个问题。这里推荐natty-fetch,一个兼容IE8的fetch库,在API设计上接近fetch标准,同时保留了类似jQuery的接口。熟悉$.ajax应该可以快速上手。11、动画React在动画方面提供了一个插件ReactCSSTransitionGroup及其低级版本ReactTransitionGroup。注意,这里的低版本不是降级版本,而是更基础的版本,暴露了更多的API。这个插件的灵感来自于Angular的ng-animate,设计思路基本一致。通过指定Transition的类名,如example,在元素进入和退出时添加对应的类名,从而实现CSS3动画。比如本例中,进入场景时会在对应的元素中添加example-enter和example-enter-active,退出时会在类名中添加example-leave和example-leave-active。当然你也可以指定不同的入口和出口类名。对于入口,React也区分了两种,一种是ReactCSSTransitionGroup第一次渲染时(出现),另一种是ReactCSSTransitionGroup渲染完成后插入新元素时(进入)。可以使用props单独配置此条目以禁用或修改超时时间。具体例子在上面给出的链接中有详细的例子和说明,本文不再赘述。但是这个插件最多只是提供动画解决方案。如果我想在动画期间做其他事情怎么办?他也没办法,轮到ReactTransitionGroup上场了。ReactTransitionGroup为其包装的动画元素提供了六个新的生命周期:componentWillAppear(callback)、componentDidAppear()、componentWillEnter(callback)、componentDidEnter()、componentWillLeave(callback)、componentDidLeave()。这些钩子可以帮助我们做一些其他随着动画的进行需要做的事情。但是官方插件有个缺点。动画只在进入和退出时执行。如果我的组件不是挂载/卸载,而是只是隐藏和显示怎么办?这里推荐一个第三方库:rc-animate,从API设计上来说,他基本延续了ReactCSSTransitionGroup的思路,但是通过引入showProp的属性,他可以处理进入退出动画的情况显示和隐藏组件(只需将组件的关于显示/隐藏的属性传递给showProp即可),并且该库还提供了自己的钩子来实现出现/进入/离开时的回调。如果你说我不满足于仅仅进入和退出动画,我想实现类似鼠标拖动的实时动画。我需要的是一个js动画库。这里推荐一个第三方库:react-motion,react-motion的一大特点就是不同于以往使用贝塞尔曲线来定义动画节奏。引入了刚度和阻尼等弹簧系数来定义动画。笔者认为,与其纠结于动画时长,难以掌握BezierNotation,不如通过不断调整刚度和阻尼来调试出最想要的弹性效果才是最合理的。自述文件提供了一系列很酷的动画效果,例如这个可拖动列表。Motion通过指定defaultStyle和style实现js动画,将变化的样式返回给子组件。{interpolatingStyle=>}本文灵感来源于阮老师两年前的一篇文章,这篇文章的实际意义大于对未来技术的展望,希望能给正在使用React开发系统的同学一些启发。