Fiber、高阶组件、hooks

86 阅读6分钟

fiber

为了使react渲染的过程可以中断。可以将控制权交给浏览器,可以让位给高优先级的任务,浏览器空闲后再恢复渲染

对于计算量比较大的js计算或者dom计算,就不会显得特别卡顿,而是一帧一帧有规律的执行任务

generator(es6中可以灵活用来中断和继续的方法)

const tasks = []
function * run(){
    let task;
    while(task = task.shift()){
        // 有高优先级任务时中断
        ifhasHeighPriorityTask()){
            yeild
        }
        execute(task)
    }
}
const iterator = run()
iterator.next()
  1. generator有类似的功能,为什么不直接使用
  • 要是用generator,需要将涉及到的所有代码都包装成genetator * 的形式,非常麻烦,工作量很大
  • generator内部是有状态的,具体例子如下
function * doWork(a,b,c){
    const x = doExpendsiveWorkA(a)
    const y = doExpendsiveWorkB(b)
    const z = doExpendsiveWorkC(x,y,c)
    return z
}

我们已经执行完了doExpendsiveWorkA和doExpendsiveWorkB,还未执行doExpendsiveWorkC。如果此时 b被更新了,那么在新的时间分片里,我们只能沿用之前获取到的x,y结果==>不符合预期 2. 如何判断当前是否有高优先任务 当前js的环境其实并没有办法来判断是否有高优任务

只能约定一个合理的时间,当超过了这个执行时间,如果任务仍然没有执行完成,中断当前任务,把控制权交给浏览器

16ms是一个相对合理的时间,而requestIdleCallback恰好就可以做这个合理的时间, 时浏览器在有空的时候执行我们的回调,这个回调会传入一个参数,表示,浏览器有多少时间执行我们的任务

  • 浏览器在一帧内要做什么事情
  1. 处理用户的输入事件
  2. js的执行
  3. requestAnimation 调用
  4. 布局 layout
  5. 绘制 paint 如果浏览器在16ms以内执行完上述事件,则剩下的事件就是空闲事件,
  • 浏览器很忙怎么办 requestIdleCallback timeout参数,100ms,如果超过这个timeout后,回调还没有被执行,那么会在下一帧强制执行回调
  • 兼容性 requestIdleCallback兼容性非常差,通过messageChannel来模拟实现了requestIdleCallback的功能
  • timeout超时后一定要被执行吗

不是的,react里预定了五个优先级的等级

  • Immediate 最高优先级,这个优先级的任务应该马上执行不能中断
  • userBlocking,比如点击按钮这些用户交互,需要及时得到反馈
  • Normal 不需要用户立即感受到的变化,比如网络请求
  • low,这些任务可以延后,但是最终也需要执行
  • idle,可以被无限期延后

平时用过高阶组件吗,什么是高阶组件,高阶组件能用来做什么

简称HOC,High Order Components

  1. 是一个函数
  2. 入参:原来的react组件
  3. 返回值,新一个新的react组件
  4. 是一个纯函数,不应该有任何的副作用
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('lily')
        wrappedFunc(myName)
    }
    return wrappedFunc
}
const wrappedHello = wrapWithUsername(helloWold)
wrappedHello()

怎么写一个高阶组件

  1. 普通方式
  2. 装饰器
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.setStata({
                    name: username || ''
                })
            }
            render(){
                return(
                    <div>
                        <WrappedComponent name={this.state.name} {...this.props}>
                        <p>身高为{height||0}</p>
                    </div>
                )
            }
        }
    }
}
 import {decoratorWithNameHeight} from '../hoc/index'
 @decoratorWithNameHeight(180)
 class UglyWorld extends Component<Props,any>{
     render(){
         return <div>bye,ugly world</div>
     }
 }
  1. 多个高阶组件的组合
// 如上,多个的时候应是
 @decoratorWithNameWidth(100)
 @decoratorWithNameHeight(180)
 class UglyWorld extends Component<Props,any>{
     render(){
         return <div>bye,ugly world</div>
     }
 }

高阶组件能用来做什么,技术层面上

  1. 属性代理
    • 操作props(上边的例子就是操作props)
    • 操作组件实例
    export const refHooc = ()=>{
        return (WrappedComponent)=>{
            return class extends Component{
                componentDidMount(){
                    // 这里就可以劫持到实例的属性,state,methods
                    console.log(this.ref.state)
                }
                render(){
                    return (
                        <WrappedComponent
                            {...this.props}
                            ref={(instance)=>{
                                this.ref = instance
                            }}
                         />
                    )
                }
            }
        }
    }
    @refHoc
    class RefDemoComponent extends Component{
        state = {
            weight : 60
            height: 170
        }   
        render(){
            return <div>bye ugly world my name is {this.props.name}</div>
        }
    }
    
    1. 继承/劫持
    // render劫持
    export function hijackHoc<T extends {new(...args: any[]):any}>(component){
        return class extends component{
            handleClick(){
                super.handleClick()
            }
            render(){
                const parent = super.render()
                return React.cloneElement(parent,{
                    onClick:()=>this.handleClick()
                })
            }
        }
    }
    
    扩展: connect原理
    1. Provider: Provider的作用是从最外部封装了整个应用,并向connect模块传递store
    2. connect: 负责连接React和Redux
    // 1.store.substrubite(() => {}) 和 unsubscribe();
    // 2.{ ...mapStateToProps( store.getState() ) };
    // 3. {...mapDispatchToProps(store.dispatch)};
    
    import React from "react";
    import store from "./store";
    
    const connect = (mapStateToProps, mapDispatchToProps) => {
      return (Wrapper) => {     // Wrapper 传递进来的组件
        class WrapperFragment extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              // 连接store并初始化state
              storeState: { ...mapStateToProps(store.getState()) }, 
            };
          }
    
          componentDidMount() {
              this.unsubscribe = store.subscribe(() => {                                 // 组件订阅 store的更新
              this.setState({
                storeState: { ...mapStateToProps(store.getState()) },
              });
            });
          }
          componentWillUnmount() {
            // 组件卸载时取消对store的更新
            this.unsubscribe();
          }
           render() {
                return (
                  <Wrapper
                    // 传递参数
                    {...this.props}   
                    // 将store.getState() 获取到的参数作为porps传递进 组件
                    {...mapStateToProps(store.getState())}   
                    // 将store.dispatch() 获取到的更新store的方法作为porps传递进 组件
                    {...mapDispatchToProps(store.dispatch)}     
                  />
                );
              }
            }
            return WrapperFragment;
          };
        };
    
        export default connect;
    

    什么是React hooks? React hooks有什么优势

    hooks: 可以在不写class组件的情况下,使用state和其它react特性 eg: useState,useEffect,useMemo 为什么不写class而是转向了hooks的写法

    react hooks的优势

    class的缺点
    1. 组件间的状态逻辑很难复用 组件间如果有state的逻辑是相似的,class模式下基本上可以用高阶组件来解决 虽然能够解决问题,但是需要在元素,组件外部再包一层会致层级非常冗余

    2. 复杂业务的有状态组件会越来越复杂

    3. 监听和定时器的操作,被分散在多个区域

    4. this的指向问题

      render中绑定函数,会使每次都返回一个新的函数,造成组件每次都渲染,资源浪费

    hooks的优点

    1. 利于业务逻辑的封装和拆分,可以非常自由的组合各种自定义hooks,
    2. 可以在无需修改组件结构的情况下,复用状态逻辑
    3. 定时器,监听等都被聚合到同一块代码下

    hooks的使用注意事项

    1. 只能在函数内部的最外层调用hook,不要在循环,条件判断或者子函数中调用
    2. 只能在react的函数组件中调用hook,不要再其它的js函数里调用

    问题

    1. 为什么,hooks不能在循环和条件判断中调用
    2. 为什么useEffect的第二个参数是空数组,就相当于componentDIdmout只执行一次
    3. 自定义的hook怎么操作组件的

    手写代码实现useState

    function Counter(){
        const [count,setCount] = useState(0)
        const onClick = ()=>{
            setCount(count+1)
        }
        return (
            <div>
                <div>{count}</div>
                <button onCLick={onClick}>点击</button>
            </div>
        )
    }
    let stateArray: any[] = []
    let cursor: number = 0
    function useState<T>(initialState:T):[T,(newState:T)=>void]{
        const currentCursor = cursor
        stateArray[currentCursor] = stateArray[currentCursor] || initialState
    
        setState = (newState: T)=>{
             stateArray[currentCursor] = newState
        }
        ++cursor
        return [stateArray[currentCursor],setState]
    }
    export function render(){
        ReactDom.render(
            <ReactDOM.StrictMode>
                <Counter />
            </ReactDOM.StrictMode>,
            document.getElementNyId('root')
        )
        cursor = 0 // 每次渲染把游标值为0
    }
    

    手写代码实现useEffect

    // 同上边的例子,增加useEffect调用
    useEffect(()=>{
        console.log('count发生了改变'+count,)
    },[count])
    const allDeps: Array<any[] | undefined> = []
    let effectCursor: number = 0
    function useEffect(callback: ()=>void,depArr?: any[]){
        if(!depArr){
            callback()
            allDeps[effectCursor] = depArr
            effectCursor++
            return
        }
        const deps = allDeps[effectCursor]
        const hasChanged = deps ? depArr.some((el,i)=>el !== deps[i]): true
        if(hasChange()){
            callback()
            allDeps[effectCursor] = depArr
        }
        effectCursor++
    }