文章目录
1)用于构建用户界面的 JavaScript 库(只关注于 View)
2) 由 Facebook 开源
library:库,小而巧,可以很方便地从一个库切换到另外的库
Framework:框架,大而全,提供一整套的结局方案
React 的特点
- Declarative(声明式编码):
React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件 - Component-Based(组件化编码)
创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI。
组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离 - Learn Once, Write Anywhere(支持客户端与服务器渲染)
无论你现在正在使用什么技术栈,你都可以随时引入 React 来开发新特性,而不需要重写现有代码。
React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用 - 高效
- 单向数据流
React 高效的原因
- 虚拟(virtual)DOM, 不总是直接操作 DOM
- DOM Diff 算法, 最小化页面重绘
虚拟DOM,即用JS对象来描述DOM树结构,
Diff算法则是找旧VDOM与新的VDOM的最小差异,然后再把差异渲染出来
相关 js 库
- react.js: React 的核心库
- react-dom.js: 提供操作 DOM 的 react 扩展库
- babel.min.js: 解析 JSX 语法代码转为纯 JS 语法代码的库
Babel 可以将 ES6 代码转为 ES5 代码,这样我们就能在目前不支持 ES6 浏览器上执行 React 代码。Babel 内嵌了对 JSX 的支持。通过将 Babel 和 babel-sublime 包(package)一同使用可以让源码的语法渲染上升到一个全新的水平。
<script type="text/babel"> //必须声明babel
// 创建虚拟DOM元素
const vDom = <h1>Hello React</h1> // 千万不要加引号
// 渲染虚拟DOM到页面真实DOM容器中
ReactDOM.render(vDom, document.getElementById('test'))
</script>
虚拟 DOM
一个网页呈现的过程:
1.浏览器请求服务器获取页面HTML代码
2.浏览器要先在内存中,解析DOM结构,并在浏览器内存中,渲染出一棵DOM树
3.浏览器把DOM树,呈现到页面上
目标:把性能做到最优:按需渲染页面(只更新渲染更新的数据,所对应的页面元素)
如何实现页面的按需渲染:获取内存中的新旧两棵DOM树,进行对比,得到需要更新的DOM元素
但是,浏览器中并没有直接提供 获取DOM 树的API,程序员手动模拟两棵新旧DOM树,就是React中虚拟DOM的概念。
- DOM:浏览器中的概念,用JS对象来表示页面上的元素,并提供了操作DOM对象的API。
(真实的DOM节点,属性特别多,操作慢) - 虚拟DOM:是框架中的概念,是开发框架的程序员,
手动用JS对象来模拟页面 上的DOM和DOM嵌套
;为了实现页面中,DOM元素的高效更新
(描述一个DOM节点:tag 标签名;attrs DOM属性键值对;childen DOM字节点数组 或 文本内容)
Diff算法 - 提供对比两个树的方案
- tree diff:新旧两棵DOM树,
逐层对比
的过程。当整棵DOM逐层对比完毕,则所有需要被按需更新的元素,必然能够找到; - component diff:在进行Tree Diff的时候,
每一层中,组件级别的对比
。
– 如果对比前后,组件的类型相同,则暂时认为此组件不需要被更新
– 如果对比前后,组件类型不同,则需要移除旧组件,创建新组件,并追加到页面上 - element diff:在进行组件对比的时候,
如果两个组件类型相同,则需要进行 元素级别的对比
在React中的使用
- React 提供了一些 API 来创建一种
特别
的一般 js 对象
a. var element = React.createElement('h1', {id:'myTitle'},'hello')
b. 上面创建的就是一个简单的虚拟 DOM 对象
- 虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
- 我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM 变化 而更新界面
渲染虚拟 DOM( 元素)
- 语法:
ReactDOM.render(virtualDOM, containerDOM)
- 作用: 将虚拟 DOM 元素渲染到页面中的真实容器 DOM 中显示
- 参数说明
a. 参数一: 纯 js 或 jsx 创建的虚拟 dom 对象
b. 参数二: 用来包含虚拟 DOM 元素的真实 dom 元素对象(一般是一个 div)
建虚拟 DOM 的 2 种方式
- 纯 JS(一般不用)
React.createElement('h1', {id:'myTitle'}, title)
- JSX:
<h1 id='myTitle'>{title}</h1>
JSX
- 全称: JavaScript XML
- react 定义的一种类似于 XML 的 JS 扩展语法: XML+JS
- 作用: 用来
创建 react 虚拟 DOM(元素)对象
a. var ele = <h1>Hello JSX!</h1>
b. 注意 1: 它不是字符串, 也不是 HTML/XML 标签
c. 注意 2: 它最终产生的就是一个 JS 对象
- 标签名任意: HTML 标签或其它标签
- 标签属性任意: HTML 标签属性或其它
- 基本语法规则
a. 遇到< 开头的代码, 以HTML标签的语法解析
: html 同名标签转换为 html 同名元素, 其它标签需要特别解析
b. 遇到以 { 开头的代码,以 JS 语法解析
: 标签中的 js 代码必须用{ }包含
7) babel.js 的作用
a. 浏览器不能直接解析 JSX 代码, 需要 babel 转译为纯 JS 的代码才能运行
b. 只要用了 JSX,都要加上 type=“text/babel”, 声明需要 babel 来处理
模块与组件
模块
- 理解: 向外提供特定功能的 js 程序,
一般就是一个 js 文件
- 为什么: js 代码更多更复杂
- 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率
组件
- 理解: 用来
实现特定(局部)功能效果的代码集合
(html/css/js) - 为什么: 一个界面的功能更复杂
- 作用: 复用编码, 简化项目编码, 提高运行效率
将标记与逻辑共同存放在称之为“组件”的松散耦合单元之中
每个组件都是封装好的,并且可以单独运行,这样就可以通过组合简单的组件来构建复杂的 UI 界面
模块化:从代码的角度
:把一些可以复用的代码,抽离为单个模块,便于项目的维护和开发
组件化:从UI界面的角度进行分析
:把一些可以复用的UI元素,抽离为单独的组件
组件化的好处:随着项目规模的增大,手里的组件越来越多,很方便就能把现有的组件,拼接为一个完整的页面
React 面向组件编程
自定义组件(Component) :
<script type="text/babel">
// 1. 定义组件
/*方式1: 工厂函数组件(简单组件)*/
function MyComponent () {
return <h2>工厂函数组件(简单组件)</h2>
}
/*方式2: ES6类组件(复杂组件)*/
class MyComponent2 extends React.Component {
render () {
console.log(this) // MyComponent2的实例对象
return <h2>ES6类组件(复杂组件)</h2>
}
}
// 2. 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example1'))
ReactDOM.render(<MyComponent2 />, document.getElementById('example2'))
</script>
注意
- 组件名必须首字母大写
- 虚拟 DOM 元素只能有一个根元素
- 虚拟 DOM 元素必须有结束标签
render() 方法,接收输入的数据并返回需要展示的内容
render() 渲染组件标签的基本流程
- React 内部会创建组件实例对象
- 得到包含的虚拟 DOM 并解析为真实 DOM
- 插入到指定的页面元素内部
组件三大属性
1: state
- state 是组件对象最重要的属性, 值是对象(可以包含多个数据)
- 组件被称为"状态机",
通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
3)组件可以选择把它的 state 作为 props 向下传递到它的子组件中:
this.state 应该被视为一个组件的 私有属性 ,内部状态
每次在组件中调用 setState 时,React 都会自动更新其子组件, 每次组件更新时 render 方法都会被调用。
1) 初始化state:
constructor(props){
super(props)
this.state = {
stateProp1:value1
}
}
2) 读取某个状态值
this.state.statePropertyName
3) 更新状态 -> 组件界面更新
this.setState({
stateProp1:value1
})
** 2: props**
在 React 应用中,数据通过 props 的传递,从父组件流向子组件。(外部数据)
- 每个组件对象都会有 props(properties 的简写)属性
- 组件标签的所有属性都保存在 props 中
作用
- 通过标签属性
从组件外向组件内传递
变化的数据 - 注意: 组件内部不要修改 props 数据
1) 内部读取某个属性值
this.props.propertyName
2) 对 props 中的属性值进行类型限制和必要性限制
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
3) 扩展属性: 将对象的所有属性通过 props 传递
<Person {...person}/>
4) 默认属性值
Person.defaultProps = {
name: 'Mary'
}
5) 组件类的构造函数
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
3: refs
- 组件内的标签都可以定义 ref 属性来·
标识
·自己
a. <input type=“text”ref={input => this.msgInput = input}
/>
b. 是一个回调函数,在组件初始化渲染完或卸载时自动调用:把input赋值给this.input【箭头函数】 - 在组件中可以通过 this.msgInput 来得到对应的真实 DOM 元素
- 作用: 通过 ref 获取组件内容特定标签对象, 进行读取其相关数据
事件处理
- 通过 onXxx 属性指定组件的事件处理函数(注意大小写)
a. React 使用的是自定义(合成)事件
, 而不是使用的原生 DOM 事件
b. React 中的事件是通过事件委托
方式处理的(委托给组件最外层的元素) - 通过
event.target
得到发生事件的DOM 元素对象
在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为 on[Event],将处理事件的监听方法命名为 handle[Event] 这样的格式。
<input onFocus={this.handleClick}/>
handleFocus(event) {
event.target // 返回 input 对象
}
强烈注意
- 组件内置的方法中的 this 为组件对象
- 在组件类中自定义的方法中 this 为 null
a. 强制绑定 this: 通过函数对象的 bind()
b. 箭头函数(ES6 模块化编码时才能使用)
组件的组合
功能界面的组件化编码流程( 无比重要)
- 拆分组件: 拆分界面,抽取组件 【App根组件- add子组件,list子组件】是位置关系
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
a. 动态显示初始化数据
b. 交互功能(从绑定事件监听开始)
注意
- 数据保存在哪个组件里?
看数据是某个组件需要(就给它),还是某些组件需要(给共同的父组件:App) - 需要在子组件中改变父组件的状态,怎么做?
状态在哪个组件,更新状态的行为就应该定义在哪个组件:父组件定义函数,传递给子组件,子组件调用 - 更新状态只能用setState()
- 只要是你定义的,就bind(this)
- 流程感,要有流程
收集表单数据
- 问题: 在 react 应用中, 如何收集表单输入数据
- 包含表单的组件分类(看官网文档)
a. 受控组件 - 密码: 表单项输入数据能自动收集成状态
b. 非受控组件 - 用户名: 需要时才手动读取表单输入框中的数据
组件生命周期
- 组件对象从创建到死亡它会经历特定的生命周期阶段
- React 组件对象包含一系列的勾子函数(生命周期回调函数), 在生命周期特定时刻回调
- 我们在定义组件时, 可以重写特定的生命周期回调函数, 做特定的工作
生命周期流程图
1) 组件的三个生命周期状态:
- Mount:插入真实 DOM – 挂载
- Update:被重新渲染
- Unmount:被移出真实 DOM
2) React 为每个状态都提供了勾子(hook)函数
- componentWillMount()
- componentDidMount()
- componentWillUpdate()
- componentDidUpdate()
- componentWillUnmount()
3) 生命周期流程:
render(): 初始化渲染或更新渲染调用(必须重写, 返回一个自定义的虚拟DOM)
一、初始化阶段:
- constructor(): 创建对象初始化 state,绑定this(可以箭头函数代替)
- getDefaultProps:获取实例的默认属性
- getInitialState:获取每个实例的初始化状态
componentWillMount
: 组件即将被装载、渲染到页面上(多用于根组件中的应用程序配置)- render:组件在这里生成虚拟的DOM节点
componentDidMount
:组件真正在被装载之后(在第一次渲染之后执行,只执行一次, 已经在dom树中, 适合启动/设置一些监听)
(在这可以完成所有没有 DOM 就不能做的所有配置,并开始获取所有你需要的数据。设置事件监听, 发送 ajax 请求)
二、每次更新 state: this.setSate():
- componentWillReceiveProps:在初始化render的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染
- shouldComponentUpdate : 组件接受到新属性或者新状态的时候,确定是否更新组件。默认情况下,它返回true。如果确定在 state 或 props 更新后组件不需要在重新渲染,则可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了(这是一个提高性能的地方)
- componentWillUpdate:在shouldComponentUpdate返回 true 确定要更新组件之前件之前执行,组件即将更新不能修改属性和状态
- render:组件重新描绘
- componentDidUpdate:组件已经更新(它主要用于更新DOM以响应props或state更改)
三、移除组件,销毁阶段:
- componentWillUnmount 做一些收尾工作, 如: 清理定时器
组件即将销毁,在这你可以取消网络请求,或者移除所有与组件相关的事件监听器
虚拟 DOM 与 与 DOM Diff
1). 虚拟DOM是什么?
一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
如果只是更新虚拟DOM, 页面是不会重绘的
2). Virtual DOM 算法的基本步骤
用JS对象树表示DOM树的结构;然后用这个树构建一个真正的DOM树插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把差异应用到真实DOM树上,视图就更新了
3). 进一步理解
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:
既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。
CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)
基本原理图
命令式编程与声明式编程
- 声明式编程
只关注做什么
, 而不关注怎么做(流程), 类似于填空题 - 命令式编程
要关注做什么和怎么做(流程), 类似于问答题
var arr = [1, 3, 5, 7]
// 需求: 得到一个新的数组, 数组中每个元素都比arr中对应的元素大10: [11, 13, 15, 17]
// 命令式编程
var arr2 = []
for(var i =0;i<arr.length;i++) {
arr2.push(arr[i]+10)
}
console.log(arr2)
// 声明式编程
var arr3 = arr.map(function(item){
return item +10
})
// 声明式编程是建立命令式编程的基础上
// 数组中常见声明式方法
map() / forEach() / find() / findIndex()