React笔记(一)
1.什么是JSX
- React作为一个使用虚拟DOM渲染页面的框架,并且本质上是一个JavaScript库,其包含有一套可以在JS代码内书写HTML的扩展语法。这套扩展语法就叫做JSX。
- JSX的全称为JavaScript XML,是react定义的一种类似于XML的JS扩展语法,其主要功能为创建虚拟DOM。在组件多层嵌套时更为便捷。
2.JSX的语法规则
-
定义虚拟DOM时不要使用引号。
//正确用法 var A = <div><p>xxx</p></div>; //错误用法:a变成字符串了 var A = "<div><p>xxx</p></div>";
-
在标签内部需要使用JS时,需要使用{}将这段JS语句包裹起来,并且这段JS的返回值,会变成页面上的元素显示出来。
function a(){ console.log('123'); return '456'; } var B = <div><p>{a()}</p></div>; //渲染B组件后,会在屏幕上显示456,并在F12控制台上输出123。
-
标签内样式类名要使用
className
,为了与定义类时所用的class
做区分;当使用内联样式时,要使用style属性,并使用属性键值对的形式书写,区别于CSS样式中的xxx-xxx形式,在JSX的标签内联样式中键的书写方式为xxxXxx的驼峰命名法。//正确用法 var A = <div className=‘aaa’></div>; var B = <div style={{minHeight:'400px'}}></div>; //错误用法:类名属性不对 var A = <div class=‘aaa’></div>; //错误用法:键值对中的键不对 var B = <div style={{min-height:'400px'}}></div>; //错误用法:少一层大括号,第一层代表内部将要写JS语句,第二层代表对象,内部是属性键值对 var B = <div style={minHeight:'400px'}></div>; //错误用法:内部是JS语句,值作为字符串需要使用引号包起来 var B = <div style={{minHeight:400px}}></div>;
-
返回的虚拟DOM只能有一个根标签,且根标签必须闭合
//正确用法 var A = <div><p>xxx</p></div>; //错误用法:返回两个根标签 var A = <div></div><p>xxx</p>;
-
当创建组件时,需要使用大写字母作为开头,如果小写子母开头,React将其判断为一个标签,并去渲染特定的标签,如果渲染不到即报错。
3.组件类型
- 函数式组件创建时函数名要大写,返回值为一个虚拟DOM,在页面渲染时需要写作闭合标签。需要注意的是,函数式组件内部的this属性为undefined。这是由于使用babel进行编译后,内部开启了严格模式。
- 类式组件在创建时要继承于React.Component类。并且需要书写render()方法,返回值为虚拟DOM。其中类的this是渲染在页面时,所创建的此组件的实例对象。
4.state
-
React组件内含有三大核心属性,
state
是最重要的属性,其驱动着页面的变化,在不使用React Hooks
的情况下,只有类组件存在此属性,因为在使用其的过程中需要应用到this关键字。使用的整体流程为:初始化state=>在页面需要变化的部分使用state=>在页面需要变化时调用setState方法。在调用对应的组件内部的setState方法时,会自动触发render方法,重新渲染页面。import React, { Component } from 'react' export default class Demo extends Component { //初始化state state = { count: 1 }; //配置按钮事件响应函数 //使用箭头函数的原因,this指向问题,由于因为事件函数是作为onClick的回调,不是通过实例调用的,是直接调用。 //并且由于类中的方法默认开启了局部的严格模式,所以事件响应函数中的this为undefined。 //在使用箭头函数时会将此函数的this指向组件类的当前实例对象,消除错误的发生。 addNumber = () => { const count = this.state.count + 1; this.setState({ count }); } render() { return ( <div> {/* 在需要显示的地方,使用state展示 */} <p>{this.state.count}</p> {/* 在绑定时使用onClick属性,而不使用onclick属性 */} {/* 在需要触发页面变化的地方绑定事件 */} <button onClick={this.addNumber}>点我加一</button> </div> ) } }
5.props
-
在直属的父子关系内,父子之间传值使用props。在使用时需要使用,父组件在其标签内添加属性
//父组件 <Child name='abc'/> //子组件 class Child extends Component { render() { return ( <div>{this.props.name}</div> ) } }
-
当需要批量传递props时可以使用解构赋值与扩展运算符,这时则可以批量传递props的属性值,也叫批量传递标签属性
//父组件 //定义对象 var p = { name:'abc', age:16 } //向子组件传递内容 //最外侧{}代表的是内部为JS语句,里面是对对象使用扩展运算符,在react中是可以对对象使用扩展运算符的 <Child {...p}/> //子组件 class Child extends Component { render() { return ( <div>{this.props.name}...{this.props.age}</div> ) } }
-
组件props的限制,在需要对父组件向子组件传递的内容进行限制时,可以使用prop-types进行限制,可以配置对于props的一些限制,例如传递类型,默认值,是否是必填项等。
//父组件 <Child name='abc'/> //子组件 //引入PropTypes import PropTypes from 'prop-types'; class Child extends Component { render() { return ( <div>{this.props.name}...{this.props.age}</div> ) } }; //定义props的限制 Child.propTypes={ //限制必填与字符串类型 name:PropTypes.string.isRequired, //限制为数字类型 age:PropTypes.number }; //定义props的默认值 Child.defaultProps={ age:17 };
此时即使父组件没有向子组件传递
age
属性,子组件也会显示age
为17
,并且如果将父组件向子组件传递的name
属性由abc
转换成数字,或删除在控制台处也会出现error
。 -
区别于
state
,在函数式组件内部也可以使用props
,类式组件内部应用的是this
,传递的属性是存在于其实例对象上的。函数式组件不存在this
,因此使用函数的参数将props
传递进子组件。并且props限制等使用方法与类式组件相同//父组件 <Child name='abc'/> //子组件 function Child(props) { return ( <div>{props.name}...{props.age}</div> ) } Child.propTypes={ name:PropTypes.string.isRequired, age:PropTypes.number } Child.defaultProps={ age:17 }
6.ref
-
React是使用虚拟DOM进行页面渲染的,因此要减少对真实DOM的操作,真实DOM也就是通过
getElementById
等方法获取到的DOM元素,此时可以使用第三个核心属性ref
对特定元素进行操作。 -
与HTML标签中的
id
属性类似,存在着一种字符串类型的ref
属性来对组件内的标签进行标识,此时对此组件内标签的取值就可以通过此种简单的方式获取,在使用时要使用refs
,因为所有ref
属性会寄存于refs
这个键值对的集合中。class Demo extends Component{ //配置驱动显示的状态 state = { show:'' }; render(){ return ( <div> <p>{this.state.show}</p> {/* input框,并为其配置一个ref属性 */} <input type="text" ref='abc'/> {/* 通过ref读取input的内容,可以在F12控制台上看见输出了一个DOM元素 */} <button onClick={()=>{console.log(this.refs.abc);this.setState({show:this.refs.abc.value})}}>同步</button> </div> ) } }
-
字符串形式的ref在目前的版本上已经过时了,因此可以使用回调形式的ref,具体的使用方式,只需要将上面的
ref=‘abc'
这段替换成内联函数形式的ref={c=>this.abc=c}
即可,在使用时,也无需去通过this.refs
获取,而是直接从当前组件的实例对象this
上获取。 -
使用回调形式的ref时,如果在回调函数中添加
console.log
,会发现此回调函数被触发的两次。原因是在每次渲染时都会创建一个新的内联函数实例,并清空旧的,因此会相应的触发两次。但在大多数情况下此无关紧要。可以通过类似于事件绑定函数方式,在类内定义方法,并进行绑定的方式设置,这样会减少此种情况的发生。class Demo extends Component{ //配置驱动显示的状态 state = { show:'' }; setRef = (c)=>{ this.abc =c; } render(){ return ( <div> <p>{this.state.show}</p> {/* input框,并为其配置一个ref属性 */} <input type="text" ref={this.setRef}/> {/* 通过ref读取input的内容,可以在F12控制台上看见输出了一个DOM元素 */} <button onClick={()=>{console.log(this.abc);this.setState({show:this.abc.value})}}>同步</button> </div> ) } }
-
在React内部存在一个直接创建ref的方法,
createRef
。在使用时,区别于回调形式与字符串形式,其定义ref时不会在标签内定义,而是在方法内或构造器内定义,并将其赋值给需要使用的标签。class Demo extends Component{ constructor(props){ super(props); this.abc = React.createRef(); } state = { show:'' }; render(){ return ( <div> <p>{this.state.show}</p> {/* input框,并为其配置一个ref属性 */} <input type="text" ref={this.abc}/> {/* 通过ref读取input的内容,可以在F12控制台上看见输出了一个DOM元素 */} <button onClick={()=>{console.log(this.abc);this.setState({show:this.abc.current.value})}}>同步</button> </div> ) } }
-
需要注意的是,在使用
createRef
时,想要获取ref所对应的DOM元素不可以直接获取,需要使用ref.current
获取。另外,对于函数组件,由于其没有实例化对象,所以是没有ref属性的,也就是不可以在函数式组件上使用ref属性;对于类式组件,可以使用ref属性,其对应的是此组件的当前实例实例化对象。
7.事件处理
-
在React中使用驼峰命名法,而非传统HTML中的纯小写方式书写标签内的事件绑定部分。在上面的部分代码中已经体现了如何为一个按钮绑定事件。
-
在React中绑定事件时,如果需要阻止默认事件,例如表单的提交。这时不能如原生JS一样,使用false的返回值实现,需要使用传递给事件的event参数内部的
preventDefault
方法实现。<button onClick={(e)=>{e.preventDefault()}}>阻止默认</button>
-
在最开始的state部分,使用了箭头函数来解决
getState()
的this指向问题。在React的事件绑定中,都存在着这种问题,除了箭头函数以外,可以使用原生JS中的bind
完成这项处理。上面的state部分代码可以相应的替换为:class Demo extends Component { constructor(props){ super(props); this.addNumber = this.addNumber.bind(this); } //初始化state state = { count: 1 }; //配置按钮事件响应函数 addNumber() { const count = this.state.count + 1; this.setState({ count }); } render() { return ( <div> {/* 在需要显示的地方,使用state展示 */} <p>{this.state.count}</p> {/* 在绑定时使用onClick属性,而不使用onclick属性 */} {/* 在需要触发页面变化的地方绑定事件 */} <button onClick={this.addNumber}>点我加一</button> </div> ) }
-
当需要向事件传递参数时,也可以通过这两种方式实现。比如说,在方法内存在一个计算后得出的属性,每一次点击增加这个属性值而不是固定的增加一,这个时候代码就需要如下这种写法。
class Deno extends Component { constructor(props){ super(props); this.addNumber = this.addNumber.bind(this); } state = { count: 1 }; addNumber(a) { const count = this.state.count + a; this.setState({ count }); } render() { var a = 1; return ( <div> <p>{this.state.count}</p> <button onClick={(e)=>{this.addNumber(a,e)}}>点我加{a}</button> </div> ) } }
又或者可以将按钮那一行替换为
<button onClick={this.addNumber.bind(this,a)}>点我加{a}</button>
8.状态提升
-
当兄弟组件需要使用互相的状态时,可以把此状态提升到共同的父组件上,这种做法就叫做状态提升。
-
在状态提升前,需要了解如何子组件向父组件传值。在React中子组件向父组件传值,本质上也是父组件向子组件传递内容。但此内容是父组件内部的一个方法,子组件通过这个方法,操作父组件的状态,即子组件向父组件传值。
class Root extends Component { constructor(props){ super(props); this.addNumber = this.addNumber.bind(this); } state = { count: 1 }; addNumber(a) { const count = this.state.count + a; this.setState({ count }); } render() { return ( <div> <p>{this.state.count}</p> {/* 向子组件传递操作本组件状态的方法,并将调用此方法的实例绑定在本组件上 */} <Child fun={this.addNumber.bind(this)}/> </div> ) } } class Child extends Component { constructor(props){ super(props); } render() { var a = 1; //使用箭头函数向方法内传值,实现子组件操作父组件的状态 return ( <button onClick={()=>{this.props.fun(a)}}>点我加一</button> ) } }
-
状态提升也就是在上述的基础上,将原本位于兄弟组件上的状态,提升到父组件上,这样子组件可以通过props获取父组件内部属性,另一个子组件也可以通过props操作父组件的属性,实现了兄弟组件之间的联系。
9.函数柯里化
-
在上述的代码中可以发现,每一次事件处理传值时都需要使用箭头函数,或进行绑定传值。原因是,标签的onClick属性(或其他关于事件处理的属性)都是一个函数。也就是在onClick后的{}内应当存在一个函数,但向函数传递参数,最好的做法还是调用并传参,但即使在函数中指定形参,在标签内绑定事件时也无法确定其形参。这时可以使用函数柯里化。
-
首先了解函数柯里化,其代表着将原本传递的多个参数,分层传递。例如
//不使用柯里化 function add(a,b){ return a+b; }; add(1,2);//3 //使用柯里化 function addC(a){ return function(b){ return a+b; }; } addC(1)(2);//3
-
这时会发现,使用函数柯里化的部分,在函数名后跟着两个()。这其实不难理解,第一个()代表着函数
addC
的调用运算结果:外侧的return,一个有一个形参的函数,第二个()代表着此调用此返回函数的运算结果:里侧的return,两个实参的和。 -
应用到实际事件处理传值中,可以使用如下代码替换上部分事件处理传参代码。
class Demo extends Component { constructor(props){ super(props); this.addNumber = this.addNumber.bind(this); } state = { count: 1 }; addNumber(a) { return (e)=>{ const count = this.state.count + a; this.setState({ count }); } } render() { var a = 1; return ( <div> <p>{this.state.count}</p> <button onClick={this.addNumber(a)}>点我加{a}</button> </div> ) } }
-
相当于在addNumber的核心代码外包了一层外壳,通过这个外壳获取参数,并把参数传递给壳内的函数。返回时将壳内的函数传递给标签的事件属性上,完成事件绑定。
参考文章
详解JS函数柯里化 - 简书 (jianshu.com)
React 官方中文文档 – 用于构建用户界面的 JavaScript 库 (docschina.org)