React的设计理念
单向数据流
数据从上往下传递,由父组件传递到子组件,子组件可以监听但不能随意修改接收到的值。
🌰: 数据流传递的过程
function Child(props) {
// props 表示从父组件接收的参数集合
const {name} = props
return <p>{name}</p>
}
function Father() {
return <div>
<Child name='test' />
</div>
}
🌰: 子组件修改父组件的值
function Child(props) {
// props 表示从父组件接收的参数集合
const {name,onChangeName} = props
return <>
<p>{name}</p>
<button onClick={() => onChangeName('child')}>更改</button>
</>
}
function Father() {
const [myname,setMyname] = useState('father')
return <div>
<Child name={myname} onChangeName={() => setMyname(newName)}/>
</div>
}
单向数据流的优点
- 状态改变可记录,可追踪, 源头易追溯
- 状态有唯一的入口, 可维护性强
- 修改状态时会重走一遍修改的流程, 状态可预测
单向数据流的缺点
- 页面渲染完成后,有新数据不能自动更新,需要手动整合新数据和模板重新渲染
- 代码量上升,数据流转过程变长,代码重复性变大
- 由于对应用状态独立管理(单一的全局 store),在处理局部状态较多的场景时(如用户输入交互较多的应用),会比较繁琐。
虚拟Dom(Virtual dom)
什么是虚拟Dom❓
其实就是js的object类型通过 JS 的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM节点,以减少真实dom操作,提高渲染性能。
异步可中断
为什么需要异步可中断❓
因为JS是单线程的,在js执行的过程中 UI渲染是阻塞的,对于一些比较耗时的js,执行时会造成页面卡顿的现象,所以需要对一些比较耗时的js能够进行分段执行,这里就需要可中断系统来让出执行权,当其他任务执行结束之后再继续执行剩余的部分。
react15为什么慢❓
React15之前的协调过程是同步的,也叫stack reconciler,又因为js的执行是单线程的,这就导致了在更新比较耗时的任务时,不能及时响应一些高优先级的任务,比如用户的输入,所以页面就会卡顿,这就是cpu的限制。
如何实现❓
在刚才的解决方案中提到了任务分割,和异步执行,并且能让出执行权,由此可以带出react中的三个概念
- Fiber: react15的更新是同步的,因为它不能将任务分割,所以需要一套数据结构让它既能对应真实的dom又能作为分隔的单元,这就是Fiber(链表结构 ),Fiber 的主要特性是支持增量渲染:能够将渲染工作分割为小块,并且将它们分散到多个帧中。。
// 模拟实现
let firstFiber // 根节点
let nextFiber = firstFiber //下一个需要遍历的节点
let shouldYield = false // 是否暂停当前任务
// 根节点->第一个子节点->子节点的兄弟节点
function performUnitOfWork(nextFiber){
//...
return nextFiber.next
}
// 遍历 fiber tree
function workLoop(deadline){
while(nextFiber && !shouldYield){
nextFiber = performUnitOfWork(nextFiber)
shouldYield = deadline.timeReaming < 1 // 没有剩余时间时
}
requestIdleCallback(workLoop)
}
// 浏览器中的api,会在浏览器空闲时刻调用传入函数
requestIdleCallback(workLoop)
基于传统的stack reconciler 调用栈
有种深一脚 浅一脚的感觉
基于fiber结构的 调用栈
就很流畅
-
Scheduler:有了Fiber,我们就需要用浏览器的时间片异步执行这些Fiber的工作单元,我们知道浏览器有一个api叫做requestIdleCallback,它可以在浏览器空闲的时候执行一些任务,我们用这个api执行react的更新,让高优先级的任务优先响应不就可以了吗,但事实是requestIdleCallback存在着浏览器的兼容性和触发不稳定的问题,所以我们需要用js实现一套时间片运行的机制,在react中这部分叫做scheduler。
-
Lane:管理任务的优先级,有了异步调度,还需要细粒度的管理各个任务的优先级,让高优先级的任务优先执行,各个Fiber工作单元还能比较优先级,相同优先级的任务可以一起更新
代数效应(副作用分离)
什么是代数效应❓
代数效应指的是在函数式编程中能够将副作用从函数调用中分离
假设我们有一个函数
// 从服务器获取价格
async function getTotalPrice() {
const p1 = await getPrice(1)
const p2 = await getPrice(2)
return p1 + p2
}
// 处理价格
async function handelPrice() {
const totalPrice = await getTotalPrice()
return ....
}
因为 getTotalPrice 是一个异步函数通过 async声明,那么在handelPrice 就要为其添加async 才能通过 await 调用 async的结果,这就是async的传染性
而代数效应的存在就是为了通过一种方式实现类似的 避免 async 传染的现象
代数效应在react中的应用
对于类似 useState, useEffect 等hooks,我们不需要过多关注 hooks 内部的状态管理,react会自动维护, 我们只需要写自己的任务即可