React基础
1.1. React基本认识
1.2. React基本使用
1.3. JSX的理解和使用
React组件化编程
2.1. 基本理解和使用
2.2. 组件的3大属性: state, props, refs
2.3. 组件中的事件处理
2.4. 组件的组合使用
2.5. 组件收集表单数据
2.6. 组件的生命周期
Create React App 创建React 前端工程
3.1 工程目录结构
3.2 试运行
3.2 打包编译
题外话题:页面性能
由一道面试题引发的思考:从用户浏览器输入URL到页面最后呈现有哪些过程?
网络传输阶段:
用户输入URL地址
浏览器解析URL解析出主机名
浏览器将主机名转换成服务器ip地址(浏览器先查找本地DNS缓存列表 没有的话 再向浏览器默认的DNS服务器发送查询请求 同时缓存)
浏览器将端口号从URL中解析出来
浏览器建立一条与目标Web服务器的TCP连接(三次握手)
浏览器向服务器发送一条HTTP请求报文
服务器向浏览器返回一条HTTP响应报文
如果文档中有资源 重复6 7 动作 直至资源全部加载完毕
页面渲染阶段:
HTML解析出DOM Tree
CSS解析出Style Rules
将二者关联生成Render Tree
Layout 根据Render Tree计算每个节点的信息
Painting 根据计算好的信息绘制整个页面
我们关心的性能问题
排除网络问题,页面加载消耗时间在哪里?
主要消耗时间:解析DOM 树、解析Style Rules、计算布局(排布)、绘制过程。
综合平台首页
加载时间分析:
对浏览器来说,我们日常开发中做的最多的事情是什么?
1、根据业务数据,添加、修改或者删除DOM元素
2、改变元素CSS样式,以满足不同操作场景
结论:
当解析的html文件很大时(复杂页面),生成DOM树占用内存也较大,同时遍历元素耗时也很长。DOM的更核心问题是:DOM修改导致的页面重绘、重新排版!重新排版是用户阻塞的操作,直接影响用户体验,同时,如果频繁重排,浏览器CPU使用率也将大涨!
为了提高页面性能,我们努力的目标:减少或者避免DOM操作,减轻或者减少浏览器重绘、重排的过程
我们前端技术(基于DOM操作多少)的演进:
1、asp.net 服务器端技术,每次交互重新获取页面所有数据,重新渲染界面。
2、ajax阶段,获取数据,局部更新页面。比如EasyUI\Extjs
3、虚拟DOM,DIFF后更细粒度更新DOM
1. React基础
ReactJS的背景和原理
在Web开发中,我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因。
“Any problem in computer science can be solved by anther layer of indirection.”
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
找对象也是如此,单身的你也许就缺一个中间层:介绍人。
为了解决和优化性能,React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新(减少、减轻DOM更新)。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A变成B,然后又从B变成A,React会认为UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。
另外,尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是Diff
部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
百度百科
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
虚拟DOM与DOM diff算法
虚拟DOM是什么?
React会在内存中构建一个相对应的DOM树,基于React开发时所有的DOM构造都是通过虚拟DOM进行。每当组件的状态发生变化时,React都会重新构建整个DOM数据,然后将当前的整个DOM树和上一次的DOM树进行对比,得出DOM结构变化的部分(Patchs),然后将这些Patchs 再更新到真实DOM中。整个过程都是在内存中进行,因此是非常高效的。
进一步理解
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
初始渲染时,首先将数据渲染为 Virtual DOM,然后由 Virtual DOM 生成 DOM。
数据更新时,渲染得到新的 Virtual DOM,与上一次得到的 Virtual DOM 进行 diff,得到所有需要在 DOM 上进行的变更,然后在 patch 过程中应用到 DOM 上实现UI的同步更新。
Virtual DOM
又是结论
我们基于React开发页面,其实就是在往虚拟DOM里面添加元素而已。
比较现行框架后的直观认识
1.1. React的基本认识
- 1). Facebook开源的一个javascript库
- 2). 一个用来动态
构建用户界面
的javascript库(最终还是和Dom打交道) - 3). React的特点
- Declarative(声明式编码)
- Component-Based(组件化编码) :通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发
- Learn Once, Write Anywhere(支持客户端与服务器渲染)
- 高效
- 单向数据流: React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单
- 4). React高效的原因
- 虚拟(virtual)DOM, 不总是直接操作DOM(批量更新, 减少更新的次数)
- 高效的DOM Diff算法, 最小化页面重绘(减小页面更新的区域)
1.2. React的基本使用
导入相关js库文件(react.js, react-dom.js)
-
编码:
HelloWorld_React 问题:这种虚拟DOM的定义是不是不够直观?
1.3. JSX的理解和使用
02_JSX
从上面的代码可以看到将 HTML 直接嵌入了 JS 代码里面,这个就是 React 提出的一种叫 JSX 的语法,这应该是最开始接触 React 最不能接受的设定之一,因为前端被“表现和逻辑层分离”这种思想“洗脑”太久了。但实际上组件的 HTML 是组成一个组件不可分割的一部分,能够将 HTML 封装起来才是组件的完全体,React 发明了 JSX 让 JS 支持嵌入 HTML 不得不说是一种非常聪明的做法,让前端实现真正意义上的组件化成为了可能。(其实Easyui的控件,都是将Html代码包含在了js里面。)
- 理解
- 全称: JavaScript XML( JSX简介)
- react定义的一种类似于XML的JS扩展语法: XML+JS
- 作用: 用来创建react虚拟DOM(元素)对象
- 编码相关
- js中直接可以套标签, 但标签要套js需要放在{}中
- 在解析显示js数组时, 会自动遍历显示
- 把数据的数组转换为标签的数组:
var liArr = dataArr.map(function(item, index){
return {item}
})
- 注意:
- 标签必须有结束
- 标签的class属性必须改为className属性
- 标签的style属性值必须为: {{color:'red', width:12}}
再结论
JSX解决了React定义虚拟DOM时,JS脚本写HTML标签的尴尬
2. react组件化开发
VM定义问题:能不能通过定义个函数来返回虚拟DOM结构呢?
2.1. 基本理解和使用
03_component_basic
ReactDOM.render()渲染组件标签的基本流程
- React内部会通过创建
组件实例对象/调用组件函数
, 得到虚拟DOM对象(雷同React.createElement对象) - 将虚拟DOM解析为真实DOM
- 插入到指定的页面元素内部
2.2 组件的3大属性
问题:对比Easyui的控件,我们能给React组件传递些参数么?如何转递和接受参数?React为什么不是MVVM?特殊情况真实DOM元素我该如何获取?
2.2.1. 组件的3大属性: props
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为 “props”。
function Welcome(props) {
return Hello, {props.name}
;
}
const element = ;
ReactDOM.render(
element,
document.getElementById('root')
);
2.2.2. 组件的3大属性: state
组件的state是组件内部的状态,state的变化最终将反映到组件UI的变化上(React单向数据流的体现)。我们在组件的构造方法constructor中通过this.state定义组件的初始状态,并通过调用this.setState方法改变组件状态(也是改变组件状态的唯一方式),进而组件UI也会随之重新渲染。
//初始化指定:
constructor() {
super()
this.state = {
stateName1 : stateValue1,
stateName2 : stateValue2
}
}
//读取显示:
this.state.stateName1
//更新状态-->更新界面 :
this.setState({stateName1 : newValue})
State 与 Props 区别
props 是组件对外的接口,state 是组件对内的接口。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的props属性进行传递,因此props是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口state。根据对外接口props 和对内接口state,组件计算出对应界面的UI。
主要区别:
- State是可变的,是一组用于反映组件UI变化的状态集合;
- 而Props对于使用它的组件来说,是只读的,要想修改Props,只能通过该组件的父组件或者外围修改。在组件状态上移的场景中,父组件正是通过子组件的Props, 传递给子组件其所需要的状态。
2.2.3. 组件的3大属性: refs
特殊情况下需要获取组件内部元素的DOM对象。
2.3. 组件中的事件处理
问题:DOM事件怎么在虚拟DOM里面定义和使用?
-
给标签添加属性:
onXxx={this.eventHandler}
在组件中添加事件处理方法
eventHandler = (event) => {
//处理过程
}
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
//例如,传统的 HTML:
//在 React 中略微不同:
2.4. 组件的组合使用
问题:组件内部能不能使用组件,形成新的组件?组件嵌套
2.5. 组件收集表单数据
问题:表单数据怎么收集?
2.6. 组件的生命周期
问题:既然都组件化了,每个组件是不是有生命周期,这样我们就可以控制组件的初始化和更新?
组件从被创建到被销毁的过程称为组件的生命周期。React为组件在不同的生命周期阶段提供不同的生命周期方法,让开发者可以在组件的生命周期过程中更好地控制组件的行为。通常,组件的生命周期可以被分为三个阶段:挂载阶段、更新阶段、卸载阶段。
2.6.1. 挂载阶段
这个阶段组件被创建,执行初始化,并被挂载到DOM中,完成组件的第一次渲染。依次调用的生命周期方法有:
constructor
这是ES 6 class的构造方法,组件被创建时,会首先调用组件的构造方法。这个构造方法接收一个props参数,props是从父组件中传入的属性对象,如果父组件中没有传入属性而组件自身定义了默认属性,那么这个props指向的就是组件的默认属性。你必须在这个方法中首先调用super(props)才能保证props被传入组件中。constructor通常用于初始化组件的state以及绑定事件处理方法等工作。
componentWillMount
这个方法在组件被挂载到DOM前调用,且只会被调用一次。这个方法在实际项目中很少会用到,因为可以在该方法中执行的工作都可以提前到constructor中。在这个方法中调用this.setState不会引起组件的重新渲染。
render
这是定义组件时唯一必要的方法(组件的其他生命周期方法都可以省略)。在这个方法中,根据组件的props和state返回一个React元素,用于描述组件的UI,通常React元素使用JSX语法定义。需要注意的是,render并不负责组件的实际渲染工作,它只是返回一个UI的描述,真正的渲染出页面DOM的工作由React自身负责。render是一个纯函数,在这个方法中不能执行任何有副作用的操作,所以不能在render中调用this.setState,这会改变组件的状态。
componentDidMount
在组件被挂载到DOM后调用,且只会被调用一次。这时候已经可以获取到DOM结构,因此依赖DOM节点的操作可以放到这个方法中。这个方法通常还会用于向服务器端请求数据。在这个方法中调用this.setState会引起组件的重新渲染。
2.6.2. 更新阶段
组件被挂载到DOM后,组件的props或state可以引起组件更新。props引起的组件更新,本质上是由渲染该组件的父组件引起的,也就是当父组件的render方法被调用时,组件会发生更新过程,这个时候,组件props的值可能发生改变,也可能没有改变,因为父组件可以使用相同的对象或值为组件的props赋值。但是,无论props是否改变,父组件render方法每一次调用,都会导致组件更新。State引起的组件更新,是通过调用this.setState修改组件state来触发的。组件更新阶段,依次调用的生命周期方法有:
componentWillReceiveProps
这个方法只在props引起的组件更新过程中,才会被调用。State引起的组件更新并不会触发该方法的执行。方法的参数nextProps是父组件传递给当前组件的新的props。但如上文所述,父组件render方法的调用
并不能保证传递给子组件的props发生变化,也就是说nextProps的值可能和子组件当前props的值相等,因此往往需要比较nextProps和this.props来决定是否执行props发生变化后的逻辑,比如根据新的props调用this.setState触发组件的重新渲染。
shouldComponentUpdate
这个方法决定组件是否继续执行更新过程。当方法返回true时(true也是这个方法的默认返回值),组件会继续更新过程;当方法返回false时,组件的更新过程停止,后续的componentWillUpdate、render、componentDidUpdate也不会再被调用。一般通过比较nextProps、nextState和组件当前的props、state决定这个方法的返回结果。这个方法可以用来减少组件不必要的渲染,从而优化组件的性能。
componentWillUpdate
这个方法在组件render调用前执行,可以作为组件更新发生前执行某些工作的地方,一般也很少用到。
render
更新Render
componentDidUpdate
组件更新后被调用,可以作为操作更新后的DOM的地方。这个方法的两个参数prevProps、prevState代表组件更新前的props和state。
2.6.3. 卸载阶段
组件从DOM中被卸载的过程,这个过程中只有一个生命周期方法:componentWillUnmount
这个方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清除组件中使用的定时器,清除componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏。
只有类组件才具有生命周期方法,函数组件是没有生命周期方法的,因此永远不要在函数组件中使用生命周期方法。
Gitee源码:https://gitee.com/BeautifulHao/react-simple
3. Create React App 创建React 前端工程
问题:React组件化了,一个业务页面N多个组件组成,组件怎么复用,组件怎么发布等等都是问题
3.1 工程构建
提前node 和 npm 安装,安装脚手架工具:
npm install -g create-react-app
创建项目:
create-react-app react-demo
3.2 调试运行
yarn start
3.2 打包编译
yarn build