react fiber
为了使 react 渲染的过程中可以被中断,可以将控制权交给浏览器。可以让位给高优先级的任务,浏览器空闲后再恢复渲染。
对于计算量比较大的 js 计算或者 vdom 的计算,就不会显得特别卡顿,而是一帧一帧的有规律的执行任务。
const tasks = []
function run() {
let task
while (task = tasks.shift()) {
execute(task) // 10s
}
}
generator.
const tasks = [] // 10 个 task
function * run() {
let task
while(task = tasks.shift()) {
if (hasHighPriorityTask()) { // 有高优任务
yield // 中断执行
}
execute(task) // 10s
}
}
const iterator = run()
iterator.next() // 中断恢复
- generator 有类似功能,为什么不直接使用
- 要使用 generator,需要将涉及到的所有代码都包装成generator * 的形式,非常麻烦,工作量很大(在原有的基础上迭代)
- generator 内部是有状态的
function * doWork(a, b, c) {
const x = doExpendsiveWorkA(a)
yield
const y = doExpendsiveWorkB(b)
yield
const z = doExpendsiveWorkC(c)
return z
}
我们已经执行完了 doExpendsiveWorkA 和 doExpendsiveWorkB,还未执行 doExpendsiveWorkC
如果此时 b 被更新了,那么在新的时间分片里,我们只能沿用之前获取到的 x,y 结果。
- 如何判断当前是否有高优的任务呢?
当前的 js 环境其实并没有办法去判断是否有高优先级的任务。
只能约定一个合理的执行时间,当超过了这个执行时间,如果任务仍然没有执行完成,中断当前任务,将控制权交还给浏览器。
每秒60帧,1000ms/60f=16ms/f
requestIdleCallback.
使浏览器在**有空的时候**去执行我们的回调。这个回调会穿入一个参数,表示浏览器有多少时间供我们执行任务。
-
浏览器在一帧内要做什么事情
处理用户输入事件 JS 的执行 requestAnimation 调用 布局 layout 绘制 paint
16ms - 10ms 6ms -> requestIdleCallback
-
浏览器很忙怎么办
requestIdleCallback timeout 参数,如果超过这个timeout后,回调还没有被执行,那么会在下一帧强制执行回调。 如 100ms 16ms 16ms 16ms ... 16ms -> 100ms强制执行回调。
-
兼容性?
requestIdleCallback 兼容性很差,通过 messageChannel 模拟实现了 requestIdleCallback 的功能。
-
timeout 超过后就一定要被执行吗?
task console.log(); requestIdleCallback timeout 100ms
不是的,react 里预订了5个优先级的等级
- Immediate 最高优先级,这个优先级的任务应该被马上执行不能中断
- UserBlocking 这些任务一般是用户交互的结果,需要即时得到反馈
- Normal 不需要用户立刻感受到的变化,比如网络请求
- Low 这些任务可以延后,但是最终也需要执行
- Idle 可以被无限期延后
平时用过高阶组件吗?什么是高阶组件?高阶组件能用来做什么?
简称 HOC, High Order Component
- 是一个函数
- 入参:原来的 react 组件
- 出参:新的 react 组件
- 是一个纯函数,不应该有任何的副作用.
function helloWorld() {
const myName = sessionStorage.getItem('name')
console.log(`Hello world, my name is ${myName}`)
}
function byeWorld() {
const myName = sessionStorage.getItem('name')
console.log(`Bye world, my name is ${myName}`)
}
HOC
function helloWorld(myName) {
console.log(`Hello world, my name is ${myName}`)
}
function byeWorld(myName) {
console.log(`Bye world, my name is ${myName}`)
}
function wrapWithUserName(wrappedFunc) {
const tempFunction = () => {
const myName = sessionStorage.getItem('name')
wrappedFunc(myName)
}
return tempFunction
}
const wrappedHello = wrapWithUserName(helloWorld)
const wrappedBye = wrapWithUserName(byeWorld)
wrappedHello()
wrappedBye()
怎么写一个高阶组件?
-
普通方式(同上)
-
装饰器
不太受部分人推崇,因为推崇尽量减少 class 语法
接收一个原有的函数,在原有的函数做一个类似于劫持的东西去做自己的各种事情,返回一个新的函数
- 多个高阶组件的组合
// hoc
import React, { Component} from 'react'
interface State {
name: string
}
export const decoratorWithNameHeight = (height?: number) => {
return (WrappedComponent: any) => {
return class extends Component<any, State> {
public state: State = {
name: ''
}
componentWillMount () {
let userName = localStorage.getItem('myName')
this.setState({
name: userName || ''
})
}
render () {
return (
<div>
<WrappedComponent name={this.state.name} {...this.props} />
<p>身高为{height || 0}</p>
</div>
)
}
}
}
}
export const decoratorWithNameWidth = (width?: number) => {
return (WrappedComponent: any) => {
return class extends Component<any, any> {
render () {
return (
<div>
<WrappedComponent {...this.props} />
<p>宽度为{width || 0}</p>
</div>
)
}
}
}
}
// components
import React, { Component } from 'react'
import { decoratorWithNameHeight, decoratorWithNameWidth } from '../../hoc/index'
interface Props {
name: string
}
@decoratorWithNameWidth(100)
@decoratorWithNameHeight(180)
class UglifyWorld extends Component<Props, any>{
render() {
return (
<div>Bye uglify world! My name is {this.props.name}</div>
)
}
}
export default UglifyWorld
// 页面输出
// Bye uglify world! My name is xxx
// 宽度为 xxx
// 身高为 xxx
高阶组件能用来做什么?
-
属性代理 1.1 操作 props 1.2 操作组件实例
// refHoc.tsx import React, { Component} from 'react' export const refHoc = () => { return (WrappedComponent: any) => { return class extends Component<any, any> { ref:any = null componentDidMount() { console.log(this.ref.state) } render () { return ( <WrappedComponent {...this.props} ref = {(instance: any) => { this.ref = instance }} /> ) } } } } // demo import React, { Component} from 'react' import { refHoc } from '../hoc/refHoc' interface Props { name?: string } interface State { width?: number height?: number } @refHoc() class RefDemoComponent extends Component<any, any> { state: State = { width: 100, height: 100 } render () { return ( <div>Bye uglify world! My name is {this.props.name}</div> ) } } export default RefDemoComponent // 输出:Bye uglify world! My name is xxx // 组件实例,输出 Object {width:100, height:100} -
继承/劫持
// hijack.tsx
import React, { Component} from 'react'
export function HiJackHoc<T extends {new(...args:any[])}>(
component: T
) {
return class extends Component {
handleClick() {
console.log(this.handleClick)
super.handleClick()
alert('handleClick被我劫持了')
}
render() {
const parent = super.render()
return React.cloneElement(parent, {
onClick: () => this.handleClick()
})
}
}
}
// test
import React, { Component} from 'react'
import { HiJackHoc } from '../../hoc/hijack'
interface Props {
name?: string
}
interface State {
width?: number
height?: number
}
@HiJackHoc
class RefDemoComponent extends Component<any, any> {
state: State = {
width: 100,
height: 100
}
handleClick() {
this.setState({
width: this.state.width + 1
})
}
render () {
return (
<div onClick = {() => this.handleClick()}>点我呀 {this.state.width}</div>
)
}
}
export default RefDemoComponent
什么是 React Hooks? React Hooks 有什么优势?
可以不写 class 的情况下,使用 state 和 其他 react 特性。
useState useEffect useMemo
为什么不写 class 而转向了 hooks 写法?
React Hooks 有什么优势?
class 的缺点
-
组件间的状态逻辑很难复用
组件间如果有 state 的逻辑是相似的,class 模式下基本上是用高阶组件来解决的。
虽然能够解决问题,但是我们需要在组件外部再包一层,会导致层级非常冗余。
-
复杂业务的有状态组件会越来越复杂
-
监听和定时器的操作,被分散在多个区域
didMount document.addEventListener('xxx')
destroyed document.removeEventListener('xxx')
-
this 指向问题
class App extends React.Component<any, any> { constructor(props) { super(props) this.state = { num: 1, title: '123' } this.handleClick2 = this.handleClick1.bind(this) } handleClick1() { this.setState({ num: this.state.num + 1 }) } handleClick3 = () => { this.setState({ num: this.state.num + 1 }) } render() { return ( {/* render 里放的每次都会返回一个新函数,造成 ChildComponent 每次都会重新渲染 */} <ChildComponent onClick = {this.handleClick1.bind(this)}></ChildComponent> <ChildComponent onClick = {() => this.handleClick1() }></ChildComponent> {/* 解决上述重新渲染的问题 */} <ChildComponent onClick = {this.handleClick2}></ChildComponent> <ChildComponent onClick = {this.handleClick3}></ChildComponent> ) } }
Hooks 的优点
解决 class 的缺点
-
利于业务逻辑的封装和拆分,可以非常自由的组合各种自定义 hooks。(自定义 hooks:自己封装的用到了react hooks的公共逻辑)
-
可以在无需修改组件结构的情况下,复用状态逻辑
-
定时器、监听等等都被聚合到同一块代码下
useEffect(() => {
const timer = setInterval(() => {
// code
}, 1000)
return () => clearInterval(timer)
}, [])
Hooks 使用注意事项
-
只能在函数内部的最外层调用 hook,不要在循环、条件判断或者子函数中调用。
-
只能在 React 的函数组件中调用 hook,不要在其他的 js 函数中调用。
-
为什么 hooks 不能在循环、条件判断中调用?
-
为什么 useEffect 的第二个参数是空数组,就相当于 componentDidMount 只执行一次?
-
自定义的 hook 怎样操作组件的?
手写代码实现 useState
const [count, setCount] = useState(0)
setCount(1)
import React, { Component} from 'react'
import ReactDOM from 'react-dom'
function Counter() {
const [count, setCount] = useState(0)
const [name, setName] = useState('')
const onClick = () => {
setCount(count + 1)
}
const onClickName = () => {
setName(`${name} ${Math.random()}`)
}
return (
<div>
<div>{count}</div>
<button onClick={onClick}>点击修改count</button>
<div>{name}</div>
<button onClick={onClickName}>点击修改name</button>
</div>
)
}
// 实际上是用的是单向链表,不是数组
let stateArray: any[] = []
let cursor = 0
// let state:any
function useState<T>(initialState: T): [T, (newState: T) => void]{
const currentCursor = cursor
stateArray[currentCursor] = stateArray[currentCursor] || initialState
// state = state || initialState
// function setState(newState: T) {
// state = newState
// render()
// }
function setState(newState: T) {
stateArray[currentCursor] = newState
render()
}
++ cursor
// return [state, setState]
return [stateArray[currentCursor], setState]
}
export function render() {
ReactDOM.render(
<React.StrictMode>
<Counter />
</React.StrictMode>,
document.getElementById('root')
)
cursor = 0
}
手写代码实现 useEffect
import React, { Component, useState} from 'react'
import ReactDOM from 'react-dom'
function CounterEffect() {
effectCursor = 0
const [count, setCount] = useState(0)
const [count1, setCount1] = useState(0)
useEffect(() => {
console.log(`count 发生了改变 ~~~ ${count}`)
}, [count])
useEffect(() => {
console.log(`count1 发生了改变 ~~~ ${count1}`)
}, [count1])
const onClick = () => {
setCount(count + 1)
}
const onClick1 = () => {
setCount1(count1 + 1)
}
return (
<div>
<div>{count}</div>
<button onClick={onClick}>点击修改count</button>
<div>{name}</div>
<button onClick={onClick1}>点击修改count1</button>
</div>
)
}
const allDeps: Array<any[] | undefined> = [] // 二维数组
let effectCursor: number = 0
function useEffect(callback: () => void, depArray?: []) {
if (!depArray) {
callback()
allDeps[effectCursor] = depArray
effectCursor ++
return
}
const deps = allDeps[effectCursor]
const hasChanged = deps
? depArray.some((el, i) => el !== deps[i])
: true
if (hasChanged) {
callback()
allDeps[effectCursor] = depArray
}
effectCursor ++
}
export function render() {
ReactDOM.render(
<React.StrictMode>
<CounterEffect />
</React.StrictMode>,
document.getElementById('root')
)
}