react fiber?
讲react fiber之前, 咱们先来看 一下普通的函数如何执 行行?
咱们 用while来示例例, 可以看出 一旦开始, 直到task清空, 期间的 行行为咱们完全 无法控制.
const tasks = []
function run() {
let task
while (task = tasks.shift()) {
execute(task)
}
}
而如果我们 用generator来写, 其实就能在函数执 行行时通过yeild中断, 通过next去恢复.
const tasks = []
function * run() {
let task
while (task = tasks.shift()) {
// 判断是否有?优先级事件需要处理, 有的话让出控制权
if (hasHighPriorityEvent()) {
yield
}
// 处理完?优先级事件后,恢复函数调?栈,继续执?…
execute(task)
}
}
而react fiber的核 心 目的就是为了了使React 渲染的过程可以被中断,可以将控制权交回浏览器 ,让位给 高优先级的任务,浏览器 空闲后再恢复渲染。
这样的话 高性能要求的 一些dom计算在设备上就不不会显得很卡顿, 而是会 一帧 一帧的有规律律的执 行行, 看起来就 十分流畅.
那么有 几个问题.
- generator有类似的功能, 为什什么不不直接使 用?
React开发 人员在git issue 里里回答过这个问题. 总结起来主要的就是两点:
要使 用generator的话, 需要将涉及的所有代码都包装成generator * 的形式, 非常麻烦
generator内部是有状态的, 很难在恢复执 行行的时候获取之前的状态.
function* doWork(a, b, c) {
var x = doExpensiveWorkA(a);
yield;
var y = x + doExpensiveWorkB(b);
yield;
var z = y + doExpensiveWorkC(c);
return z;
}
比如这段代码, 如果想在多个时间分片内执行, 而当我们在之前的时间片内已经执行完了doExpensiveWorkA 和 doExpensiveWorkB, 还没执 行行doExpensiveWorkC, 但是此时b被更新了. 那么在新的时间分里, 我们只能沿用之前获取到的x和y的结果, 来执行doExpensiveWorkC. 而我们无法获取到更新后的b的值, 再来继续做doExpensiveWorkC的计算.
- 怎么判定现在有更更 高优先级的任务?
而我们真正的代码中其实 无法真正的判断是否有更高优先级的任务, 只能来约定 一个合理的执行时间, 当过了这个执行行时间后任务仍没有执 行完成的话, 就中断当前任务. 并且将控制权交还给浏览器 .
而正常情况下, 我们 一般是按照每秒60帧, 也就是每帧16ms的刷新是 人眼能感知的最低限度.
然而浏览器 恰好提供了了这样的 方法, requestIdleCallback。
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
requestIdleCallback是让浏览器 在’有空’的时候就执 行我们的回调,这个回调会传入一个参数,表示浏览器有多少时间供我们执行。
那么说了了半天, 都是要在浏览器 有空的时候执 行行, 那浏览器 什什么时候才有空呢?
浏览器 在 一帧内都要做什什么事情
处理用户输入事件
Javascript执 行行
requestAnimation 调 用
布局 Layout
绘制 Paint
而只有在做完这些本职 工作后, 剩下的时间才是有空的时间, 也就是执 行行我们 requestIdleCallback 回调的时间.
浏览器 很忙怎么办?
浏览器可能一直很忙, 忙到几十帧都没空去执 行行requestIdleCallback, 那咱们的回调岂不是永远无法执行了?
放心, 浏览器给我们提供了 一个timeout参数, 当超过这个超时间并且回调还没执行时, 在下 一帧这个回调会被强制执行.
兼容性?
众所周知requestIdleCallback的兼容性是很差的, react通过messageChannel模拟实现了了 requestIdleCallback 的功能.
timeout超时后就一定要被执行吗?
在react 里不是的, react预订了5个优先级, 低优先级的可以慢慢等待, 高优先级的任务应该率先被执行.
Immediate(-1) - 这个优先级的任务会同步执行, 或者说要马上执行且不能中断
UserBlocking(250ms) 这些任务 一般是 用户交互的结果, 需要即时得到反馈
Normal (5s) 应对哪些不需要立即感受到的任务,例如网络请求
Low (10s) 这些任务可以放后,但是最终应该得到执行. 例如分析通知
Idle (没有超时时间) 一些没有必要做的任务 (e.g. 比如隐藏的内容), 可能会被饿死
什么是高阶组件? 高阶组件能用来做什么?
高阶组件简称 HOC, 即为 High Order Components.
A higher-order component is a function that takes a component and returns a new component.
咱们稍微分解 一下官方定义,可以得到以下信息:
1. 高阶组件是 一个函数
2. 入参:原 react 组件
3. 出参:新 react 组件
4. 高阶组件是 一个纯函数, 它不不应该有任何副作 用, 比如修改传 入的 react 组件(当然这个不不是上 面那句句话能看出来的,这 一点称之为约束或者规范更更合理理 一些)
所以, 高阶组件是 一个函数,接收 一个组件,返回 一个组件。
先来写 一个 高阶函数
在写 高阶组件之前, 咱们根据上 面的 4 条信息, 先写 一个简单的 高阶函数的实现试 一下。
比如现在有两个函数,
function helloWorld() {
const myName = sessionStorage.getItem(“lubai”);
console.log(“hello, beautiful world !! my name is “ + myName);
}
function byeWorld() {
const myName = sessionStorage.getItem(“lubai”);
console.log(“bye, ugly world !! my name is “ + myName);
}
helloWorld();
byeWorld();
两个函数一个表达了对世界的渴望与好奇, 是一种新 生;一个表达了对世界的失望与无奈,是一种死去; 文艺的 一匹
但是我们可以发现, myName 的获取逻辑都是 一样的, 而我们重复写了了两遍, 只有 console.log 的逻辑是不不同的。
万 一以后 myName 的获取逻辑变了了怎么办??我们能不不能封装 一下?
所以我们可以写 一个中间函数, 里里 面包含获取 myName 的逻辑。
function helloWorld(myName) {
console.log(“hello, beautiful world !! my name is “ + myName);
}
function byeWorld(myName) {
console.log(“bye, ugly world !! my name is “ + myName);
}
function wrapWithUserName(wrappedFunc) {
const tempFunction = () => {
const myName = sessionStorage.getItem(“qiuku”);
wrappedFunc(myName);
};
return tempFunction;
}
wrapWithUserName(helloWorld)();
wrapWithUserName(byeWorld)();
怎样写 一个 高阶组件
平时看到的 大概是这样的
export const NewComponent = hoc(WrappedComponent);
1. 普通方式
接下来咱们 用普通 方式写 一个类的 高阶组件
2. 装饰器
接下来咱们 用装饰器 方式写 一个类的 高阶组件
3. 多个 高阶组件组合
会发现 用普通 方式书写的话, 逻辑会显得 非常乱, 所以建议使 用装饰器 的写法。
高阶组件能 用来做什什么
属性代理理
1.1 操作 props 其实上 面的这 几个例例 子, 就是在操作 props
1.2 操作组件实例例继承/劫持
什么是 react hooks? React hooks有什么优势?
Hook 即为”钩 子”, 是 react 16.8 的新特性, 你可以在不不写 class 的情况下使 用 state 和其他的 react 特性。
凡是 use 开头的 React API 都是 Hooks.
那么为什什么要不不写 class 呢?hook 相对于 class 又有什什么优势呢?
react hooks 有什什么优势
先来看 一下 class 写组件有什什么不不 足之处吧!
1. 组件间的状态逻辑很难复 用
组件间如果有 state 的逻辑是类似的话, class 模式下基本都是 用 高阶组件来解决的,。
虽然能够解决问题, 但是你会发现, 我们可能需要在组件外部再包 一层元素, 会导致层级 非常冗余
复杂业务的有状态组件会越来越复杂
比如类组件中都是通过更更改 this.state 来达到状态修改的 目的的, 但是组件内部太多对 state 的访问和修改, 很难在后期给拆成更更细粒度的组件, 就会导致组件越来越庞 大。
还有 比如设置监听, 比如添加定时器 , 我们需要在两个 生命周期 里里完成注册和销毁, 很有可能漏漏写导致内存问题
componentDidMount() {
const timer = setInterval(() => {});
this.setState({timer})
}
componentWillUnmount() {
if (this.state.timer) {
clearInterval(this.state.timer);
}
}
3. This 指向问题
React 里绑定事件函数有以下四种方法,如果新玩家刚接触,稍不注意就会写错,导致性能上的大损耗。
class App extends React.Component
handleClick2;
constructor(props) {
super(props);
this.state = {
num: 1,
title: “ react study”,
};
this.handleClick2 = this.handleClick1.bind(this)
}
handleClick1() {
this.setState({
num: this.state.num + 1,
});
}
handleClick3 = () => {
this.setState({
num: this.state.num + 1,
});
};
render() {
return (
Ann, {this.state.num}
{/ 在render函数?绑定this,由于bind会返回?个新函数, 所以每次?组件刷新 都会导致?组件的重新刷新, 就算?组件的其他props没有改变。/}
{/ 构造函数内绑定this, 每次?组件刷新的时候, 如果传递给?组件的其他 props不变, ?组件就不会刷新。/}
{/ 使?箭头函数, 每次都会?成?个新的箭头函数, 每次?组件刷新的时候, 如果 传递给?组件的其他props不变, ?组件就不会刷新/}
{/ 使?类?定义的箭头函数, 和handleClick2原理?样, 但是?第?种更简洁 /}
);
}
}
hooks的优点 ?
hooks 的优点, 一对就较明显了
1. 能优化类组件的三大问题
2. 能在无需修改组件结构的情况下复用状态逻辑( 自定义 Hooks )
3. 能将组件中相互关联的部分拆分成更更 小的函数( 比如设置订阅或请求数据)
4. 副作 用的概念
副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原 生 dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器 、记录 日志等。
以往这些副作 用都是写在类组件生命周期函数中的。
而 useEffect 在全部渲染完毕后才会执 行,useLayoutEffect 会在浏览器 layout 之后,painting之前执行。
而且比如绑定/解绑事件都可以写在 一个副作用函数里了, 不会再散落在各地难以维护。
react hooks 的注意事项
只能在函数内部的最外层调用Hook,不要在循环、条件判断或者子函数中调用
2. 只能在 React 的函数组件中调 用 Hook,不要在其他 JavaScript 函数中调用
react hooks 是怎么实现的
说了了这么多, 优点啊, 注意事项啊, 大家可能 比较懵逼。为什么不不能在循环或者判断条件中使 用??
2. 为什么 useEffect 的第 二个参数是空数组, 就相当于 componentDidMount 只执行 一次??
3. 自定义 hook 怎么操作组件的?
接下来我们来实现 一下简单的 Hooks, 一看就懂了了。
1. useState
先来看 一下 useState 是怎么使 用的
const [count, setCount] = useState(0);
传 入 一个初始值, 返回 一个状态值和 一个设置状态的 方法。
咱们先来实现 一个简易易的 useState。 // 代码 Mock-UseState1-Counter.tsx
可以看到这个 useState 只 支持设置 一次的, 如果是多个 useState 的话就 无法满 足需求了了。多个useState
那么咱们来看看怎么 支持多个 useState!!
前面 useState 的简单实现 里里,初始的状态是保存在 一个全局变量量中的。
以此类推,多个状态,应该是保存在 一个专 门的全局容器 中。这个容器 ,就是 一个朴实 无华的Array 对象。具体过程如下:
第 一次渲染时候,根据 useState 顺序,逐个声明 state 并且将其放 入全局 Array 中。
每次声明 state,都要将 cursor 增加 1。
更更新 state,触发再次渲染的时候,cursor 被重置为 0。
按照 useState 的声明顺序,依次拿出最新的 state 的值,视图更更新。
那么明 白了了原理理之后, 你可能也会想明 白为什什么不不能在循环或者判断条件中使 用了了吧??
因为是按照数组索引 入存储的, 如果在判断中使 用, 某个 state 没有按照对应索引存储,那么后 面的 state 索引也会有问题。
// 代码 Mock-UseState2-Counter.tsxuseEffect
来看 一下最基本的 用法
useEffect(() => {
console.log(count);
}, [count]);
它有如下四个特点
- 有两个参数 callback 和 dependencies 数组
- 如果 dependencies 不存在,那么 callback 每次 render 都会执行
- 如果 dependencies 存在,只有当它发 生了变化, callback才会执行
- 如果 dependencies 为空数组, 则只执行 一次 callback
接下来来实现 一个简易易 useEffect 吧!!
// 代码 Mock-UseEffect1-Counter.tsx
它同样有个问题, 就是只能注册 一个 useEffect,利利 用相同的 cursor+array 的思想,实现多个 useEffect 的注册吧
// 代码 Mock-UseEffect2-Counter.tsx
手写代码
- promise-allsettled.js
- promise-limit.js
