React特点
声明式编程
- 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI
- 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面
组件化开发
- 组件化开发页面是目前前端的流行趋势,我们会将复杂的页面拆分成一个个小的组件
- 如何合理的进行组件的划分和设计也是重点
多平台适配
- React主要是用于开发Web页面
- ReactNative用于开发移动端跨平台
- ReactVR用于开发虚拟现实Web应用
案例
在页面显示一个文本 Hello World
点击下方的一个按钮,点击之后文本改变为Hello React
原生代码实现
React开发
React开发依赖
- 必须依赖三个库
- react:包含react所必须的核心代码
- react-dom:react渲染在
不同平台
所需的核心代码 - babel:将jsx转换成React代码的工具
- Vue只需要一个Vue.js文件即可,但是React需要依赖三个库
- 0.14版本之前没有react-dom,所有功能都包含的在react中
- 这三个库各司其职
- 拆分的原因:react-native
- react中包含了react和react-native所共有的核心代码
- react-dom针对web和native所完成的事情不同
- web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
- native端:react-dom会将jsx最终渲染成原生的控件(如Android中的Button,IOS中的UIButton)
Babel
- Babel是什么
- Babel又名Babel.js
- 是目前前端使用非常广泛的编译器、转移器
- 当下很多浏览器不支持ES6语法,但是ES6的语法非常的简洁方便
- 编写源码时可以使用ES6来编写,之后通过Babel工具将ES6转换成大多数浏览器都支持的ES5语法
- React和Babel的关系
- 默认情况下开发React可以不使用babel
- 自己需要写React.createElement来编写代码,非常的繁琐,可读性也非常差
- 所以我们可以直接编写jsx(JavaScript XML)的语法,通过Babel来转换成React.createElement
引入React依赖
- 添加依赖的三种方式
- 直接通过CDN引入
- 下载到本地,添加本地依赖
- 通过npm管理
暂时可以通过CDN进行引入
这里的crossorigin属性,是为了拿到跨域脚本的错误信息
用React实现Hello World
- 使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性
text/babel
- ReactDOM.render
- 参数一:要渲染的内容,可以是HTML元素,也可以是React组件
- 参数二:要将渲染分内容挂载到哪一个HTML元素上
- 通过
{}
语法来引入外部的变量或者表达式,在Vue中使用的{{}}
,需要注意区分
添加按钮,实现文本改变
错误语法
jsx特点:多个组件最外层(根)只能有一个组件
正确语法
添加点击事件
创建点击触发事件function
绑定点击事件
点击页面上的按钮,发现控制台有日志打印,但是页面的文本并没有改变
在Vue中,绑定的元素发生改变,页面数据会立即跟着改变,但是在React中,需要重新渲染DOM
最终代码
组件化开发
整个逻辑可以看做是一个整体,那么就可以将它封装成一个组件
ReactDOM.render的第一个参数是一个HTML或者一个组件
所以可以将之前的业务逻辑封装到一个组件中,然后传入到ReactDOM.render函数的第一个参数
封装组件
暂时使用类的方式封装组件
- 定义一个类(类名大写,组件的名称必须是大写的,小写会被认为是HTML元素),继承自React.Component
- 实现当前组件的render函数,render中返回的jsx内容就是之后React去渲染的内容
数据依赖
组件中的数据,可以分为两类
- 参与界面更新的数据:当数据改变时,需要重新将组件渲染的内容
- 不参与界面更新的数据:当数据改变时,不需要更新将组件渲染的内容
参与界面更新的数据可以称之为参与数据流,这个数据是定义在当前对象的state中
- 可以通过在构造函数中
this.state={定义的数据}
- 当数据发生变化时,可以调用
this.setState
来更新数据,并且通知React进行更新操作 - 在进行更新操作时,会重新调用render函数,并且使用最新的数据来渲染页面
事件绑定
事件绑定中的this
在类中直接定义一个函数,并且将这个函数绑定到HTML原生的onClick事件上,当前这个函数的this指向的是谁?
默认情况下,btnClick中的this是undefined
- 在正常的DOM操作中,监听点击,监听函数中的this其实就是节点对象(比如button对象)
- React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质上是React的Element对象
- 那么在这里发生监听的时候,react给我们的函数绑定的this,默认情况下就是undefined
我们在绑定的函数中,可能想要使用当前对象,比如执行this.setState函数,就必须拿到当前对象的this
- 需要在传入函数时,给这个函数直接绑定this
- 例如
</button onClick={this.btnClick.bind(this)}>改变文本</button>
完整代码
ES6的class
在ES6之前,通过function来定义类,但是这种模式一直被很多从其他编程转到JavaScript的人所不适应,因为大多数面向对象的语言,都是使用class关键字来定义类的。
JavaScript从ES6开始引入了class关键字,用于定义一个类。
类中有一个constructor构造方法,当我们通过new关键字调用时,就会默认执行这个构造方法
构造方法中可以给当前对象添加属性
类中也可以定义其他方法,这些方法会被放到类的prototypy上
另外,属性也可以直接定义在类上
继承是面向对象的一大特性,可以减少我们重复代码的编写,方便公共内容的抽取。
ES6中增加了extends关键字来作为类的继承
在constructor中,子类必须通过super来调用父类的构造方法,对父类进行初始化,否则会报错。
数组的map方法
案例练习
电影列表展示
计数器
JSX
这段element变量的声明右侧赋值的标签语法
- 它不是一段字符串,没有使用引号包裹,看起来像一段html
- 正常情况下这么写是不可以的,如果将type="text/babel"去掉,就会出现语法错误
JSX是什么
- JSX是一种JavaScript的语法扩展(extension),也在很多地方称之为JavaScript XML
- 它用于描述我们的UI界面,并且其完全可以和JavaScript融合在一起使用
- 它不同于Vue中的模块语法,不需要专门学习模块语法中的一些指令(v-if,v-for,v-else,v-bind)
React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
- 比如UI需要绑定时间(button,a原生标签等)
- 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI
他们之间是密不可分的,所以react没有将标记分离到不同的文件中,而是组合到到了一起,这个地方就是组件(Component)
JSX其实是嵌入到JavaScript中的一种结构语法。
JSX的书写规范
- JSX的顶层只能有一个根元素,所以很多时候我们会在外层包裹一个div原生(或者后面学习的Fragment)
- 为了方便乐队,通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写
- jsx中的标签可以是单标签,也可以双标签,如果是单标签,必须以/>结尾
jsx中的注释
jsx嵌入变量
- 情况一:当变量是number、string、array类型时,可以直接显示
- 情况二:当变量是null、undefined、boolean类型时,内容为空
- 如果希望可以显示null、undefined、boolean,那么需要转换成字符串
- 可以调用toString()方法,或者直接拼接字符串,或者String(变量)等方式
- 情况三:对象类型不能作为子元素(not valid as a React child)
jsx嵌入表达式
- 可以是运算表达式,如数字计算,字符串拼接等
- 可以是三目表达式
- 可以是一个函数
JSX的使用
JSX绑定属性
- 比如元素都会有title属性
- 比如img元素会有src属性
- 比如a元素会有herf属性
- 比如元素可能需要绑定class
- 比如原生使用内联样式style
事件绑定
- 如果原生DOM原生有一个监听事件,我们如何操作
- 方式一:获取DOM原生,添加监听时间
- 方式二:在HTML原生中,直接绑定onClick
- 在React中如何操作
- 在React事件的命名采用小驼峰(lowCamelCase),而不是纯小写
- 需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行
this的绑定问题
- 事件执行后,我们可坑需要获取当前类的对象中的相关属性,这个时候需要用到this
- 如果这里直接打印this,会发现它是一个undefined
- 为什么是undefined
- 原因是btnClick函数并不是我们主动调用的,而是当button发生改变,React内部调用了btnClick函数
- 而它内部调用时,并不知道如何绑定正确的this
解决this的问题
- 方案一:bind给btnClick显示绑定this
- 方案二:使用ES6的class field语法
- 方案三:事件监听时传入箭头函数
事件参数传递
- 在执行事件函数时,有可能我们需要获取一些参数信息,比如event对象、其他参数
- 情况一:获取event对象
- 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
- 假如我们用不到this,那么直接传入函数就可以获取到event对象
- 情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染部分内容
- 在vue中,我们可以通过指令来控制,比如v-if,v-show
- 在react中,所有的条件判断都和普通的JavaScript代码一致
常见的条件渲染的方式
- 条件判断语句,适合逻辑较多的情况
- 三目运算符,适合逻辑比较简单的情况
- 逻辑与运算符&&,适合如果条件成立,渲染一个组件;如果条件不成立,什么内容都不渲染
- v-show的效果。主要是控制display属性是否为none
列表渲染
真实开发中,我们会从服务器请求到大量的数据,数据会以列表的形式存储
- 比如歌曲、歌手、排行榜列表的数据
- 比如商品、购物车、评论列表的数据
- 比如好友消息、动态、联系人列表的数据
在React中并没有Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX
- 很多从vue转型到react的同学非常不习惯,认为vue的方式更加简洁明了
- 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活
在React中,展示列表最多的方式就是使用数组的map高阶函数
很多时候,我们在展示一个数组中的数据之前,需要先对它进行一些处理
- 比如过滤掉一些内容:filter函数
- 比如截取数组中的一部分内容:slice函数
JSX的本质
jsx仅仅是React.createElement(component, props, …children)函数的语法糖
所有的jsx最终都会被转换成React.createElement的函数调用
React.createElement在源码的packages–>react–>src–>ReactElement.js文件
createElement需要传递三个参数
- type
- 当前ReactElement的类型
- 如果是标签元素,那么就用字符串表示,如
"div"
- 如果是组件元素,那么就直接使用组件的名字
- config
- 所有jsx中的属性都在config中以对象的属相和值的形式存储
- children
- 存放在标签中的内容,以children数组的方式进行存储
- 如果是多个元素,React内部有对它们进行处理
Babel
默认jsx是通过babel帮助我们进行语法转换的,所以我们之前写的jsx代码都需要babel进行转换
可以在babel的官网中快速查看转换的过程https://babeljs.io/repl/#?presets=react
直接编写jsx代码
如果我们自己来编写React.createElement代码,就可以不使用babel相关的内容
虚拟DOM
虚拟DOM的创建过程
我们通过React.createElement最终创建出来一个ReactElement对象
React利用ReactElement对象组成了一个JavaScript对象树
这个JavaScript的对象树就是虚拟DOM(Virtual DOM)
我们可以将之前的jsx返回结果进行打印,就可以查看到ReactElement对象树
JSX代码
ReactElement对象
真实DOM
为什么使用虚拟DOM
为什么采用虚拟DOM,而不是直接修改真实DOM
- 很难追踪状态发生的变化:原有的开发模式,很难追踪到状态发生的变化,不方便针对我们应用程序进行调试
- 操作真实DOM性能较低:传统的开发模式,会频繁的对DOM进行操作,而这种做法的性能非常的低
DOM操作性能低:
- document.createElement本身创建出来的是一个非常复杂的对象https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
- DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作
频繁操作DOM的问题
比如现在有一组数组需要渲染:[0,1,2,3,4],我们可以通过ul和li将他们展示出来
后来我们又增加了5条数据:[0,1,2,3,4,5,6,7,8,9]
方式一:重新遍历整个数组
方式二:在ul后面追加另外5个li
这段代码的性能非常低效
因为我们通过document.createElement创建元素,再通过ul.appendChild(li)渲染到DOM上,进行了多次DOM操作
对于批量操作,最好的办法不是一次次修改DOM,而是对批量的操作机械能合并,比如可以通过DocumentFragment进行合并
我们可以通过Virtual DOM来帮助我们解决上面的问题
声明式编程
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
React官方的说法:Virtual DOM是一种编程理念
- 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
- 我们可以通过ReactDOM.render让虚拟DOM和真实DOM同步起来,这个过程叫做协调(Reconciliation)
这种编程方式赋予了React声明式的API
- 你只需要告诉React希望UI是什么状态
- React来确保DOM和这些状态是匹配的
- 你不需要直接进行DOM操作,可以从手动更改DOM、属性操作、事件处理中解放出来
案例练习
需求
- 在界面以表格的形式,显示一些数据的数据
- 在底部显示书籍的总加个
- 点击+或者-可以减少书籍数量,如果数量为1,则不能继续-
- 点击移除按钮,可以将数据移除,当所有书籍都被移除完毕时,显示:购物车为空