一、React简介
React是用于构建用户界面的JavaScript库。
我们一般绘制一个页面有3步:

前两步react不管,react只管第3步。React能让你不去操作DOM,但前两步还是要自己操作。
React解决的问题:
(1)原生JavaScript操作DOM频繁、效率低(DOM-API操作UI)。
(2)使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排。
(3)原生JavaScript没有组件化 编码方案,代码复用率低。
React的特点 :
(1)采用组件化模式 、声明式编码 (以前是命令式,比如修改页面上一个盒子的样式,需要js/jquery拿到,.style来改变它的样式;声明式通过语法直接定义盒子的样式,然后React直接帮你去操作DOM改变盒子样式),提高开发效率及组件复用率 。
(2)在React Native中可以使用React语法进行移动端开发(安卓和iOS)。
(3)使用虚拟DOM+优秀的Diffing算法 ,尽量减少与真实DOM的交互。(虚拟DOM是React操作用的,不是页面上的真实DOM,是放在内存中操作的)
一个例子说明:
原生JavaScript实现

如果在2个数据的基础上新加一个数据,则需要重新执行代码,把三个数据全部重新绘制。

React的实现,

多了一个数据则React虚拟DOM会多一个,React会进行虚拟DOM的比较,没变的虚拟DOM直接拿页面中的真实DOM来用,发生变化的虚拟DOM重新绘制。
二、React的基本使用
2.1 新建虚拟DOM
1.需要加入的js依赖
babel.min.js(可以将ES6转ES5;jsx转js,让浏览器执行)
react.development.js(react的核心库)
react-dom.development.js(react扩展库,帮忙操作DOM)

2.新建文件夹+html文件、加入js依赖
注意:react.development.js需要在react-dom.development.js前面。


说明:
(1)引入react.development.js后全局有React,引入react-dom.development.js后全局有ReactDOM。
(2)VDOM对象不要加引号。
(3)ReactDOM.render()方法是把参数1的组件渲染到参数2的容器上。获取容器也操作了DOM,React就是这个地方需要操作DOM。
看看效果:


2.2 虚拟DOM的两种创建方式
1.使用jsx创建虚拟DOM
与刚才例子的区别:

2.使用js创建虚拟DOM
可以不使用babel.min.js。

说明:
(1)React.createElement()的参数分别为(标签名、标签属性、标签内容)。
这里看不出jsx的优势,可以看一个例子:
需要在里加。
jsx写法:

js写法:

可以看到,jsx对于创建多级的标签更简便。也可以用看起来更直观的写法。

其实jsx最后也会转变为js的写法,但是开发人员不用写了。
2.3 虚拟DOM的数据类型


可以看到虚拟DOM是一般的Object对象。
真实DOM VS 虚拟DOM


加一个debugger查询属性:



总结虚拟DOM:
(1)本质是Object类型的对象(一般对象)。
(2)虚拟DOM比较"轻",真实DOM比较"重",因为虚拟DOM是React内部在用。无需真实DOM上那么多的属性。
(3)虚拟DOM最终会被React转为真实DOM,呈现在页面上。
2.4 JSX语法规则
全称为JavaScript XML,react定义的一种类似于XML的JS扩展语法。
1.定义虚拟DOM,不要写引号。

2.标签里混入javascript表达式时需要加花括号{}。

3.样式的类名指定不要用class,要用className.


效果如下:

报错原因是在JSX里面,如果想应用样式作为类名,用className而不是class。

4.内联样式,要用style={{key:value}}的形式去写
再给span标签加一个样式,使用js一般写法。


style不能用字符串,需要用双花括号。



5.只有一个根标签


6.标签必须闭合

7.标签首字母规则
(1)若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。

用一个html没有标签,可以看到内容正常显示:


(2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。


定义组件的内容后面讲述。
区分JS语句(代码)和JS表达式
注意:jsx里混入的javascript内容只能是表达式,不能是语句(代码)。
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。
下面都是表达式:
(1)a
(2)a+b
(3)demo(1)
(4)arr.map()
(5)function rest(){}
(6)console.log()
注意:
(1)arr.map()返回一个数组,例子如下,


(2)function rest(){}也有一个返回值,一个例子如下


2.语句(代码)。下main这些都是
(1)if(){}
(2)for(){}
(3)switch(){case:xxx}
一个例子:动态显示数组数据
目标效果:

根据之前的内容,不能在jsx里写for循环。

但是还有一个问题,浏览器报错了:

key属性是虚拟DOM的唯一标识。
修改如下:

这个写法可能还是有点问题,后面讲述。
2.5 一些概念
1. 模块与模块化
模块:向外提供特定功能的js程序,一般就是一个js文件。
模块化:当应用的js都以模块来编写的,这个应用就是一个模块化的应用。
2.组件和组件化
组件:用来实现局部功能效果的代码和资源的集合(html/css/js/image等)。
组件化:当应用是以多组件的方式实现,这个应用就是一个组件化的应用。
三、面向组件编程
2.1 安装React Developer Tools调试工具
React Developer Tools。
安装:
(1)chrome浏览器->更多工具->扩展程序
(2)点击左上角,打开Chrome网上商店。

搜索react(提供方为facebook)
一般浏览器不能访问,百度网盘链接:https://pan.baidu.com/s/15XwT-CdN9PBwZrkQBbU1Kg
提取码:1hxn

把这个程序加入浏览器:







2.2 组件类型
一个组件包含html、css、js等内容。
2.2.1 函数式组件

上述代码定义了一个函数式组件并使用ReacDOM进行渲染。
效果:

第二个报错是找不到图标:
可以给项目配一个图标,图标名称固定为favicon.ico,放在根目录下,

第一个报错意思是函数内容不能作为组件节点,要改写成标签的形式:

还是报错,因为demo首字母没有大写。



注意:函数组件 是一个函数,但不是开发者的代码调用的,而是react调用的。
补充:jsx的this
在js中的函数的this函数是指window对象。jsx不是,打印结果为未定义:

jsx的代码要经过babel的翻译,babel翻译完后开启了严格模式,禁止自定义的函数指向window。
补充:
定义函数式组件然后选然后,后面会默认执行的内容为,
(1)React解析组件标签,找到自定义组件。
(2)发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转化为真实DOM,随后呈现在页面中。
2.2.2 类组件
1.回忆JS类相关的知识
定义类和创建实例对象:


加上方法(类中的方法不加function关键字):


方法放在了类的原型对象 中,没有放在对象中。

方法中的this是指对象 ,可以获取对象的内容。但也不全对,因为调用函数的方法有很多种,比如call,bind,apply等,这些都会改变方法的this指向。

上述代码虽然是p1实例对象调用的speak(),但是使用call(),this指向call()的参数,所以speak()方法打印不到this的信息。

类可以继承。子类直接继承父类的构造器和方法。


子类也可以有自己的构造器,需要调用super(),且写在最前面。


子类可以直接调用父类的方法:


可以看到,对象没有speak()方法,需要通过原型链查找,先找到类的原型对象,再找到父类的原型对象。
也可以重写父类方法:


可以看到,子类的原型对象和父类的原型都想都有了speak(),子类找到了就不会去找父类。
类组件
类组件的三大特性:类组件一定要继承React.Component类、必须有render()方法,render()方法必须有返回值。


这里有一个问题,类中的方法是实例使用,可是jsx中没有new实例,那render()方法是在哪里调用的?
其实写了一个类组件的组件标签,React解析组件标签,找到组件类,随后new一个实例对象,并通过该实例调用原型上的render方法。将render返回的虚拟DOM转为真实的DOM,随后呈现在页面中。
所以,render中的this是实例对象。可以打印认证。


简单组件VS复杂组件
有state的组件是复杂组件,否则是简单组件。

把数据方法组件的state中,当数据改变(即状态改变),就会重新将虚拟DOM转为真实DOM。

state默认为null。
2.3 组件实例3大核心属性
注意:函数式组件没有实例,没有this,也没有属性。但新版React具有hooks,也可以使用核心属性。
对组件实例的属性赋值写在构造器中。
1.state
一个小例子:
目标效果:

点击页面变成:

实现:



接下来要加上点击事件。
先来回顾一下JS中绑定事件的三种方式。
JS中绑定事件的三种方式

JSX绑定事件
使用按钮3的方式(避免使用document),先加一个有弹窗的点击事件:


报错是因为React把JS中原生属性名都变成了驼峰形式。

浏览器还是会报错,因为onClick的需要是一个函数,而不是字符串。


可以看到,一打开页面就触发了点击事件。
因为在创建Weather组件实例时需要调用render()方法,而需要把demo()的返回值赋给onClick,即调用了demo()。

接下来对类组件中state中的数据isHot进行修改。那用this.state可以获取吗?答案是不能。
注意,在JS中的方法中的this指的是window。


JS中使用严格模式后,方法中的this是未定义的:


JSX中,构造器和render()中使用this.state能获取到是因为这里的this指向的是实例对象。
那如何取呢?可以使用一个变量暂存。


但是这种写法很麻烦。
方法是把类组件外部的事件函数放到类组件的内部。


但是点击还是会报错:

原因是changeWeather()中的this未定义:


因为只有通过实例对象调用的方法的this才生效。在render()中直接写this.changWeather()不属于实例对象调用,为什么呢?
补充:JS类中方法this指向
先来看实例对象调用方法时方法中的this的指向。


再来看看用变量获取对象的方法后再调用方法:


可以看到this的指向未定义。原因是因为"const x = p1.study"根本没用调用study(),是把实例对象的原型对象赋值给变量再调用,再执行方法本质是从原型对象对象。类中的方法局部默认开启严格模式。


再回到刚才的问题。

在render()中将changeWeather()绑定到render()中的h1的onClick上时,其实还未调用changeWeather(),只是绑定了原型对象上的changeWeather(),点击时触发方法调用是直接调用,不是实例对象调用,类中方法默认开启严格模式,不是实例对象调用this未定义。
解决方法如下:

上图红框中的代码的作用是在初始化实例对象(调用构造器)时给实例对象加一个changeWeather方法,该方法中的this指向实例对象(bind方法可以返回一个方法和改变方法中this的指向,注意bind方法没有调用函数 )。所以"this.changeWeather=this.changeWeather.bind(this)"等号左边的this.changeWeather是实例对象的,由render()方法调用(点击事件触发调用),而等号右边的this.changeWeather是原型上的。

可以看到,成功获取到实例对象,该实例对象有一个changeWeather方法(原型上也有)。在点击后触发事件调用的是实例对象上的changeWeather方法。
获取到了就可以修改state中isHot的值:

实际上这样写是没效果的。原因是state中的数据不能直接更改,直接更改React不认可,需要使用内置API--setState(React.Component中定义的方法)去更改。

这样就达到点击切换天气文字的效果。
再来说一些setState的细节问题。若是state中对象有多个字段。

使用setState()参数为一个新的对象,但是不是用新对象完全覆盖旧对象(this.setState({isHot:!isHot})执行后原来state中的wind还是存在,只是更新了isHot属性的值。),即setState()的更新是一种合并操作。
另外,每一次调用setState()后React都会帮忙调用render()重新渲染,达到改变state中的数据后页面内容自动重新渲染。
state的简写方式
功能已经实现了,但是代码 还是可以精简一下。
补充:JS中类的方法
类中除了构造器和方法,还可以写赋值语句,作用是新增或修改所以类的实例对象的属性值。


回到刚才说精简代码的问题。


这样所有实例对象的state都有相同的默认值并且都有changeWeather方法。
但是方法还需要修改,因为这样写的话方法中的this没法指向实例对象。需要改成箭头函数。

原因是箭头函数的this若是未定义,则会去找外侧的this,这里是实例对象。
精简后的代码为:

另外注意:state中的数据只能是对象。
2.props
之前的数据都是在构造器或者类中写死的,若是要从组件外部往组件内部传数据 写到state中,则需要使用组件的属性props。
我们知道html中的标签可以给属性赋值,类组件对象也可以。


使用props的例子:


props批量数据传递
使用React中props批量传递数据的简写方式:


注意:这种方式要求传递的字段名与组件中使用的字段名相同。
给props传递数值信息时需要去掉引号加花括号。

对props进行限制
我们可以对props中的对象的字段进行不同的限制,比如类型、默认值、非空等。
需要先引入一个依赖prop-types.js(下载到项目目录中):


说明:
(1)ProTypes的string表示是字符串,number表示是数字,isRequired表示非空,func表示函数。
另外注意:props是只读的,只允许读,不允许改。
props的简写方式

类组件构造器中的props
之前说过,构造器有一个默认参数props。重写类组件的构造器时需要在最前面加上super(props)。
函数组件使用props
函数组件没有实例,this未定义,不能使用state和ref属性,但是可以使用props属性。因为函数可以接受参数。


3.ref
用一个例子来展示。
效果:


可以看出这种写法很麻烦,需要使用document。
React的写法是使用ref属性。
字符串形式的ref(不推荐)

给组件中render()的html标签加一个ref属性后,会保存到实例对象的refs属性(key为ref的值,value为标签名)中:

react中使用this.refs可以直接拿到节点:


注意:这里refs拿到的节点不是虚拟DOM,是真实DOM。
拿到节点后可以获取组件的属性:


接着实现右边的效果:


回调形式的ref


可以看到,箭头函数的参数是节点本身。


另外,如果ref回调函数是以内联函数的方式(箭头函数是内联函数)定义的,则在更新操作会被调用两次。是因为在每次渲染时都会创建一个新的函数实例,所以react清空旧的ref并且设置新的。



也可以通过将ref的回调函数定义成class的绑定函数 的方式可以避免上述问题:

但是两种方式采用哪种无关紧要,常用内联函数的形式。
createRef
React.createRef()返回一个容器,该容器可以存储被ref所标识的节点。

上面的写法直接将带ref属性的input节点存储到this.myRef中。
先看看myRef的结构:



可以看到,通过current获取属性值。

注意:一个容器只能放一个节点。
