React 学习笔记1 组件、State
React是用于构建用户界面的JS库。
React是由Facebook(Meta)开发的,是开源的。
React采用组件化模式、声明式编码,可以提高开发效率,提高代码复用率。
使用React Native,可以用React语法进行移动端开发,可以使用JS编写安卓和IOS应用,而不需要使用java oc swift。
React使用虚拟DOM和Diffing算法,减少与真实DOM的交互。
入门Demo
想使用React,需要引入一些相关的JS文件:
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script><!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
babel.min.js 用于把ES6->ES5(ES6的部分语法,浏览器可能不支持,通过babel把ES6转换为浏览器支持的语法) JSX->JS(React中使用的是JSX,但是浏览器无法识别JSX,需要使用babel转换为JS)
react.development.js react核心库
react-dom.development.js react扩展库
React核心库要在扩展库之前引入。
由于React使用的是JSX,需要使用babel进行翻译,script的type需要设置为text/babel。
在JSX中,编写思路是1.创建虚拟DOM 2.渲染虚拟DOM到页面
1.创建虚拟DOM
const VDOM = 标签
在JSX中,直接写标签本身就可以,不需要包装成字符串。
2.渲染虚拟DOM到页面
ReactDOM.render(要渲染的虚拟DOM,渲染的容器)。
在React中,没有提供选择器,可以使用JS语法获取DOM。
<body><div id="demo"></div><script src="https://unpkg.com/react@17/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script><!-- Don't use this in production: --><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script><script type="text/babel">const VDOM = <h2>hello world</h2>ReactDOM.render(VDOM,document.getElementById('demo'));</script>
</body>
JSX与JS:使用JS创建虚拟DOM,语法会比较复杂,首先,要使用React.createElement(标签名,标签属性,标签内容)创建虚拟DOM。标签属性是一个对象,里面以key value的形式配置标签属性。
当虚拟DOM内部标签层级比较复杂时,JS语法要使用React.createElement(标签名,标签属性,React.createElement(....))的形式嵌套创建,有时候语法会十分繁琐。
但使用JSX,可以更简单地创建虚拟DOM。JSX经过babel翻译之后,会变成JS的格式,可以把JSX看做JS的语法糖。
虚拟DOM:console.log创建的虚拟DOM,虚拟DOM实际上是一个对象。没有真实DOM上绑定的许多方法,只是记录了虚拟DOM的部分信息。因为虚拟DOM只是React内部使用,帮助React识别DOM的修改情况,因此不需要真实DOM上那么多信息。虚拟DOM存储在内存中,最终,虚拟DOM会被转换为真实DOM。
使用小括号包含标签,可以表示括号内的内容都是一体的:
JSX
在JSX中,定义虚拟DOM时,语法就是HTML的标签语法,不需要转换为字符串等。但如果在标签中需要包含JS语法,需要用{}包含JS表达式。
在JSX中,标签中的class要写成className。
如果想写内联样式,不能使用style="..."的形式,因为在JSX中,style中不让写字符串。需要写style={{}},{{}}中以key value的形式配置。外层的{}表示要写JS表达式,内层的{}表示使用对象。
在JSX中,根标签只能有一个。所有的标签都必须闭合。标签若以小写字母开头,JSX会将标签转为html中的同名元素,若无对应同名元素,会报错。如果标签以大写字母开头,会被识别为组件。React会去渲染对应的组件,若组件没有定义,会报错。
<body><div id="demo"></div><script src="https://unpkg.com/react@17/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script><!-- Don't use this in production: --><script src="https://unpkg.com/@babel/standalone/babel.min.js"></script><style>.demo{background-color: aquamarine;}</style><script type="text/babel">let title= 'cat'const VDOM = (<div><h1 style={{ color:'blue' }}>{title}</h1><h2 className='demo'>hello world</h2></div>)ReactDOM.render(VDOM,document.getElementById('demo'));</script>
</body>
在JSX标签中,对于数组,JSX会自动遍历数组,并把结果放在对应位置。
对于对象,JSX无法直接解析对象,会报错。
在{}中,只能编写JS表达式,他会返回一个值(变量,变量运算,函数等都是JS表达式)。而不能写JS语句,比如for循环,if语句,switch等。且在React中,循环产生的标签必须有key值。
模块:向外提供特定功能的JS程序。一般是一个JS文件。通过模块,可以复用JS,简化JS的编写,提高JS运行效率。
组件:用来实现局部功能效果的代码和资源的集合(HTML\CSS\IMAGE等)。使用组件,可以复用编码。
模块化:应用的js都按模块化编写。
组件化:应用是以多组件的方式实现的。
开发者工具
React有官方提供的开发者工具React Developer Tools
是在Chrome使用的工具
组件
React中,定义组件有两种形式,一种是函数式的,另一种是类式的。
函数式组件
<body><div id="demo"></div><script type="text/babel">function Demo(){return <h2>demo 函数式</h2>}ReactDOM.render(<Demo/>,document.getElementById('demo'))</script></body>
由于函数不能直接用作React节点,在进行渲染时,render的第一个参数要写成函数同名的标签。并且标签是闭合的。标签名和函数名首字母都需要是大写,如果是小写,会被识别为HTML同名标签。
在函数式组件的函数中,this是undefined。这是由于babel编译时,会默认开启严格模式,严格模式会禁止自定义函数的this指向window。
在执行了ReactDOM.render之后,React会解析组件标签,去找Demo标签,然后发现组件使用函数定义,React会调用该函数,将返回的虚拟DOM转换为真实DOM,然后呈现在页面中。
因此,在使用函数式组件时,函数名必须大写,函数必须有返回值,且返回值是虚拟DOM,在渲染时,第一个参数要渲染的虚拟DOM应该是函数同名标签,且标签需要闭合。
类式组件
类
先来回顾一下JS中的类,类通过class 类名{}来定义类
class Cat(){}
然后通过new来创建实例对象
const c = new Cat()
初始化实例
如果想要不同的实例具有不同的信息,new传递参数,然后在类中通过构造器方法给实例设定属性:
class Cat(){constructor(name,age){this.name = name;this.age = age;}
}
构造器中的this指向类的实例对象。
如果想要给类添加方法:
class Cat(){constructor(name,age){this.name = name;this.age = age;}say(){console.log(`my name is ${this.name}`)}
}
当通过实例.方法调用方法时,方法中的this指向实例。如果是通过.call .apply等方法调用,this的指向可能会发生变化。
类继承
如果有一个类OrangeCat,想让他继承Cat类。
继承通过 子类 extends 父类来实现,在继承后,父类构造器中的内容,子类可以直接使用。
子类自己的属性:
如果子类想有一些自己的属性,是父类中不具有的,可以在子类中通过自己的构造器编写,构造器的参数是父类参数+子类自己的参数。构造器内部需要调用super来调用父类的构造器,就不需要在子类构造器中重复编写和父类构造器中一摸一样的语句了。语法是super(需要调用的参数),super需要写在最前面。
子类自己的方法:
如果子类需要定义自己的方法,可以在子类中重写父类上的同名方法,也可以在子类中定义新方法。子类定义的方法会出现在子类自己的原型上,会比父类原型先查找到。
<script>class Cat{constrocter(name,age){this.age = agethis.name = name}say(){console.log(`my name is ${this.name}`)}}class OrangeCat extends Cat{constructor(name,age,hobby){super(name,age)this.hobby = hobby}say(){console.log(`my name is ${this.name},my hobby is ${this.hobby}`)}hi(){console.log('hihi')}}
</script>
类式组件
类式组件用类的形式创建,类名就是组件名,类需要继承React.Component。由于类继承自React.Component,因此如果需要构造器,构造器内部一定要有super。但也可以不定义构造器。
在React的类式组件中,可以先不写构造器。但需要写render方法。
render方法的返回值是虚拟DOM。
然后再通过ReactDOM.render(类名同名标签(需要闭合),容器),来渲染组件。
在类式组件使用时,执行ReactDOM时,React发现组件是类定义的,会new一个该类的实例,并通过该实例调用到原型上的render方法。将render返回的虚拟DOM转为真实DOM,呈现在页面中。render方法在类的原型对象上,this是类的实例对象。
<script type="text/babel">class Cat extends React.Component{render(){return <h2>demo 类式</h2>}}ReactDOM.render(<Cat/>,document.getElementById('demo'))
</script>
对于ReactDOM new出来的类的实例,有时候也叫他组件实例对象/组件对象。
state
简单组件:无状态的组件
复杂组件:有状态(state)的组件
状态:组件状态驱动页面,组件的状态里存储着数据,数据会驱动页面的表现。
state是组件实例的三大核心属性之一。
函数式组件中没有this,也没有实例,是简单组件。state是在复杂组件中存在的。但函数组件也不是不能具有state,可以通过hooks方法让函数式组件获取state。
如何在state中存放数据:要往类的实例对象上添加属性,可以通过构造器添加。在构造器中,通过this.state = 对象 的形式,添加state数据。
如何访问state中的数据:在标签中,通过{JS表达式}来获取。
<script type="text/babel">class Cat extends React.Component{constructor(props){super(props)this.state = {nowColor:'orange',} }render(){return <h2>{this.state.nowColor}</h2>}}ReactDOM.render(<Cat/>,document.getElementById('demo'))
</script>
在React中,onClick的C必须大写,且value接收一个函数。由于函数是JS表达式,需要写在{}里。且React在处理onClick时,会把value的返回值赋值给onClick作为回调,也就是说,onClick的value需要是函数,而不要调用函数(调用函数时,会把函数的返回值赋给onClick,也就是undefined,当触发click时,不会有所反应)。
类方法的this指向
为了理解通过类方法创建的组件,需要先思考一下类的方法中,this指向的问题。
<script type="text/javascript">class Cat{constructor(name,age){this.name = namethis.age = age}say(){console.log(this)}}const cat = new Cat('cat',10)const tmp = cat.saytmp()
</script>
实际上,对于tmp来说,把cat.say函数赋值给tmp,tmp调用时,相当于是直接调用函数,函数的this并不是cat,而应该是window,但由于类中定义的方法是局部严格模式,直接调用方法时,由于this不能指向window,this值是undefined。
<script type="text/babel">class Cat extends React.Component{constructor(props){super(props)this.state = {nowColor:'orange',} }render(){return <h2 onClick={this.changeColor}>{this.state.nowColor}</h2>}changeColor(){this.state.nowColor = this.state.nowColor == 'orange' ? 'black' : 'orange'}}ReactDOM.render(<Cat/>,document.getElementById('demo'))
</script>
在类中,通过实例对象调用changeColor方法时,changeColor的this才指向实例,如果在onClick中通过onClick={this.changeColor}给onClick绑定回调函数,实际上绑定的是通过this.changeColor找到的方法,当在页面上点击h2时,这个方法并不是通过实例调用的,而是直接调用的,由于类的方法是严格模式,因此this并不指向实例,是undefined。
那么该如何在onClick中获取正确的this呢?需要在构造器中通过bind修改this指向,并把修改好this指向的方法添加到类的实例上:
<script type="text/babel">class Cat extends React.Component{constructor(props){super(props)this.state = {nowColor:'orange',} this.changeColor = this.changeColor.bind(this)}render(){return <h2 onClick={this.changeColor}>{this.state.nowColor}</h2>}changeColor(){this.state.nowColor = this.state.nowColor == 'orange' ? 'black' : 'orange'}}ReactDOM.render(<Cat/>,document.getElementById('demo'))
</script>
.bind方法返回新的函数。
setState
想要修改state的数据,并不能通过直接更改state的方式进行,也就是通过上面的方式,虽然能够修改this.state.nowColor的值,但是React并不承认这种修改,对state的修改要通过一个React内置的API进行。
这个API就是setState。
changeColor = () => {const nowColor = this.state.nowColorthis.setState({nowColor: nowColor == 'orange' ? 'black' : 'orange'})}
setState方法接收一个对象,对象的key是要修改的key,value是想要修改为的value值。
通过setState的更新是一种合并操作,也就是对于state中的数据,同名的数据会进行更新,不会影响不同名数据。
对于类,构造器只调用一次;render除了初始时会调用一次,状态更新时也会调用一次;changeColor点击几次,就会调用几次。
state简写形式
对于类中定义的自定义方法,一般都是作为交互的回调函数使用,也就是说,通过this.方法访问时,基本都很难获得正确的this指向,如果在构造器中对每个方法的this都重新bind,代码会很重复。
对于state,state可以不写在构造器里,直接写在类中就可以。
对于自定义的方法,这个方法也可以通过赋值的方式写。这时候,相当于方法不在原型上,在实例上。
对于自定义方法的this,为了获取正确的this值,需要写箭头函数。箭头函数会让this指向实例。
简化后:
<script type="text/babel">class Cat extends React.Component{state = {nowColor:'orange',} render(){return <h2 onClick={this.changeColor}>{this.state.nowColor}</h2>}changeColor = () => {const nowColor = this.state.nowColorthis.setState({nowColor: nowColor == 'orange' ? 'black' : 'orange'})}}ReactDOM.render(<Cat/>,document.getElementById('demo'))
</script>
state的value是对象。组件也被称为状态机,通过更新组件的state来更新对应页面的显示。
由于调用对象不同,render中的this一般为组件的实例对象,组件自定义方法中的this一般是undefined,需要通过bind修改this,或者箭头函数来修正this。
state数据不能直接修改,需要借助setState。