react原理


1)用于构建用户界面的 JavaScript 库(只关注于 View)
2) 由 Facebook 开源

library:库,小而巧,可以很方便地从一个库切换到另外的库
Framework:框架,大而全,提供一整套的结局方案

React 的特点

  1. Declarative(声明式编码):
    React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件
  2. Component-Based(组件化编码)
    创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI。
    组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离
  3. Learn Once, Write Anywhere(支持客户端与服务器渲染)
    无论你现在正在使用什么技术栈,你都可以随时引入 React 来开发新特性,而不需要重写现有代码。
    React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用
  4. 高效
  5. 单向数据流

React 高效的原因

  1. 虚拟(virtual)DOM, 不总是直接操作 DOM
  2. DOM Diff 算法, 最小化页面重绘

虚拟DOM,即用JS对象来描述DOM树结构,
Diff算法则是找旧VDOM与新的VDOM的最小差异,然后再把差异渲染出来

相关 js 库

  1. react.js: React 的核心库
  2. react-dom.js: 提供操作 DOM 的 react 扩展库
  3. 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中的使用

  1. React 提供了一些 API 来创建一种 特别 的一般 js 对象
a. var element = React.createElement('h1', {id:'myTitle'},'hello')
b. 上面创建的就是一个简单的虚拟 DOM 对象
  1. 虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
  2. 我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM 变化 而更新界面

渲染虚拟 DOM( 元素)

  1. 语法:
ReactDOM.render(virtualDOM, containerDOM)
  1. 作用: 将虚拟 DOM 元素渲染到页面中的真实容器 DOM 中显示
  2. 参数说明
    a. 参数一: 纯 js 或 jsx 创建的虚拟 dom 对象
    b. 参数二: 用来包含虚拟 DOM 元素的真实 dom 元素对象(一般是一个 div)

建虚拟 DOM 的 2 种方式

  1. 纯 JS(一般不用)
React.createElement('h1', {id:'myTitle'}, title)
  1. JSX:
<h1 id='myTitle'>{title}</h1>

JSX

  1. 全称: JavaScript XML
  2. react 定义的一种类似于 XML 的 JS 扩展语法: XML+JS
  3. 作用: 用来创建 react 虚拟 DOM(元素)对象
a. var ele = <h1>Hello JSX!</h1>
b. 注意 1: 它不是字符串, 也不是 HTML/XML 标签
c. 注意 2: 它最终产生的就是一个 JS 对象
  1. 标签名任意: HTML 标签或其它标签
  2. 标签属性任意: HTML 标签属性或其它
  3. 基本语法规则
    a. 遇到< 开头的代码, 以HTML标签的语法解析: html 同名标签转换为 html 同名元素, 其它标签需要特别解析
    b. 遇到以 { 开头的代码,以 JS 语法解析: 标签中的 js 代码必须用{ }包含
    7) babel.js 的作用
    a. 浏览器不能直接解析 JSX 代码, 需要 babel 转译为纯 JS 的代码才能运行
    b. 只要用了 JSX,都要加上 type=“text/babel”, 声明需要 babel 来处理

模块与组件

模块

  1. 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件
  2. 为什么: js 代码更多更复杂
  3. 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率

组件

  1. 理解: 用来实现特定(局部)功能效果的代码集合(html/css/js)
  2. 为什么: 一个界面的功能更复杂
  3. 作用: 复用编码, 简化项目编码, 提高运行效率

将标记与逻辑共同存放在称之为“组件”的松散耦合单元之中
每个组件都是封装好的,并且可以单独运行,这样就可以通过组合简单的组件来构建复杂的 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>

注意

  1. 组件名必须首字母大写
  2. 虚拟 DOM 元素只能有一个根元素
  3. 虚拟 DOM 元素必须有结束标签

render() 方法,接收输入的数据并返回需要展示的内容

render() 渲染组件标签的基本流程

  1. React 内部会创建组件实例对象
  2. 得到包含的虚拟 DOM 并解析为真实 DOM
  3. 插入到指定的页面元素内部

组件三大属性

1: state

  1. state 是组件对象最重要的属性, 值是对象(可以包含多个数据)
  2. 组件被称为"状态机", 通过更新组件的 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 的传递,从父组件流向子组件。(外部数据)

  1. 每个组件对象都会有 props(properties 的简写)属性
  2. 组件标签的所有属性都保存在 props 中

作用

  1. 通过标签属性从组件外向组件内传递变化的数据
  2. 注意: 组件内部不要修改 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

  1. 组件内的标签都可以定义 ref 属性来·标识·自己
    a. <input type=“text” ref={input => this.msgInput = input}/>
    b. 是一个回调函数,在组件初始化渲染完或卸载时自动调用:把input赋值给this.input【箭头函数】
  2. 在组件中可以通过 this.msgInput 来得到对应的真实 DOM 元素
  3. 作用: 通过 ref 获取组件内容特定标签对象, 进行读取其相关数据

事件处理

  1. 通过 onXxx 属性指定组件的事件处理函数(注意大小写)
    a. React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件
    b. React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  2. 通过event.target得到发生事件的 DOM 元素对象

在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为 on[Event],将处理事件的监听方法命名为 handle[Event] 这样的格式。

<input onFocus={this.handleClick}/>
handleFocus(event) {
	event.target // 返回 input 对象
}

强烈注意

  1. 组件内置的方法中的 this 为组件对象
  2. 在组件类中自定义的方法中 this 为 null
    a. 强制绑定 this: 通过函数对象的 bind()
    b. 箭头函数(ES6 模块化编码时才能使用)

组件的组合

功能界面的组件化编码流程( 无比重要)

  1. 拆分组件: 拆分界面,抽取组件 【App根组件- add子组件,list子组件】是位置关系
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件
    a. 动态显示初始化数据
    b. 交互功能(从绑定事件监听开始)
    注意
  • 数据保存在哪个组件里?
    看数据是某个组件需要(就给它),还是某些组件需要(给共同的父组件:App)
  • 需要在子组件中改变父组件的状态,怎么做?
    状态在哪个组件,更新状态的行为就应该定义在哪个组件:父组件定义函数,传递给子组件,子组件调用
  • 更新状态只能用setState()
  • 只要是你定义的,就bind(this)
  • 流程感,要有流程

收集表单数据

  1. 问题: 在 react 应用中, 如何收集表单输入数据
  2. 包含表单的组件分类(看官网文档)
    a. 受控组件 - 密码: 表单项输入数据能自动收集成状态
    b. 非受控组件 - 用户名: 需要时才手动读取表单输入框中的数据

组件生命周期

  1. 组件对象从创建到死亡它会经历特定的生命周期阶段
  2. React 组件对象包含一系列的勾子函数(生命周期回调函数), 在生命周期特定时刻回调
  3. 我们在定义组件时, 可以重写特定的生命周期回调函数, 做特定的工作

生命周期流程图

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()
发布了149 篇原创文章 · 获赞 5 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_26327971/article/details/104923773