一、react中的虚拟DOM
- react中组件化是直接使用js实现,vue中通过
实现;
.vue文件
- 在vue中模板
;
template(模板结构)->ast(普通对象)->render函数(带变量的函数对象)->虚拟dom(赋值的函数对象)
- 在react中直接
;
js->虚拟dom
- 使用虚拟dom,页面源码中是没有相应的dom标签,页面中元素都来自于引入的js文件;浏览器也不用解析相应html元素,直接解析js,形成真正的dom。
- 与vue:react中挂载是嵌套在
中;vue是直接替换;import引入时,都是将子组件文件以对象键值对形式导入,不同的是react中jsx语法会被解析;但是生命周期都还是没有开始;
index.html的#app
- vue是在父组件
开始子组件的生命周期,通过解析子组件标签触发的组件生命周期;react也是,在解析到子组件标签后才开始子组件的生命周期。
beforeMount周期后
- ** react中引入时子组件render函数中jsx语法已经被解析,但createElement()中值也是变量存在,因为生命周期还没有开始;vue中引入子组件是只是组件文件的对象形式(不是组件对象),生命周期也没有开始,加载时子组件render函数要在挂载阶段与自身Vue实例结合才把render函数中变量赋值,然后执行render函数,返回h(具体值/),这个执行完就是虚拟dom;**
- DOM
-
:就是指用js对象表示的UI元素;是浏览器在内存中形成的很复杂的对象;本质
-
:只不过是浏览器解析生成的,浏览器在js中提供了固定的 DOM API来操作 DOM 对象;特点/区别
-
:当元素发生更新时,内存中存在新旧两个DOM树对象,浏览器通过新树替换旧树进行重新渲染页面;工作原理
-
- 虚拟DOM
-
:手动使用JS对象来模拟(相对真正dom对象做了简化)DOM树,也是对象,然后通过特定方法渲染成真正的DOM,在页面中出效果;本质
-
:弥补浏览器不能新旧对比、实现指定更新的不足,为了实现DOM节点的高效更新;作用
-
:通过JS创建对象,模拟一个DOM节点,节点中有children属性描述子节点,通过节点间嵌套,就模拟出了一个DOM树;怎么模拟DOM树
-
:不是由浏览器提供的,是开发人员自己通过JS模拟出来的,浏览器解析的是js代码,然后参照这个js代码创建真正dom树展示;特点/区别
-
:在页面更新前,使用JS语法模拟出新旧两个DOM树,(因为浏览器没有提供获取它内存中DOM树的接口),然后使用
进行比较;让浏览器只更新有变化的元素。diff算法
-
- 像vue、react框架中,页面都是通过虚拟DOM转为真正DOM来展示的;原理都类似;只是感觉react更直白一些,vue更贴心一些,写的时候还是可以写html、css;
- react中创建页面元素:与内部创建虚拟DOM做法类似,都是直接通过
实现,不再直接写html标签;与vue不同(通过html标签写页面);js+接口
二、react中的Diff算法
前提
:有新旧两个虚拟DOM了,通过把旧虚拟dom一步步修改变成新虚拟dom一样的过程中,得到需要变化的元素;
意义
:diff 是通过JS层面的计算,对比新旧虚拟DOM,返回一个patch对象,即补丁对象,在通过特定的操作解析patch对象,完成页面的重新渲染;
1、
tree diff
:将新旧两个整体的虚拟DOM,逐层对比;其中包含组件对比、元素对比;
2、
componment diff
:(react中组件类似vue,就是主页面引入的部分)在对比每一层时,进行组件之间对比;
- 组件对比规则:
- 相同位置,两个组件类型相同,就暂且保存先认为不用更新;如果类型都不同,那直接移除旧组件,新建组件替换到被移除的位置;(这些后会被记录)
3、
element diff
:组件中元素之间的对比;
4、
key属性
:把页面中 DOM节点 与虚拟DOM中的对象,做一层关系。
三、react中的JSX语法
背景
:因为在react中书写元素都是在js中,react原生提供了
react、react-dom
来支持;
import React from 'react'
/react模块,用于专门来创建react组件、生命周期等;
import ReactDom from 'react-dom'
/react-dom模块,用于将组件渲染到页面上,内部封装了和DOM相关的包;
var myDiv = React.createElement('div',{title:'mytitle',id:'myDiv'},'文本内容');
ReactDom.render(myDiv,document.getElementById('app'))
/=ReactDom.render(虚拟dom,嵌套元素)
不足
:原生方法书写html元素,比较麻烦,开发人员更喜欢可以以手写
html代码
的方式定义页面,所以react中提供了
JSX语法
来支持在 js 中书写html标签;
1、jsx介绍
-
:符合 XML 规范的 JS 语法,即手写的html标签要符合XML标准;定义
-
:内部将js中的html代码用原生原理
转换成React.createElement方法
,再交给js对象
渲染;ReactDOM.render()
-
:webpack一开始是不识别JSX语法的,需要安装配置怎么用
语法转换工具(@babel/preset-react
babel-loader 7.x对应:babel-preset-react);注:
-
效果展示
var myDiv = <div id="myDiv">jsx识别的语法
<p id="mySpan">js中写html没有语法提示</p>
</div>
// =后面就是jsx语法,
//会被解析成render函数的返回值:
//React.createElement('div',{title:'mytitle',id:'myDiv'},'文本内容')
ReactDom.render(myDiv,document.getElementById('app')) //传入render函数,内部执行形成得到createElement()函数,这个函数执行生成虚拟dom,并渲染出真正dom
2、jsx语法解析规则:
-
:在基本规则
遇到js文件中
就按html代码解析,转成js对象;遇到<>
或者正常的js代码就按js代码解析;{}
-
:在HTML代码中有一些名称与js关键字冲突,所以在使用时要改名;如:JSX中要写变化
,语法内会解析成正常<div className="myClass"></div>
<div class = "meClass">
-
:单行、多行注释JSX中html的注释
{/* 这是p标签 */}
<p id="mySpan">js中写html没有语法提示</p>
{
// 多行注释
// 多行注释
}
四、react中的组件
组件文件
:组件文件名后缀:
.jsx/.js
,区别在于
.jsx文件
中有html语法提示;都需要然后在webpack中配置
babel语法转换器
;
1、
function方式
定义组件
- 函数组件通过
形参,接收外界传进来的数据,将props
,而且数据是只读的,不能内部修改;接收的数据以props对象的属性方式存储
- 外界通过在函数标签中定义属性的方式传递数据;
- 函数组件定义时必须大写开头,因为函数的组件使用方式就是以函数名为标签名,而
中解析标签是:小写标签按正常html标签解析,大写标签才会当做组件标签解析;jsx语法
组件:Hello.js
import React from 'react'
function Person(props){
return <div>
<h1>哈哈---{props.name+"--"+props.age}</h1>
</div>
}
export default Person
---
入口文件:main.jsx
import Hello from './componments/Hello' /=>类似vue,引入Hello组件直接就是render函数,
/=>这是由文件内部导出时解析结果决定的
var one = {
name:'zs',
age:12
}
ReactDom.render(<Person one={one}></Person>
,document.getElementById('app'))
//或者可以展开方式:{props.name+"---"+props.age}
ReactDom.render(<Person {...one}></Person>
,document.getElementById('app'))
2、
class方式
定义组件
- 类的实例是继承构造函数中的属性和方法;
- 类中构造函数外定义的普通方法挂载到原型对象上,类的实例可以通过原型链使用;
- 类中构造函数外定义的属性(只能定义静态)、静态方法是挂载到了构造器上,类的实例访问不到;
-
:对比class与function构造函数补充
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(){
console.log('函数原型上的方法');
}
Person.info = '构造函数自身的属性';
Person.addInfo =function(){
console.log('构造函数自身的方法');
};
var p1 = new Person('zs',21)
console.log(p1);
----
class Per{
/类中只能定义方法(实例方法、静态方法)和静态属性
/类中方法定义到构造函数原型上,只能调用原型或通过实例使用
/类中静态属性、静态方法定义在构造函数自身,只能自身或子类继承后调用
constructor(name,age){//类中的构造函数(方法),new实例自动执行
this.name = name;
this.age = age;
}
say(){//方法
console.log('class中构造函数原型上的方法');
}
static info = '类中的静态属性';//静态属性
static add = function(){
console.log('静态方法也定义构造函数自身');
}
}
-
:在class定义组件中主要用到补充
类的封装、继承特性
class Per{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log('定义在Per构造函数原型上的方法');
}
static info = 'zs';/构造函数中的属性,只能通过构造函数调用
}
class Chinese extends Per{
constructor(name,age,color){
super(name,age);/父类的传参必须放在前面
this.color = color;
}
}
var p2 = new Chinese('yellow','zs',22)
console.log(Chinese.info);/子构造函数可以调用父构造函数中静态属性/方法,但是实例不行
- 需要继承
;React.component
- 类中需要定义
render方法返回null或jsx语法(让内部解析成createElement()函数)
- 与函数组件类似,最终都是用名字做标签名,内部最终返回的都是:h();
-
,参数1:使用的就是ReactDom.render()
React.createElement()
main.js:
class Hello2 extends React.Component{ /=>继承react组件功能
render(){
return <div>这是class类创建的组件{this.props.aa}</div>
} /=>定义render函数,内部返回值会被JSX解析,所以最终return的结果就是:createElement()函数;
}
ReactDom.render(<div>
<Hello2 aa='asdClass'></Hello2> ://=这就相当于new了一个Hello2类的实例并执行了render函数,render是实例原型链上的方法
</div>,document.getElementById('app'))
3、对比两种组件定义方式
- 区别一:参数props的使用
- 在function组件中,通过标签接收参数,需要传入
,通过props
的方式使用;{props.[属性名]}
- 在class组件中,同样标签接收参数,不需要传参数(也没有地方接收),直接通过
使用;{this.props.[属性名]}
- 在function组件中,通过标签接收参数,需要传入
- 区别二:两者都不能对传入参数做修改;
- 区别三:函数组件是无状态的组件;class类定义组件是有状态的;
- 有状态:可以有自己的类似vue中data的属性,即
;this.state={}
- 函数组件使用时就是执行构造函数返回 h()虚拟dom
- class组件使用时也执行了new,同时也执行了render函数,状态就可以在new时创建,在render函数执行时改变;
- 有状态:可以有自己的类似vue中data的属性,即
- 有无状态区别:
- 有状态组件:有私有数据(state属性),有生命周期函数;适用于需要修改数据的组件;
- 无状态组件:没有私有数据,无state属性,无生命周期函数;适用于数据不改变的组件,
;如子组件
4、组件中state/data与props区别
- 前者是组件私有的数据,通过ajax请求来的;
- 后者是外界传过来的数据;
- 前者的数据是可读可写的;
- 后者的数据是只读的;
五、react中的class有状态组件应用
1、路径问题
path.join(__dirname,'./aa'); /='aa' '/aa' './aa' 效果一样:e:\QD\react\aa
path.join(__dirname,'../aa'); /= 向上一级:e:\QD\aa
不识别'/'根目录
path.resolve(__dirname,'./aa'); /='aa' './aa' 效果一样:e:\QD\react\aa
path.resolve(__dirname,'../aa'); /= 向上一级:e:\QD\aa
path.resolve(__dirname,'/aa'); /= e:\aa
都是转为绝对路径;resolve识别/,为根目录
下一个》》2、