应用是个状态机,状态驱动视图
v = f(d)v是视图
f是组件
d是数据/状态
把函数式思想引入前端,通过PureComponent组合来实现UI
最大好处是让UI可预测,对同样的f
输入同样的d
一定能得到同样的v
可以把各个f
单独拎出来测试,组合起来肯定没有问题,从理论上确定了组件质量是可靠的,组合出来的整个应用的UI也是可靠的
A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES
针对构建UI提供一种组件化的方案
For many applications, using React will lead to a fast user interface without doing much work to specifically optimize for performance.
寻找成本与收益的平衡点,不刻意去做性能优化,还能写出来性能不错(非最优)的应用
实际上,React所作的性能优化主要体现在:
但无论怎样,性能肯定不及年迈的(经验丰富的)FEer手写的原生DOM操作版
在DOM树之上加一层额外的抽象
组件化方式:提供组件class模版、生命周期hook、数据流转方式、局部状态托管
运行时:用虚拟DOM树管理组件,建立并维护到真实DOM树的映射关系
JSX -> React Element -> 虚拟DOM节点 ..> 真实DOM节点
描述对象
createElement
createElement
得到React Element描述对象虚拟DOM树的节点集合是真实DOM树节点集合的超集,多出来的部分是自定义组件(Wrapper)
结构上,内部树布局是森林,维护在instancesByReactRootID
:
由props
和state
把组件组织起来,组件间数据流向类似于瀑布
数据流向总是从祖先到子孙(从根到叶子),不会逆流
props
:管道state
:水源单项数据流是由状态丢弃机制决定的,具体表现为:
单向数据流是对渲染视图过程而言的,子孙的state
如何改变都不会影响祖先,除非通知祖先更新其state
state
是最小可变状态集,特点:
state
或者props
计算出来props
是不可变的,仅用来填充视图模版:
props React Element描述对象
-----> 组件 ---------------------> 视图
首次渲染时收集data-view
的映射关系,后续确认数据变化后,更新数据对应的视图
实现方式 | 依赖收集 | 监听变化 | 案例 |
---|---|---|---|
getter & setter | getter | setter监听变化 | Vue |
提供数据模型 | 解析模版 | 所有数据操作都走框架API,通知变化 | Ember |
脏检查 | 解析模版 | 在合适的时机,取最新的值和上次的比较,检查变化 | Angular |
虚拟DOM diff | 几乎不收集 | setState通知变化 | React |
从依赖收集的粒度来看:
getter
动态收集依赖粒度最细,最精确state
变化时,重新计算对应子树的内部状态,对比找出变化(diff),然后在合适的时机应用这些变化(patch)
细粒度的依赖收集是精确DOM更新的基础(哪些数据影响哪个元素的哪个属性),无需做额外的猜测和判断,框架如果明确知道影响的视图元素/属性是哪些的话,就可以直接做最细粒度的DOM操作
React不收集依赖,只有2个已知条件:
state
属于哪个组件state
变化只会影响对应子树子树范围对于最终视图更新需要的DOM操作而言太大了,需要细化(diff)
树的diff是个相对复杂(NP)的问题,先考虑一个简单场景:
A A'
/ \ ? / | \
B C -> G B C
/ \ | | |
D E F D E
diff(treeA, treeA')
结果应该是:
1.insert G before B
2.move E to F
3.remove F
如果要计算机来做的话,增
和删
好找,移
的判定就比较复杂了,首先要把树的相似程度量化(比如加权编辑距离),并确定相似度为多少时,移
比删+增
划算(操作步骤更少)
对虚拟DOM子树做diff就面临这样的问题,考虑DOM操作场景的特点:
假设:
移
的判断就没难度了)key
,作为diff
依据,假定同key
表示同元素(降低比较成本)这样tree diff问题就被简化成了list diff(字符串编辑问题):
本质是一个很弱的字符串编辑算法,所以,即便不考虑diff开销,单从最终的实际DOM操作来看,性能也不是最优的(相比手动操作DOM)
另外,保险起见,React还提供了shouldComponentUpdate
钩子,允许人工干预diff
过程,避免误判
通过提升状态来共享,能减少孤立状态,减少bug面,但毕竟比较麻烦。组件间远距离通信问题没有好的解决方案
另一个问题是在复杂应用中,状态变化(setState
)散落在各个组件中,逻辑过于分散,存在维护上的问题
为了解决状态管理的问题,提出了Flux模式,目标是让数据可预测
(state, action) => state
产生action 传递action update state
view交互 -----------> dispatcher -----------> stores --------------> views
特点是store比较重,负责根据action更新内部state及把state变化同步到view
container其实就是controller-view:
action 与Flux一样,就是事件,带有type和data(payload)
同样手动dispatch action
---
store 与Flux功能一样,但全局只有1个,实现上是一颗不可变的状态树
分发action,注册listener。每个action经过层层reducer得到新state
---
reducer 与arr.reduce(callback, [initialValue])作用类似
reducer相当于callback,输入当前state和action,输出新state call new state
action --> store ------> reducers -----------> view
用一棵不可变状态树维护整个应用的状态,无法直接改变,发生变化时,通过action和reducer创建新的对象
reducer的概念相当于node中间件,或者gulp插件,每个reducer负责状态树的一小部分,把一系列reducer串联起来(把上一个reducer的输出作为当前reducer的输入),得到最终输出state
能去掉dispatcher是因为纯函数reducer可以随便组合,不需要额外管理顺序
Redux与React没有任何关系,Redux作为状态管理层可以配合任何UI方案使用,例如backbone、angular、React等等
react-redux用来处理new state -> view
的部分,也就是说,新state有了,怎样同步视图?
container是一种特殊的组件,不含视图逻辑,与store关系紧密。从逻辑功能上看就是通过store.subscribe()
读取状态树的一部分,作为props
传递给下方的普通组件(view)
一个看起来很神奇的API,主要做3件事:
目的:避免手动逐层传递store
实现:在顶层通过context
注入store
,让下方所有组件共享store