React数据传递之组件内部通信的方法
1.概述
脱离初级前端一段时间后会发现,写样式的时间越来越少,处理数据的时间越来越多。处理数据的过程也就是实现业务逻辑的过程,这在项目中无疑是最重要的。
所以学习前端框架,了解完基本语法后,接下来就要学习其如何进行数据传递。
Angular设计之初的一大亮点就是实现了数据的双向绑定,使用Vue一段时间后发现,所谓数据的双向绑定,组件内部唯一的应用场景就是form表单(input,textarea,select,radio),而这种场景下的数据双向绑定,即便框架内部没有实现,自己实现起来也非常简单。明白这一点后感觉之前认为React没有实现数据双向绑定很low的想法很幼稚。
对于React的数据传递,涉及两方面的内容:
- 组件内部的数据传递,典型的应用场景包括如何实现form表单双向数据绑定、如何绑定事件;
- 组件间的数据传递。包括父组件往子组件传递数据、子组件往父组件传递数据以及兄弟组件之间传递数据。
本文先讨论组件内部的数据传递。
2.组件内部数据传递
React组件内部通信主要分为两部分:数据展示与事件处理。
2.1数据展示
组件内部数据的展示和更新都是通过state来实现的,如果要使用state必须使用ES6的class定义组件。数据更新在双向数据绑定部分探讨,这部分仅讨论展示初始化数据。
如果你熟悉Vue,React的state对象相当于Vue的data对象
下面是一个纯展示数据的示例:
classAppextendsComponent{ constructor(props){ super(props); //初始化state this.state={ inputValue:"test", }; } render(){ //注意,在react中,DOM元素是对象,所以使用‘()'包住 return({this.state.inputValue}
在通过class定义的React组件中,除了生命周期钩子函数,constructor()和render()着两个方法也是自动执行的,先执行constructor(),执行constructor()的同时也是再为render()渲染DOM做数据准备。
实际上constructor()函数是组件生命周期中调用的第一个函数。
2.2事件
2.2.1与DOM中事件的异同
在React中处理事件和在DOM中处理事件类似,有两点不同:
- React中通过驼峰命名法命名事件,而不是全是小写字母;
- 在JSX中直接传递函数作为事件处理程序,而不是字符串。
第2点不同有坑,后面细说
举个例子,HTML中的事件:
ActivateLasers
React中的事件:
//因为jsx中'{}'里面代表函数表达式, //所以传递给onClick的实际是函数activateLasers的函数体部分, //因此需要指定this指向,不然会报错ActivateLasers
2.2.2存在的坑
直接传递function作为eventhandler需要指定函数的执行环境,即需要手动绑定this,不然会报this为undefined的错。见下面的例子:
classAppextendsComponent{ constructor(props){ super(props); this.state={ isToggleOn:true, }; //手动绑定this this.handleClick=this.handleClick.bind(this); } handleClick(){ //如果不在constructor()方法中手动绑定this,直接将其作为事件处理程序this为undefined console.log(this); this.setState(prevState=>({ isToggleOn:!prevState.isToggleOn })); } render(){ return({this.state.isToggleOn?"on":"off"}
2.2.3为什么会有坑
React官网说这个锅要JS原生语法来背,其实不尽然,React实在JS语法早已确定的情况下设计了这样的事件系统,如果一定要有人站出来背锅,他们五五分吧。
1,JS原生语法存在的问题
JS语法中有这样的规则:如果将一个函数的函数体(没有())赋值给另一个变量,函数体内部的this指向可能会发生变化。会不会变化取决于函数和被赋值的变量是否处于同一个作用域(相同的执行环境)中,但实际使用中,将一个函数赋值给相同作用域的变量没有意义,那样的话直接使用那个函数就好,没必要在赋值给另一个变量。
this指向不发生改变的没有意义的例子(为了方便说明,直接使用var操作符):
varfn=function(){ console.log(this); }; vara=fn; fn();//window a();//window this指向发生改变的例子: varfn=function(){ console.log(this); }; //将函数体赋值给一个对象的属性,函数执行时this和定义时指向不同 varo={ a:fn, }; fn();//window o.a();//o,即{a:f}
如果想要在将函数体赋值另一个变量的同时把原函数的this指向也一块赋值过去,就需要在赋值的过程中进行绑定this的操作,如下:
varfn=function(){ console.log(this); }; //fn在赋值的同时将内部的this打包一块赋值给了a varo={ a:fn.bind(this), }; fn();//window o.a();//window
通常在将函数体赋值给变量的时候为了避免this出错,都会进行绑定执行环境的操作,典型的例子是varbindId=document.getElementById.bind(document)
2,JSX存在的问题
因为JSX中DOM元素也是对象,给元素的属性赋值实际是给DOM元素对象的属性赋值,见下:
constelement=(clickme );
等同于
constelement={ type:'button', props:{ onClick:this.handleClick, children:'clickme', }, };
这实际就是将函数体赋值给一个对象的属性,函数执行时this和定义时指向不同的场景,和原生语法相同的是this指向发生了改变,不同的是原生JS中不管怎样,this总归是有个指向的,而JSX直接undefined。
所以说不绑定this报undefined的错不能全怪JS原生语法。
3.双向数据绑定
通过state传递数据加上事件处理程序便能实现数据的双向绑定,其背后的思想是(以input为例):初始化时将state中预定义的statea赋值给input,当input的value发生改变时,触发事件处理程序,将改变后的value赋值给状态a,React监测到state改变时重新调用render()方法,即重新渲染组件,达到双向绑定的目的。
classAppextendsComponent{ constructor(props){ super(props); this.state={ inputValue:"test", }; this.changeInput=this.changeInput.bind(this); } changeInput(e){ //将改变后的input值赋值给inputValue,通过事件对象$event.target.value实现 this.setState({ inputValue:e.target.value }); } render(){ //input改变时触发changeInput return({this.state.inputValue}