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就已经执行了。
那这个代码怎么编写才会完全正常那,其实setState方法提供了一个回调函数,也就是它的第二个函数。下面这样写就可以实现我们想要的方法了。
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动画知识,可以看看文档或者书。

浙公网安备 33010602011771号