React[16x]框架
React[16x]框架
(1)React 简介
A JavaScript library for building user interfaces (用于构建用户界面的JavaScript库)
React生态:React + React-Router + Redux + Axios + Babel + Webpack
(1.1)React 背景
React
起源于 Facebook
的内部项目,因为该公司对市场上所有JavaScript MVC
框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
React
的社区也是非常强大的,随着React
的普及也衍生出了更多有用的框架,比如ReactNative
和React VR
。React
从13年开始推广,现在已经推出16RC(React Fiber
)这个版本,性能和易用度上,都有很大的提升。
(1.2) 什么是React
\(~~~~\)React
是一个简单的javascript UI
库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用虚拟DOM来有效地操作DOM。它遵循从高阶组件到低阶组件的单向数据流。
声明式编程
\(~~~~\)声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做。它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件。它没有描述控制流步骤。声明式编程的例子有HTML、SQL
等
函数式编程
\(~~~~\)函数式编程是声明式编程的一部分。javascript
中的函数是第一类公民,这意味着函数是数据,你可以像保存变量一样在应用程序中保存、检索和传递这些函数。
函数式编程有些核心的概念,如下:
- 不可变性(
Immutability
) - 纯函数(
Pure Functions
) - 数据转换(
Data Transformations
) - 高阶函数 (
Higher-Order Functions
) - 递归
- 组合
React 中一切都是组件。 我们通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件。 通常,组件是一个
javascript
函数,它接受输入,处理它并返回在UI中呈现的React
元素。
(1.3) React 和 Vue 的对比
这是前端最火的两个框架,虽然说React
是世界使用人数最多的框架,但是就在国内而言Vue
的使用者很有可能超过React
。两个框架都是非常优秀的,所以他们在技术和先进性上不相上下。
React.js
相对于Vue.js
它的灵活性和协作性更好一点,所以我在处理复杂项目或公司核心项目时,React
都是我的第一选择。而Vue.js
有着丰富的API
,实现起来更简单快速,所以当团队不大,沟通紧密时,我会选择Vue
,因为它更快速更易用,当然Vue
也完全胜任于大型项目。
(2) React 的优缺点
(2.1) React 优点
-
1.声明式设计
−React
采用声明范式,可以轻松描述应用。 -
2.高效
−React
通过对DOM
的模拟,最大限度地减少与DOM
的交互。 -
3.灵活
−React
可以与已知的库或框架很好地配合。 -
4.
JSX − JSX
是JavaScript
语法的扩展。React
开发不一定使用JSX
,但我们建议使用它。 -
5.组件 − 通过
React
构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。 -
6.单向响应的数据流
− React
实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
应用学习方面:
- 生态强大:现在没有哪个框架比React的生态体系好的,几乎所有开发需求都有成熟的解决方案。
- 上手简单: 你甚至可以在几个小时内就可以上手React技术,但是他的知识很广,你可能需要更多的时间来完全驾驭它。
- 社区强大:你可以很容易的找到志同道合的人一起学习,因为使用它的人真的是太多了。
(3) React 和 React Native
React Native
看起来很像 React
,只不过其基础组件是原生组件而非 web
组件。要理解 React Native
应用的基本结构,首先需要了解一些基本的 React
的概念,比如 JSX
语法、组件、state
状态以及props
属性。
(3.1) React Native开发特点
- 一次学习,随处编写:使用
React Native
可以为iOS
和Android
操作系统开发应用程序,不同平台上的代码根据平台会有一些微小的区别。 - 混合开发:
React Native
代码开发的模块与原生代码开发的模块可以双向通信、无缝衔接; - 高效的移动应用开发:
(1)独特的UI实现框架
(2)组件化开发
(3)跨平台移植代码迅速
(4)自动匹配不同屏幕大小的手机
- 高效的移动应用开发调试
- 高效的应用热更新
- 有效降低移动应用安装包体积
- 学习门槛低、开发难度低
使用
React Native
开发的代价
为了得到React Native
开发的优点,使用React Native
开发的APP
也需要付出一定的代价。
(1)内存消耗大
使用
React Native
开发的程序运行所需的内存比原生代码开发的程序略多。
(2)运行速度
使用
React Native
开发的代码运行速度比原生代码略慢。
React
与React Native
除了在编码表现层都使用JSX
语法外,在React
与React Native
的底层都有Virtual DOM
与DOM
之间的映射与转换,以实现了页面组件高效更新的前端表现。
(3.2) React Native与React的关系及特点
**React Native
与React
的关系及特点 : **
React
是基础框架,是一套基础设计实现理念,开发者不能直接使用它来开发移动应用或网页。在React
之上发展出了React.js框架
用来开发网页,发展出来React Native用来开发移动应用。底层原理是相同的,都是使用js
实现虚拟dom
树来驱动页面的渲染,react是驱动HTML dom的渲染,react native是驱动原生组件的渲染。
**React.js : **
目的 是为了使前端的
V层
更具组件化,能更好的复用,它能够使用简单的html
标签创建更多的自定义组件标签,内部绑定事件,同时可以让你从操作dom
中解脱出来,只需要操作数据就会改变相应的dom
。
二者都是基于组件(component
)开发,然后组件和组件之间通过props
传递方法,每个组件都有一个状态(state
),当某个方法改变了这个状态值时,整个组件就会重绘,从而达到刷新。另外,说到重绘就要提到虚拟dom
了,就是用js
模拟dom
结构,等整个组件的dom
更新完毕,它会有一个diff
的过程,对比出哪些组件发生了变化,然后才渲染到页面,简单来说只更新了相比之前改变了的部分,而不是全部刷新,所以效率很高。
虚拟DOM(Virtual DOM)
的机制:在浏览器端用Javascript
实现了一套DOM API
。基于React
进行开发时所有的DOM
构造都是通过虚拟DOM
进行,每当数据变化时,React
都会重新构建整个DOM
树,然后React
将当前整个DOM
树和上一次的DOM
树进行对比,得到DOM
结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React
能够批处理虚拟DOM
的刷新,在一个事件循环(Event Loop
)内的两次数据变化会被合并。
后续将详细学习研究React ative
(4) React[16x]入门
(4.1) React开发环境搭建
在搭建
React
开发环境前需要安装Node
(自行百度)
(4.1.1) 安装Node.js
使用React.js
是可以用最原始的<Script>
标签进行引入的,但是这实在是太low了,工作中也不会有这种形式进行引入。所以在学习的时候,我们就采用脚手架形式的安装。这就需要我们安装Node.js
,用里边的npm
来进行安装。
安装Node
只需要进入Node
网站,进行响应版本的下载,然后进行双击安装就可以了。
Node中文网址:http://nodejs.cn/ (建议你在这里下载,速度会快很多)
注意:一定要正确下载对应版本,版本下载错误,可是没有办法使用
Node.js
安装好以后,如果是Windows
系统,可以使用 Win+R
打开运行,然后输入cmd
,打开终端(或者叫命令行工具)。
node -v
如果正确出现版本号,说明Node安装成功
了
检查npm
环境,正确出现版本号,说明npm
也是没问题的
npm -v
(4.1.2) React脚手架搭建
使用npm
命令来安装脚手架工具:
npm install -g create-react-app
create-react-app
是React
官方出的脚手架工具.
补充知识:
npm install moduleName # 安装模块到项目目录下
npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
npm install --save moduleName # --save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
npm install --save-dev moduleName # --save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。
(4.2) 创建React项目
1. 准备工作
创建文件夹 reactdom ,作为项目路径
2. 创建项目
通过脚手架创建React项目命令:项目名demo01
create-react-app demo01 //用脚手架创建React项目
3. 启动项目
这个有一个小坑,启动项目一定要先进入项目目录,而不是我们创建的reactdom,
使用一下命令运行即可:
cd demo01 //等创建完成后,进入项目目录
npm start //预览项目,如果能正常打开,说明项目创建成功
(4.2.1) 项目目录介绍
1. 项目初始化目录:
2. 项目顶层目录
先从进入项目根目录说起,也就是第一眼看到的文件(版本不同,可能稍有不同)
- README.md : 这个文件主要作用就是对项目的说明,已经默认写好了一些东西,你可以简单看看。如果是工作中,你可以把文件中的内容删除,自己来写这个文件,编写这个文件可以使用Markdown的语法来编写。
- package.json :这个文件是webpack配置和项目包管理文件,项目中依赖的第三方包(包的版本)和一些常用命令配置都在这个里边进行配置,当然脚手架已经为我们配置了一些了,目前位置,我们不需要改动。如果你对
webpack
了解,对这个一定也很熟悉。 - gitignore : 这个是git的选择性上传的配置文件,比如一会要介绍的
node_modules
文件夹,就需要配置不上传。 - node_modules :这个文件夹就是我们项目的依赖包,到目前位置,脚手架已经都给我们下载好了,你不需要单独安装什么。
- public :公共文件,里边有公用模板和图标等一些东西。
- src : 主要代码编写文件,这个文件夹里的文件对我们来说最重要,都需要我们掌握。
3. public文件夹
这个文件都是一些项目使用的公共文件,也就是说都是共用的,我们就具体看一下有那些文件吧。
-
favicon.ico : 这个是网站或者说项目的图标,一般在浏览器标签页的左上角显示。
-
index.html :_ 首页的模板文件_,我们可以试着改动一下,就能看到结果。
-
mainifest.json:移动端配置文件,这个会在以后的课程中详细讲解。
4. src文件夹
这个目录里边放的是我们开放的源代码,我们平时操作做最多的目录。
-
index.js : 这个就是项目的入口文件
-
index.css :这个是
index.js
里的CSS文件。 -
app.js : 这个文件相当于一个方法模块,也是一个简单的模块化编程。
-
serviceWorker.js : 这个是用于写移动端开发的,
PWA
必须用到这个文件,有了这个文件,就相当于有了离线浏览的功能。
(4.3) Hello World和组件的编写
先把src
目录里的文件全部删除,我们一点点写一个HelloWorld程序
,并通过编写这个程序了解一下什么是React中的组件化编程。
(4.3.1) 编写入口文件
在src文件夹下,新建index.js
入口文件。
(4.3.2) 导入相关包
在index.js
文件中,导入必要的基础包
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.js'
ReactDom.render(<App/>,document.getElementById('root'))
首先引入React
两个必要的文件,然后引入一个App组件(目前这个组件还没有编写,需要一会创建),然后用React
的语法把App
模块渲染到Root ID上rootID是在 public\index.html 文件中的真实DOM
(4.3.3) 编写App组件
import React, {Component} from 'react'
class App extends Component {
render(){
return(
<div>
Hello Word!!!
</div>
)
}
}
export default App;
这里导入的包,导入形式有些不同,
//ES6的语法-解构赋值
import React, {Component} from 'react'
//分开写
import React from 'react'
const Component = React.Component
注意:
React的主要优势之一就是组件化编写,这也是现代前端开发的一种基本形式
创建React组件时,必须使用大写字母开头。一般文件名和组件名相同
(4.3.4) 启动项目
在终端,进入项目目录,使用npm start
命令启动项目,我们可以在页面上看到Hello Word
。
(4.4) React中JSX语法
JSX
语法,看起来跟html
标签几乎一样
(4.4.1) JSX简介
JSX
就是Javascript
和XML
结合的一种格式。React
发明了JSX
,可以方便的利用HTML
语法来创建虚拟DOM
,当遇到<
,JSX
就当作HTML
解析,遇到{就当JavaScript
解析.
比如我们写一段JSX
语法:
<ul className="my-list">
<li>JAVA</li>
<li>I love React</li>
</ul>
就等于之前我们写一段JS代码:
var child1 = React.createElement('li', null, 'JSPang.com');
var child2 = React.createElement('li', null, 'I love React');
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
从代码量上就可以看出JSX语法大量简化了我们的工作。
(4.4.2) 组件和普通JSX语法的区别
这个说起来也只有简单的一句话,就是你自定义的组件必须首写字母要进行大写,而JSX是小写字母开头的。
(4.4.3) JSX使用三元越算符
import React from 'react'
const Component = React.Component
class App extends Component{
render(){
return(
<ul className = 'my-list'>
<li>{Hello Word ? '你好':'欢迎你'}</li> //三目运算
<li> I love React </li>
</ul>
)
}
}
export default App;
(4.5) React 实例学习
(4.5.1) 新建组件【小姐姐组件】
现在
src
的目录下面,新建一个文件Xiaojiejie.js
文件,然后写一个基本的HTML
结构。代码如下:
import React,{Component} from 'react'
class Xiaojiejie exetends Component{
render(){
return(){
<div>
<div>
<input/>
<button>增加服务</button>
</div>
<ul>
<li>头部按摩</li>
<li>精油推背</li>
</ul>
</div>
}
}
}
export default Xiaojiejie
然后我们把入口文件的<App />
组件换成Xiajiejie组件。
(4.5.1.1) 组件外层包裹原则
这是一个很重要的原则,比如上面的代码,我们去掉最外层的<Div>
,就回报错,因为React
要求必须在一个组件的最外层进行包裹。
所以我们在写一个组件的时候,组件的最外层都需要有一个包裹。
(4.5.1.2) Fragment标签讲解
加上最外层的DIV
,组件就是完全正常的,但是你的布局就偏不需要这个最外层的标签怎么办?比如我们在作Flex
布局的时候,外层还真的不能有包裹元素。这种矛盾其实React16
已经有所考虑了,为我们准备了<Fragment>
标签。
要想使用<Fragment>
,需要先进行引入。
import React,{Component,Fragment} from 'react'
然后把最外层的<div>
标签,换成<Fragment>
标签,代码如下:
import React,{Component,Fragment } from 'react'
class Xiaojiejie extends Component{
render(){
return (
<Fragment>
<div><input /> <button> 增加服务 </button></div>
<ul>
<li>头部按摩</li>
<li>精油推背</li>
</ul>
</Fragment>
)
}
}
export default Xiaojiejie
这时候你再去浏览器的Elements
中查看,就回发现已经没有外层的包裹了。
(4.5.2) React 响应式设计和数据绑定
(4.5.2.1) React 响应式设计
React
不建议你直接操作DOM
元素,而是要通过数据进行驱动,改变界面中的效果。React
会根据数据的变化,自动的帮助你完成界面的改变。所以在写React
代码时,你无需关注DOM相关的操作,只需要关注数据的操作就可以了(这也是React如此受欢迎的主要原因,大大加快了我们的开发速度)
现在的需求是增加小姐姐的服务项,就需要先定义数据。数据定义在Xiaojiejie组件中的构造函数里constructor。
//js的构造函数,由于其他任何函数执行
constructor(props){
super(props)//调用父类的构造函数,固定写法
this.state = {
inputValue:'',//input中的值
list:[]//服务列表
}
}
在React
中的数据绑定和Vue中几乎一样,也是采用字面量(我自己起的名字)的形式,就是使用{}来标注,其实这也算是js
代码的一种声明。比如现在我们要把inputValue
值绑定到input
框中,只要写入下面的代码就可以了。其实说白了就是在JSX
中使用js
代码。
<input value={this.state.inputValue} />
现在需要看一下是不是可以实现绑定效果,所以把inputValue
赋予一个'jspang'
,然后预览看一下效果。在这里我们并没有进行任何的DOM
操作,但是界面已经发生了变化,这些都时React
帮我们作的,它还会自动感知数据的变化。
(4.5.2.2) React 绑定事件
这时候你到界面的文本框中去输入值,是没有任何变化的,这是因为我们强制绑定了inputValue
的值。如果要想改变,需要绑定响应事件,改变inputValue
的值。比如绑定一个改变事件,这个事件执行inputChange()
(当然这个方法还没有)方法。
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
现在还没有inputChange()
这个方法,在render()
方法的下面建立一个inputChange()
方法,代码如下:
inputChange(e){
// console.log(e.target.value);
// this.state.inputValue=e.target.value;
this.setState({
inputValue:e.target.value
})
}
注意点:
1.一个是this指向不对,你需要重新用bind设置一下指向(ES6的语法)。
2.另一个是React中改变值需要使用this.setState方法。
(4.5.3) React 列表渲染
(4.5.3.1) 遍历数组
现在的列表还是写死的两个<li>
标签,那要变成动态显示的,就需要把这个列表先进行数据化,然后再用javascript
代码,循环在页面上。
ist数组增加两个数组元素,代码如下:
constructor(props){
super(props) //调用父类的构造函数,固定写法
this.state={
inputValue:'jspang' , // input中的值
//----------主要 代码--------start
list:['基础按摩','精油推背']
//----------主要 代码--------end
}
}
有了数据后,可以在JSX部分进行循环输出,代码如下:
render(){
return (
<Fragment>
<div>
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button> 增加服务 </button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return <li>{item}</li>
})
}
</ul>
</Fragment>
)
}
完成上面的步骤,数据就不再是固定的了,而是动态管理的
解决 Key值报错:
打开浏览器的控制台F12,可以清楚的看到报错了。这个错误的大概意思就是缺少key
值。就是在用map
循环时,需要设置一个不同的值,这个时React
的要求。
<ul>
{
this.state.list.map((item,index)=>{
return <li key={index+item}>{item}</li>
})
}
</ul>
(4.5.3.2) 增加数组元素
要增加服务选项,我们需要再增加按钮上先绑定一个方法this.addList
(这个方法目前还没有,需要我们接下来建立).
<button onClick={this.addList.bind(this)}> 增加服务 </button>
接下来就是把this.addList
方法,代码如下:
//增加服务的按钮响应方法
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue]
})
}
注意:
...
这个是ES6的新语法,叫做扩展运算符。意思就是把list数组进行了分解,形成了新的数组,然后再进行组合
(4.5.3.3) 删除数组元素
如果要删除一个东西,就要得到数组里的一个编号,这里指下标。传递下标就要有事件产生,先来绑定一个双击事件.代码如下:
<ul>
{
this.state.list.map((item,index)=>{
return (
<li
key={index+item}
onClick={this.deleteItem.bind(this,index)}
>
{item}
</li>
)
})
}
</ul>
为了看着更清晰,我们在return
部分加了()这要就可以换行编写JSX
代码了.在onClick
我们绑定了deleteItem
方法.
获得了数据下标后,删除数据就变的容易起来.先声明一个局部变量,然后利用传递过来的下标,删除数组中的值.删除后用setState
更新数据就可以了.
//删除单项服务
deleteItem(index){
this.state.list.splice(index,1)
this.setState({
list:this.state.list
})
}
注意:
记住React
是禁止直接操作state
的,虽然上面的方法也管用,但是在后期的性能优化上会有很多麻烦,所以一定不要这样操作
(4.5.4) JSX语法注意事项
** JSX的注释,可以有下面两种写法:**
<Fragment>
{/* 正确注释的写法 */}
<div>
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>
如果你记不住,有个简单的方法,就是用VSCode
的快捷键,直接按Ctrl+/
,就会自动生成正确的注释了。
你可以把这个理解为,在jsx
中写javascript
代码。所以外出我们套入了{}
,然后里边就是一个多行的javascript
注释。如果你要使用单行祝注释//
,你需要把代码写成下面这样。
<Fragment>
{
//正确注释的写法
}
<div>
<input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>
class
换成className
,它是防止和js
中的class类名
冲突,所以要求换掉- JSX中Html解析问题
如果想在文本框里输入一个<h1>
标签,并进行渲染。默认是不会生效的,只会把<h1>
标签打印到页面上,这并不是我想要的。如果工作中有这种需求,可以使用dangerouslySetInnerHTML
属性解决。具体代码如下:
<ul>
{
this.state.list.map((item,index)=>{
return (
<li
key={index+item}
onClick={this.deleteItem.bind(this,index)}
dangerouslySetInnerHTML={{__html:item}}
>
</li>
)
})
}
</ul>
上面的代码就可以实现html
格式的输出。
4. JSX
中
label是html中的一个辅助标签,也是非常有用的一个标签。
先看下面的代码,我们在文本框前面加入一个<label>
。
<div>
<label>加入服务:</label>
<input className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>
这时候想点击“加入服务”直接可以激活文本框,方便输入。按照html
的原思想,是直接加ID
就可以了。代码如下:
<div>
<label for="jspang">加入服务:</label>
<input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>
这时候你浏览效果虽然可以正常,但console
里还是有红色警告提示的。大概意思是不能使用for
它容易和javascript
里的for
循环混淆,会提示你使用htmlfor
。
<div>
<label htmlFor="jspang">加入服务:</label>
<input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>
这时候代码就正确了,可以实现点击<label>
后,激活<input>
标签了。
(4.5.5) Simple React Snippets插件
vscode
中的Simple React Snippets
1. 安装
打开SCode
的插件查单,然后在输入框中输入imple React Snippets
然后点击进行安装就可以了
2. 快速进行引入import
直接在vscode
中输入imrc
,就会快速生成最常用的import
代码。
import React, { Component } from 'react';
2. 快速生成class
在作组件的时候,都需要写一个固定的基本格式,这时候你就可以使用快捷键cc
插件就会快速帮我们生成如下代码
class extends Component {
state = { }
render() {
return ( );
}
}
export default ;
更多详细快捷键,打开插件的说明文件看一下就可以了
(4.5.6) 组件拆分
小姐姐服务菜单已经完美的制作好了,但是这从头到尾我们只用了一个组件,但是在实际工作中肯定是团队开发,我们会把一个大功能分成不同的组件。比如把文本框和按钮单独一个组件,把下面的服务列表单独一个组件,这涉及了一个组件拆分的能力和知识
1. 新建服务菜单组件
在src
目录下,新建一个文件,这里就叫做XiaojiejieItem.js
import React, { Component } from 'react'; //imrc
class XiaojiejieItem extends Component { //cc
render() {
return (
<div>小姐姐</div>
);
}
}
export default XiaojiejieItem;
到以前写的Xiaojiejie.js
文件中用import
进行引入,代码如下:
import XiaojijieItem from './XiaojiejiItem'
2. 修改Xiaojiejie组件
已经引入了新写的组件,这时候原来的代码要如何修改才能把新组件加入?
把原来的代码注释掉,当然你也可以删除掉,我这里就注释掉了,注释方法如下:
{/*
<li
key={index+item}
onClick={this.deleteItem.bind(this,index)}
dangerouslySetInnerHTML={{__html:item}}
>
</li>
*/ }
然后在最外层加入包裹元素<div>
,为的是防止两个以上的标签,产生错误信息。
最后直接写入Xiaojiejie
标签就可以了.
<XiaojiejieItem />
完整代码:
import React,{Component,Fragment } from 'react'
import './style.css'
import XiaojiejieItem from './XiaojiejieItem'
class Xiaojiejie extends Component{
//js的构造函数,由于其他任何函数执行
constructor(props){
super(props) //调用父类的构造函数,固定写法
this.state={
inputValue:'' , // input中的值
list:['基础按摩','精油推背'] //服务列表
}
}
render(){
return (
<Fragment>
{/* 正确注释的写法 */}
<div>
<label htmlFor="jspang">加入服务:</label>
<input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
<button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return (
//----------------关键修改代码----start
<div>
<XiaojiejieItem />
</div>
//----------------关键修改代码----end
)
})
}
</ul>
</Fragment>
)
}
inputChange(e){
// console.log(e.target.value);
// this.state.inputValue=e.target.value;
this.setState({
inputValue:e.target.value
})
}
//增加服务的按钮响应方法
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
})
}
//删除单项服务
deleteItem(index){
let list = this.state.list
list.splice(index,1)
this.setState({
list:list
})
}
}
export default Xiaojiejie
(4.5.7)React-父子组件的传值
上面已经把"小姐姐"组件做了一个基本的拆分,但是还不能实现随着输入,显示出输入的内容。这里涉及的是父组件向子组件传值。然后点击删除,就相当于子组件向父组件传值。
(4.5.7.1)父组件向子组件传参
这里只介绍最实用的,最快速的上手方法。就是使用组件属性的形式父组件给子组件传值。比如:我们在<XiaojiejieItem>
组件中加入content属性
,然后给属性传递{item}
,这样就完成了父组件向子组件传值。
<XiaojiejieItem content={item} />
现在值已经顺利的传递了过去,这时候可以通过this.props.xxx
的形式进行接受,比如传递过来的值,可以用如下代码进行接收。
import React, { Component } from 'react'; //imrc
class XiaojiejieItem extends Component { //cc
render() {
return (
<div>{this.props.content}</div>
);
}
}
export default XiaojiejieItem;
修改完小姐姐子项的组件后,可以打开浏览器进行预览了。试着添加几个新的选项试一下
父组件向子组件传递内容,靠属性的形式传递。
(4.5.7.2)子组件向父组件传参
现在要作这样一个功能:点击组件中的菜单项后,删除改菜单项。这就涉及了一个字组件向父组件传递数据的知识需要掌握。
先来绑定点击事件,这时候当然是要在XiaojiejieItem
组件中绑定了,代码如下:
import React, { Component } from 'react'; //imrc
class XiaojiejieItem extends Component { //cc
render() {
return (
<div onClick={this.handleClick}>{this.props.content}</div>
);
}
handleClick(){
console.log('撩拨了小姐姐')
}
}
export default XiaojiejieItem;
这时候进行预览,打开F12,再点击服务菜单项,就会再console
里显示出"撩拨了小姐姐"的字样。但是console
里还有一个warning
警告,这个警告我们见过,就是要求循环时必须设置key值。
修改XiaoJieJie组件的render代码如下:
<ul>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item} />
)
})
}
</ul>
绑定成功后,现在就要通过操作子组件删除父组件里的数据了。但是React
有明确规定,子组件时不能操作父组件里的数据的,所以需要借助一个父组件的方法,来修改父组件的内容。其实在以前已经写了一个删除方法deleteItem
,现在要作的就是子组件调用这个方法。
//删除单项服务
deleteItem(index){
let list = this.state.list
list.splice(index,1)
this.setState({
list:list
})
}
1. 获取数组索引下标
那现在问题来了,要删除就要知道索引值,还是需要通过父组件传递给子组件.这里还是通过props
属性的形式进行传递。
<ul>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item}
index={index} />
)
})
}
</ul>
然后修改XiaojiejieItem
组件,在handleClick
方法里,写入下面代码:
return (
<div onClick={this.handleClick.bind(this)}>
{this.props.content}
</div>
);
构造函数里绑定的。(有言曰:构造函数中绑定性能会高一些,特别是在高级组件开发中,会有很大的作用)
constructor绑定this方法。
import React, { Component } from 'react'; //imrc
class XiaojiejieItem extends Component { //cc
//--------------主要代码--------start
constructor(props){
super(props)
this.handleClick=this.handleClick.bind(this)
}
//--------------主要代码--------end
render() {
return (
<div onClick={this.handleClick}>
{this.props.content}
</div>
);
}
handleClick(){
console.log(this.props.index)
}
}
export default XiaojiejieItem;
2. 子组件调用父组件方法
如果子组件要调用父组件方法,其实和传递数据差不多,只要在组件调用时,把方法传递给子组件就可以了,记得这里也要进行this
的绑定,如果不绑定子组件是没办法找到这个父组件的方法的。
<ul>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item}
index={index}
//关键代码-------------------start
deleteItem={this.deleteItem.bind(this)}
//关键代码-------------------end
/>
)
})
}
</ul>
传递后,在XiaojiejieItem
组件里直接hi用就可以了,代码如下:
handleClick(){
this.props.deleteItem(this.props.index)
}
(4.5.8)React-单项数流
1. 单项数据流
React
的特性中有一个概念叫做“单项数据流”,可能刚刚接触React
的小伙伴不太明白这个概念,还是拿出《小姐姐服务菜单》的Demo,来给大家讲解。比如我们在父组件中可以直接把this.state.list
传递过来。例如下面代码:
<ul>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item}
index={index}
list={this.state.list}
deleteItem={this.deleteItem.bind(this)}
/>
)
})
}
</ul>
其实这样传是没有问题的,问题是你只能使用这个值,而不能修改这个值,如果你修改了,比如我们把代码写成这样:
handleClick(){
//关键代码——---------start
this.props.list=[]
//关键代码-----------end
this.props.deleteItem(this.props.index)
}
就会报下面的错误;
TypeError: Cannot assign to read only property 'list' of object '#<Object>'
意思就是list
是只读的,单项数据流。那如果要改变这里边的值怎么办?其实上节课已经讲过了,就是通过传递父组件的方法。
2. React和其他框架配合使用
是可以的,
React
其实可以模块化和组件化开发。
看/public/index.html
文件,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<!--关键代码start-->
<div id="root"></div>
<!--关键代码end-->
</body>
</html>
其实React
只对这一个<div>
,外边的其他DOM
并不受任何影响,比如我们在它的下方再写一个<div>
,然后查看效果。
<div id="root"></div>
<div style="color:red">今天过的好开心,服务很满意!</div>
你可以在其他的div
里加入任何内容,但是这种情况很少,不建议这么使用。希望小伙伴们还是统一技术栈。
3. 函数式编程
在面试React
时,经常会问道的一个问题是:函数式编程的好处是什么?
- 函数式编程让我们的代码更清晰,每个功能都是一个函数。
- 函数式编程为我们的代码测试代理了极大的方便,更容易实现前端自动化测试。
React框架也是函数式编程,所以说优势在大型多人开发的项目中会更加明显,让配合和交流都得心应手。
(4.5.9) React developer tools
React在浏览器端是有一个调试工具的,这就是React developer tools,这个是React人必下的一个调试工具
React developer tools有三种颜色,三种颜色代表三种状态:
- 灰色: 这种就是不可以使用,说明页面不是又React编写的。
- 黑色: 说明页面是用React编写的,并且处于生成环境当中。
- 红色: 说明页面是用React编写的,并且处于调试环境当中。
打开浏览器,然后按F12,打开开发者工具,然后在面板的最后一个,你会返现一个React,这个就是安装的插件了。
(4.5.10) PropTypes 校验传值
在父组件向子组件传递数据时,使用了属性的方式,也就是props
,但“小姐姐服务菜单”的案例并没有任何的限制。这在工作中时完全不允许的,因为大型项目,如果你不校验,后期会变的异常混乱,业务逻辑也没办法保证。
1. PropTypes的简单应用
我们在Xiaojiejie.js
组件里传递了4个值,有字符串,有数字,有方法,这些都是可以使用PropTypes
限制的。在使用需要先引入PropTypes
。
import PropTypes from 'prop-types'
引入后,就可以在组件的下方进行引用了,需要注意的是子组件的最下面(不是类里边),写入下面的代码:
XiaojiejieItem.propTypes={
content:PropTypes.string,
deleteItem:PropTypes.func,
index:PropTypes.number
}
具体意思,我会在视频中进行讲解,请观看视频。为了防止你的为止写错,我这里给出这个XiaojiejieItem.JS
文件的代码。
import React, { Component } from 'react'; //imrc
import PropTypes from 'prop-types'
class XiaojiejieItem extends Component { //cc
constructor(props){
super(props)
this.handleClick=this.handleClick.bind(this)
}
render() {
return (
<div onClick={this.handleClick}>
{this.props.content}
</div>
);
}
handleClick(){
this.props.deleteItem(this.props.index)
}
}
//--------------主要代码--------start
XiaojiejieItem.propTypes={
content:PropTypes.string,
deleteItem:PropTypes.func,
index:PropTypes.number
}
//--------------主要代码--------end
export default XiaojiejieItem;
这时候你在浏览器中查看效果,是什么都看不出来的,你需要修改一个错误的校验。比如我们把index
改为必须是字符串
index:PorpTypes.string
这时候浏览器的console里就会报错了,报错信息如下:
Warning: Failed prop type: Invalid prop `index` of type `number` supplied to `XiaojiejieItem`, expected `string`.
in XiaojiejieItem (at Xiaojiejie.js:28)
in Xiaojiejie (at src/index.js:5)
意思就是要求传递字符串,而我们却传递了数字过去,所以给了警告。
必传值的校验:
比如现在我们加入一个avname
的属性,并放入JSX
中,就算不传递这个值也不会报错的。代码如下:
render() {
return (
<div onClick={this.handleClick}>
{this.props.avname}为你做- {this.props.content}
</div>
);
}
这时候代码是不会报错的,我们传不传无所谓。比如我们现在传一个属性过来。
<ul>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item}
index={index}
avname='你好'
deleteItem={this.deleteItem.bind(this)}
/>
)
})
}
</ul>
这时候页面显示正常了,但是怎样避免必须传递avname
这个属性值?如果不传递就报错,这就需要使用isRequired
关键字了,它表示必须进行传递,如果不传递就报错。
avname:PropTypes.string.isRequired
使用默认值defaultProps:
defalutProps
就可以实现默认值的功能,比如现在把avname
的默认值设置成"欢迎光临" ,然后把avname
的属性删除掉
XiaojiejieItem.defaultProps = {
avname:'欢迎光临'
}
(4.5.11) ref的使用
在编写组件中的方法时,经常会遇到语义化很模糊的代码,这对于团队开发是一个很大的问题。因为review代码或者合作时都会影响开发效率。或者到这核心成员离开,项目倒闭的严重影响。所以我们必须重视react代码当中的语义化
1. 代替原来项目中e.target.value
以前的案例中,我们写了下面的代码,使用了e.target
,这并不直观,也不好看。这种情况我们可以使用ref
来进行解决
之前代码:
inputChange(e){
this.setState({
inputValue:e.target.value
})
}
如果要使用ref
来进行,需要现在JSX
中进行绑定, 绑定时最好使用ES6
语法中的箭头函数,这样可以简洁明了的绑定DOM
元素。
<input
id="jspang"
className="input"
value={this.state.inputValue}
onChange={this.inputChange.bind(this)}
//关键代码——----------start
ref={(input)=>{this.input=input}}
//关键代码------------end
/>
绑定后可以把上边的类改写成如下代码:
inputChange(){
this.setState({
inputValue:this.input.value
})
}
这就使我们的代码变得语义化和优雅的多。但是就我个人的经验来讲,我是不建议用ref
这样操作的,因为React
的是数据驱动的,所以用ref
会出现各种问题。
1. ref中的坑
比如现在我们要用ref
绑定取得要服务的数量,可以先用ref
进行绑定。
<ul ref={(ul)=>{this.ul=ul}}>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item}
index={index}
deleteItem={this.deleteItem.bind(this)}
/>
)
})
}
</ul>
绑定后可以在addList()
方法中,获取当前<div>
的值.
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
})
//关键代码--------------start
console.log(this.ul.querySelectorAll('div').length)
//关键代码--------------end
}
这时候你打开控制台,点击添加服务按钮,你会返现数量怎么少一个?(就是这个坑),其实这个坑是因为React
中更多setState
是一个异步函数所造成的。也就是这个setState,代码执行是需要有一个时间的,如果你真的想了解清楚,你需要对什么是虚拟DOM
有一个了解。简单的说,就是因为是异步,还没等虚拟Dom
渲染,我们的console.log
就已经执行了。
那这个代码怎么编写才会完全正常那,其实setStat
e方法提供了一个回调函数,也就是它的第二个函数。下面这样写就可以实现我们想要的方法了。
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
//关键代码--------------start
},()=>{
console.log(this.ul.querySelectorAll('div').length)
})
//关键代码--------------end
}
(4.6) React 生命周期
React
的生命周期从广义上分为三个阶段:挂载、渲染、卸载
因此可以把React
的生命周期分为两类:挂载卸载过程和更新过程。
(4.6.1) React的生命周期图
(4.6.2) 废弃三个旧的生命周期函数
React
在 V16.3
版本中,为下面三个生命周期函数加上了 UNSAFE
:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate
- 标题中的废弃不是指真的废弃,只是不建议继续使用,并表示在
V17.0
版本中正式删除。先来说说React
为什么要这么做。
主要是这些生命周期方法经常被_误用和滥用_。并且在 React V16.0
之前,React
是_同步渲染的_,而在 V16.0
之后 React
更新了其渲染机制,是通过_异步的方式进行渲染_的,在 render
函数之前的所有函数都有可能被执行多次。
长期以来,原有的生命周期函数总是会诱惑开发者在 render
之前的生命周期函数中做一些动作,现在这些动作还放在这些函数中的话,有可能会被调用多次,这肯定不是我们想要的结果。
(4.6.2.1)废弃 UNSAFE_componentWillMount 的原因
有一个常见的问题,有人问为什么不在 UNSAFE_componentWillMount
中写 AJAX
获取数据的功能,他们的观点是,UNSAFE_componentWillMount
在render
之前执行,早一点执行早得到结果。但是要知道,在 UNSAFE_componentWillMount
中发起 AJAX
请求,不管多快得到结果也赶不上首次 render
,数据都是要在 render
后才能到达。
而且 UNSAFE_componentWillMount
在服务器端渲染也会被调用到(此方法是服务端渲染唯一会调用的生命周期函数。),你肯定不希望AJAX
请求被执行多次,所以这样的IO
操作放在 componentDidMount
中更合适。
尤其是在 Fiber
启用了异步渲染之后,更没有理由在 UNSAFE_componentWillMount
中进行 AJAX
请求了,因为 UNSAFE_componentWillMount
可能会被调用多次,谁也不会希望无谓地多次调用 AJAX
吧。
还有人会将事件监听器(或订阅)添加到 UNSAFE_componentWillMount
中,但这可能导致服务器渲染(永远不会调用 componentWillUnmount
)和异步渲染(在渲染完成之前可能被中断,导致不调用 componentWillUnmount
)的内存泄漏。
人们通常认为 UNSAFE_componentWillMount
和 componentWillUnmount
是成对出现的,但这并不能保证。只有调用了 componentDidMount
之后,React
才能保证稍后调用 componentWillUnmount
进行清理。因此,添加监听器/订阅的推荐方法是使用 componentDidMount
生命周期。
(4.6.2.2)废弃 UNSAFE_componentWillReceiveProps 的原因
有时候组件在 props
发生变化时会产生副作用。与 UNSAFE_componentWillUpdate
类似,UNSAFE_componentWillReceiveProps
可能在一次更新中被多次调用。因此,避免在此方法中产生副作用非常重要。相反,应该使用 componentDidUpdate
,因为它保证每次更新只调用一次。
UNSAFE_componentWillReceiveProps
是考虑到因为父组件引发渲染可能要根据 props
更新 state
的需要而设立的。新的 getDerivedStateFromProps
实际上与 componentDidUpdate
一起取代了以前的 UNSAFE_componentWillReceiveProps
函数。
(4.6.2.3)废弃 UNSAFE_componentWillUpdate 的原因
有些人使用 UNSAFE_componentWillUpdate
是出于一种错误的担心,即当 componentDidUpdate
触发时,更新其他组件的 state
已经”太晚”了。事实并非如此。React
可确保在用户看到更新的UI
之前,刷新在 componentDidMount
和 componentDidUpdate
期间发生的任何 setState
调用。
通常,最好避免这样的级联更新。当然在某些情况下,这些更新也是必需的(例如:如果你需要在测量渲染的 DOM
元素后,定位工具的提示)。不管怎样,在异步模式下使用 UNSAFE_componentWillUpdate
都是不安全的,因为外部回调可能会在一次更新中被多次调用。相反,应该使用 componentDidUpdate
生命周期,因为它保证每次更新只调用一次。
大多数开发者使用 UNSAFE_componentWillUpdate
的场景是配合 componentDidUpdate
,分别获取 rerender
前后的视图状态,进行必要的处理。但随着 React
新的 suspense
、time slicing
、异步渲染等机制的到来,render
过程可以被分割成多次完成,还可以被暂停甚至回溯,这导致 UNSAFE_componentWillUpdate
和 componentDidUpdate
执行前后可能会间隔很长时间,足够使用户进行交互操作更改当前组件的状态,这样可能会导致难以追踪的 BUG
。
React
新增的 getSnapshotBeforeUpdate
方法就是为了解决上述问题,因为 getSnapshotBeforeUpdate
方法是在 UNSAFE_componentWillUpdate
后(如果存在的话),在 React
真正更改 DOM
前调用的,它获取到组件状态信息更加可靠。
除此之外,getSnapshotBeforeUpdate
还有一个十分明显的好处:它调用的结果会作为第三个参数传入 componentDidUpdate
,避免了 UNSAFE_componentWillUpdate
和 componentDidUpdate
配合使用时将组件临时的状态数据存在组件实例上浪费内存,getSnapshotBeforeUpdate
返回的数据在 componentDidUpdate
中用完即被销毁,效率更高。
(4.6.3) 新增两个生命周期函数
React V16.3
中在废弃(这里的废弃不是指真的废弃,只是不建议继续使用,并表示在 V17.0
版本中正式删除)三个旧的生命周期函数的同时,React
还新增了两个生命周期函数:
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
在React V16.3
版本中加入的static getDerivedStateFromProps
生命周期函数存在一个问题,就是在生命周期的更新阶段只有在props
发生变化的时候才会调用static getDerivedStateFromProps
,而在调用了setState
和forceUpdate
时则不会。
React
官方也发现了这个问题,并在 React V16.4
版本中进行了修复。也就是说在更新阶段中,接收到新的 props
,调用了 setState
和 forceUpdate
时都会调用 static getDerivedStateFromProps
。具体在下面讲到这个函数的时候有详细说明。
(4.6.4) React 生命周期梳理
React 生命周期主要分为三个阶段:
- 挂载阶段
- 更新阶段
- 卸载阶段
(4.6.4.1) 挂载阶段
挂载阶段也可以理解为初始化阶段,也就是把我们的组件插入到 DOM 中。这个阶段的过程如下:
- constructor
- getDerivedStateFromProps
- UNSAVE_componentWillMount
- render
- (React Updates DOM and refs)
- componentDidMount
constructor
组件的构造函数,第一个被执行。如果在组件中没有显示定义它,则会拥有一个默认的构造函数。如果我们显示定义构造函数,则必须在构造函数第一行执行 super(props)
,否则我们无法在构造函数里拿到 this
,这些都属于 ES6
的知识。
在构造函数中,我们一般会做两件事:
- 初始化 state
- 对自定义方法进行 this 的绑定
constructor(props) {
super(props);
this.state = {
width,
height: 'atuo',
}
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
}
getDerivedStateFromProps
使用方式:
//static getDerivedStateFromProps(nextProps, prevState)
class Example extends React.Component {
static getDerivedStateFromProps(props, state) {
//根据 nextProps 和 prevState 计算出预期的状态改变,返回结果会被送给 setState
// ...
}
}
新的 getDerivedStateFromProps
是一个静态函数,所以不能在这函数里使用 this
,简单来说就是一个纯函数。也表明了 React
团队想通过这种方式防止开发者滥用这个生命周期函数。每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps
会被调用,这样我们有一个机会可以根据新的 props
和当前的state
来调整新的 state
。
这个函数会返回一个对象用来更新当前的 state
,如果不需要更新可以返回 null
。这个生命周期函数用得比较少,主要用于在重新渲染期间手动对滚动位置进行设置等场景中。该函数会在挂载时,在更新时接收到新的 props
,调用了 setState
和 forceUpdate
时被调用。
新的 getDerivedStateFromProps
实际上与 componentDidUpdate
一起取代了以前的 UNSAFE_componentWillReceiveProps
函数。UNSAFE_componentWillReceiveProps
也是考虑到因为父组件引发渲染可能要根据 props
更新 state
的需要而设立的。
UNSAVE_componentWillMount
UNSAFE_componentWillMount()
在挂载之前被调用。它在 render()
之前调用,因此在此方法中同步调用 setState()
不会触发额外渲染。通常,我们建议使用 constructor()
来初始化 state
。避免在此方法中引入任何副作用或订阅。如遇此种情况,请改用 componentDidMount()
。
此方法是服务端渲染唯一会调用的生命周期函数。UNSAFE_componentWillMount()
常用于当支持服务器渲染时,需要同步获取数据的场景。
render
这是 React
中最核心的方法,class
组件中唯一必须实现的方法。
当 render
被调用时,它会检查 this.props
和 this.state
的变化并返回以下类型之一:
- 原生的
DOM
,如div
React
组件- 数组或
Fragment
Portals
(插槽)- 字符串和数字,被渲染成文本节点
Boolean
或null
,不会渲染任何东西
render()
函数应该是一个纯函数,里面只做一件事,就是返回需要渲染的东西,不应该包含其它的业务逻辑,如数据请求,对于这些业务逻辑请移到componentDidMount
和componentDidUpdate
中。
componentDidMount
componentDidMount()
会在组件挂载后(插入 DOM
树中)立即调用。依赖于DOM
节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount()
里取消订阅
你可以在 componentDidMount()
里直接调用 setState()
。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在render()
两次调用的情况下,用户也不会看到中间状态。
请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor()
中初始化 state
。如果你的渲染依赖于 DOM
节点的大小或位置,比如实现 modals
和 tooltips
等情况下,你可以使用此方式处理
(4.6.4.2) 更新阶段
更新阶段是指当组件的
props
发生了改变,或组件内部调用了setState
或者发生了forceUpdate
,则进行更新。
这个阶段的过程如下:
- UNSAFE_componentWillReceiveProps
- getDerivedStateFromProps
- shouldComponentUpdate
- UNSAFE_componentWillUpdate
- render
- getSnapshotBeforeUpdate
- (React Updates DOM and refs)
- componentDidUpdate
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillReceiveProps
是考虑到因为父组件引发渲染可能要根据 props
更新 state
的需要而设立的。UNSAFE_componentWillReceiveProps
会在已挂载的组件接收新的 props
之前被调用。如果你需要更新状态以响应 prop
更改(例如,重置它),你可以比较 this.props
和 nextProps
并在此方法中使用 this.setState()
执行 state
转换。
如果父组件导致组件重新渲染,即使 props
没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。在挂载过程中,React
不会针对初始 props
调用UNSAFE_componentWillReceiveProps()
。组件只会在组件的 props
更新时调用此方法。调用 this.setState()
通常不会触发 UNSAFE_componentWillReceiveProps()
。
getDerivedStateFromProps
这个方法在挂载阶段已经讲过了,这里不再赘述。记住该函数会在挂载时,在更新时接收到新的 props
,调用了 setState
和 forceUpdate
时被调用。它与componentDidUpdate
一起取代了以前的 UNSAFE_componentWillReceiveProps
函数。
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
//...
}
它有两个参数,根据此函数的返回值来判断是否进行重新渲染,true
表示重新渲染,false
表示不重新渲染,默认返回 true
。注意,首次渲染或者当我们调用 forceUpdate
时并不会触发此方法。此方法仅用于性能优化。
因为默认是返回true
,也就是只要接收到新的属性和调用了 setState
都会触发重新的渲染,这会带来一定的性能问题,所以我们需要将this.props
与 nextProps
以及 this.state
与 nextState
进行比较来决定是否返回 false
,来减少重新渲染,以优化性能。请注意,返回false
并不会阻止子组件在 state
更改时重新渲染。
但是官方提倡我们使用内置的 PureComponent
来减少重新渲染的次数,而不是手动编写 shouldComponentUpdate
代码。PureComponent
内部实现了对 props
和state
进行浅层比较。
如果 shouldComponentUpdate()
返回 false
,则不会调用 UNSAFE_componentWillUpdate()
,render()
和 componentDidUpdate()
。官方说在后续版本,React
可能会将 shouldComponentUpdate
视为提示而不是严格的指令,并且,当返回 false
时,仍可能导致组件重新渲染。
UNSAFE_componentWillUpdate
当组件收到新的 props
或 state
时,会在渲染之前调用 UNSAFE_componentWillUpdate()
。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。但是你不能此方法中调用 this.setState()
。在 UNSAFE_componentWillUpdate()
返回之前,你也不应该执行任何其他操作(例如,dispatch Redux
的 action
)触发对 React
组件的更新。
通常,此方法可以替换为 componentDidUpdate()
。如果你在此方法中读取 DOM
信息(例如,为了保存滚动位置),则可以将此逻辑移至 getSnapshotBeforeUpdate()
中。
render
这个方法在挂载阶段已经讲过了,这里不再赘述。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {
//...
}
getSnapshotBeforeUpdate
生命周期方法在 render
之后,在更新之前(如:更新 DOM
之前)被调用。给了一个机会去获取 DOM
信息,计算得到并返回一个 snapshot
,这个 snapshot
会作为 componentDidUpdate
的第三个参数传入。如果你不想要返回值,请返回 null
,不写的话控制台会有警告。
并且,这个方法一定要和 componentDidUpdate
一起使用,否则控制台也会有警告。getSnapshotBeforeUpdate
与 componentDidUpdate
一起,这个新的生命周期涵盖过时的 UNSAFE_componentWillUpdate
的所有用例。
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('#enter getSnapshotBeforeUpdate');
return 'foo';
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('#enter componentDidUpdate snapshot = ', snapshot);
}
上面这段代码可以看出来这个 snapshot
怎么个用法,snapshot
乍一看还以为是组件级别的某个“快照”,其实可以是任何值,到底怎么用完全看开发者自己,getSnapshotBeforeUpdate
把 snapshot
返回,然后 DOM
改变,然后 snapshot
传递给 componentDidUpdate
。
官方给了一个例子,用 getSnapshotBeforeUpdate
来处理 scroll
,并且说明了通常不需要这个函数,只有在重新渲染过程中手动保留滚动位置等情况下非常有用,所以大部分开发者都用不上,也就不要乱用
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot) {
//...
}
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法。在这个函数里我们可以操作 DOM
,和发起服务器请求,还可以 setState
,但是注意一定要用if
语句控制,否则会导致无限循环。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
如果组件实现了 getSnapshotBeforeUpdate()
生命周期,则它的返回值将作为 componentDidUpdate()
的第三个参数 snapshot
参数传递。否则此参数将为 undefined
。
(4.6.4.3) 卸载阶段
卸载阶段,这个阶段的生命周期函数只有一个:
componentWillUnmount
componentWillUnmount()
会在组件卸载及销毁之前直接调用。我们可以在此方法中执行必要的清理操作,例如,清除 timer
,取消网络请求或清除在componentDidMount()
中创建的订阅等。注意不要在这个函数里调用setState()
,因为组件不会重新渲染了。
static getDerivedStateFromError()
static getDerivedStateFromError(error) {
//...
}
此生命周期会在后代组件抛出错误后被调用。它将抛出的错误作为参数,并返回一个值以更新 state
。getDerivedStateFromError()
会在渲染阶段调用,因此不允许出现副作用。如遇此类情况,请改用 componentDidCatch()
。
componentDidCatch()
componentDidCatch(error, info) {
//...
}
此生命周期在后代组件抛出错误后被调用。它接收两个参数:
error
—— 抛出的错误。info
—— 带有componentStack key
的对象,其中包含有关组件引发错误的栈信息。
componentDidCatch()
会在“提交”阶段被调用,因此允许执行副作用。它应该用于记录错误之类的情况:
如果发生错误,你可以通过调用 setState
使用 componentDidCatch()
渲染降级 UI
,但在未来的版本中将不推荐这样做。可以使用静态getDerivedStateFromError()
来处理降级渲染。
(4.7) React-请求远程数据
ajax请求框架
Axios
请求远程数据
1. 安装
Axios
的安装可以使用npm
来进行安装,你可以直接在项目根目录下,输入下面的代码。
npm install -save axios
2. axios请求远程数据
安装好axiso
之后,需要在使用ajax
的地方先引入axios
,比如现在想在Xiaojiejie.js
中使用axios
,写入下面的代码进行引入:
import axios from 'axios'
引入后,可以在componentDidMount
生命周期函数里请求ajax
,我也建议在componentDidMount
函数里执行,因为在render
里执行,会出现很多问题,比如一直循环渲染;在componentWillMount
里执行,在使用RN时,又会有冲突。所以强烈建议在componentDidMount
函数里作ajax
请求。
componentDidMount(){
axios.post('https://web-api.juejin.im/v3/web/wbbr/bgeda')
.then((res)=>{console.log('axios 获取数据成功:'+JSON.stringify(res)) })
.catch((error)=>{console.log('axios 获取数据失败'+error)})
}
上面的代码是以掘金的一个接口为例,做了一次ajax
请求。并且请求到了数据,给我们返回了。
(4.8) React-Mock数据
mock数据网站:
创建项目,创建接口,添加数据
将远程获得的数据赋值给数组:
componentDidMount(){
axios.get('xxxx')
.then((res)=>{
console.log('axios 获取数据成功:'+JSON.stringify(res))
this.setState({
list:res.data.data
})
})
.catch((error)=>{console.log('axios 获取数据失败'+error)})
}
(4.9) 实现react动画
(4.9.1) 用css3实现react动画
1. 新建组件Boss
需要给“小姐姐服务菜单”增加一个Boss服务人物,点击一下按钮就会自动出现"Boss级人物-孙悟空",不要管什么恰当不恰当了,咱们是为了练习一下动画。在src
文件夹下,新建一个Boss.js
文件
import React, { Component } from 'react';
class Boss extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return (
<div>
<div>BOSS级人物-孙悟空</div>
<div><button>召唤Boss</button></div>
</div>
);
}
}
export default Boss;
2. 业务逻辑
目前组件没有任何业务逻辑,只有一个UI,这是没办法实现动画效果的。业务逻辑是点击按钮的时候可以改变字的''显示隐藏。
要实现这个业务逻辑,先在constructor
里增加state
值isShow
,详情请看下面的代码。
this.state = {
isShow:true
}
然后把“字”的部分,增加className,并用isShow进行控制。
<div className={this.state.isShow ? 'show' : 'hide'}>BOSS级人物-孙悟空</div>
需要点击按钮时,有响应的事件,所以需要一个方法,我们编写一个toToggole()方法
toToggole(){
this.setState({
isShow:this.state.isShow ? false : true
})
}
意思就是当isShow
为true
时,我们赋值false
;当isShow
为false
时,我们赋值true
有了方法后,可以给<button>
加上onClick
响应事件了
<div><button onClick={this.toToggole}>召唤Boss</button></div>
写完这个事件,还是需要到constructor里绑定一下this
constructor(props) {
super(props);
this.state = {
isShow:true
}
this.toToggole = this.toToggole.bind(this);
}
这样我们的基本业务逻辑就算写完了,可以把代码加入到Xiaojiejie组件中,看一下效果了。
3. 加入CSS动画
在页面上看不出任何的效果,如果你打开浏览器控制台是可以看到每次点击按钮,class
都会变化的。界面没变化,知识我们没有写CSS
。现在可以在style.css
里写样式,代码如下:
.show{ opacity: 1; transition:all 1.5s ease-in;}
.hide{opacity: 0; transition:all 1.5s ease-in;}
这样就用CSS3实现了React中动画,这知识最简单的实践动画
(4.9.2) css3的Keyframes动画
用
transition
只能作一些最简单的动画,如果你想稍微复杂点,transition
就做不出来了。这时候就可以用CSS3
中的关键帧动画keyframes
。
** 1. keyframes介绍**
此属性与animation
属性是密切相关的,keyframes
译成中文就是关键帧,最早接触这个关键帧的概念是字flash中,现在Flash已经退出历史舞台了。他和transition
比的优势是它可以更加细化的定义动画效果。比如我们之前的按钮隐藏动画,不仅可以设置透明度,还可以设置颜色。
@keyframes hide-item{
0% {
opacity:1;
color:yellow;
}
50%{
opacity: 0.5 ;
color:red;
}
100%{
opacity:0;
color: green;
}
}
2.使用动画
使用动画的关键词是animation
,然后后边跟上你的制作的动画名称,如下面这段代码。
.hide{ animation:hide-item 2s ease-in ; }
这句的意思就是,使用hide-item
动画,持续时间是2秒钟,然后缓动效果是由慢到快(开始的时候慢,之后快)。
但是你会发现,动画执行一遍后又恢复了原状,这个是因为没设置forwards
属性,它是用来控制停止到最后一帧的。 我们把代码改写成下面的样子。
.hide{ animation:hide-item 2s ease-in forwards; }
完整代码css:
.show{ animation:show-item 2s ease-in forwards; }
.hide{ animation:hide-item 2s ease-in forwards; }
@keyframes hide-item{
0% {
opacity:1;
color:yellow;
}
50%{
opacity: 0.5 ;
color:red;
}
100%{
opacity:0;
color: green;
}
}
@keyframes show-item{
0% {
opacity:0;
color:yellow;
}
50%{
opacity: 0.5 ;
color:red;
}
100%{
opacity:1;
color: green;
}
}
keyframes
也是只能实现很简单的动画效果,一些复杂的动画最好还是使用别人造好的轮子,下节课继续学习React
中的动画吧。
(4.9.3) react-transition-group
React
生态中有很多第三方的动画组件,你应该学习一下react-transition-group
动画组件
1. 安装
使用它要先进行安装,这里使用npm
的形式进行安装了,当然也可以使用yarn
。
先用VSCode
打开项目根目录,然后打开终端,输入下面的命令,进行安装:
npm install react-transition-group --save
安装好后,你可以先去github
上来看一下文档,他是有着三个核心库(或者叫组件)。
-
Transition
-
CSSTransition
-
TransitionGroup
2. 使用CSSTransition
其实这个库用起来根ng-animate
差不多,先来看看如何使用CSSTransition
。
先用import
进行引入,代码如下:
import { CSSTransition } from 'react-transition-group'
引入后便可以使用了,使用的方法就和使用自定义组件一样,直接写<CSSTransition>
,而且不再需要管理className
了,这部分由CSSTransition
进行管理。修改上节课写的Boss.js
文件里的render
区域。
render() {
return (
<div>
<CSSTransition
in={this.state.isShow} //用于判断是否出现的状态
timeout={2000} //动画持续时间
classNames="boss-text" //className值,防止重复
>
<div>BOSS级人物-孙悟空</div>
</CSSTransition>
<div><button onClick={this.toToggole}>召唤Boss</button></div>
</div>
);
}
需要注意的是classNames
这个属性是由s
的,如果你忘记写,会和原来的ClassName
混淆出错,这个一定要注意。
然后你就可以到CSS
中改写style
了。在修改样式之前,有那些类名。
- xxx-enter: 进入(入场)前的CSS样式;
- xxx-enter-active:进入动画直到完成时之前的CSS样式;
- xxx-enter-done:进入完成时的CSS样式;
- xxx-exit:退出(出场)前的CSS样式;
- xxx-exit-active:退出动画知道完成时之前的的CSS样式。
- xxx-exit-done:退出完成时的CSS样式。
知道了这些要设置的CSS,就可以删除原来写的CSS了,把下面的代码写上:
.input {border:3px solid #ae7000}
.boss-text-enter{
opacity: 0;
}
.boss-text-enter-active{
opacity: 1;
transition: opacity 2000ms;
}
.boss-text-enter-done{
opacity: 1;
}
.boss-text-exit{
opacity: 1;
}
.boss-text-exit-active{
opacity: 0;
transition: opacity 2000ms;
}
.boss-text-exit-done{
opacity: 0;
}
这时候你的动画样式就正常了,你回发现我们再也不用自己管理className
了,而是完全交给了react-transition-group
来作。
3. unmountOnExit属性
我们给<CSSTransition>
加上unmountOnExit
,加上这个的意思是在元素退场时,自动把DOM
也删除,这是以前用CSS
动画没办法做到的。
render() {
return (
<div>
<CSSTransition
in={this.state.isShow} //用于判断是否出现的状态
timeout={2000} //动画持续时间
classNames="boss-text" //className值,防止重复
unmountOnExit
>
<div>BOSS级人物-孙悟空</div>
</CSSTransition>
<div><button onClick={this.toToggole}>召唤Boss</button></div>
</div>
);
}
(4.9.4) 多DOM动画制作和编写
控制多个动画
react-transition-group
这个动画库也是可以做到的。
1. 使用TransitionGrop
它就是负责多个DOM
元素的动画的,我们还是拿小姐姐这个案例作例子,现在可以添加任何的服务项目,但是都是直接出现的,没有任何动画,现在就给它添加上动画。添加动画,先引入transitionGrop
。
直接打开/src/Xiaojiejie.js
的文件,然后在最顶部同时
import {CSSTransition , TransitionGroup} from 'react-transition-group'
引入之后,就可以使用这个组件了,方法是在外层增加<TransitionGroup>
标签。
<ul ref={(ul)=>{this.ul=ul}}>
<TransitionGroup>
{
this.state.list.map((item,index)=>{
return (
<XiaojiejieItem
key={index+item}
content={item}
index={index}
deleteItem={this.deleteItem.bind(this)}
/>
)
})
}
</TransitionGroup>
</ul>
这个需要放在循环的外边,这样才能形成一个组动画,但是只有这个<TransitonGroup>
是不够的,你还是需要加入<CSSTransition>
,来定义动画。
2. 加入
可以完全仿照上节课的经验,为Xiaojiejie
组件,加上具体的动画设置,就可以实现多DOM
元素的动画效果了。代码如下:
<ul ref={(ul)=>{this.ul=ul}}>
<TransitionGroup>
{
this.state.list.map((item,index)=>{
return (
<CSSTransition
timeout={1000}
classNames='boss-text'
unmountOnExit
appear={true}
key={index+item}
>
<XiaojiejieItem
content={item}
index={index}
deleteItem={this.deleteItem.bind(this)}
/>
</CSSTransition>
)
})
}
</TransitionGroup>
</ul>
<Boss />
</Fragment>
总结:React
动画还有很多知识,能做出很多酷炫的效果,完全可以单独分出来一个岗位,我在工作中用的都是比较简单的动画,用react-transition-group
动画已经完全可以满足我的日常开发需求了。如果你想学习更多的React
动画知识,可以看看文档或者书。