• React hook 以及 React Fiber 原理


    /*
     * @Descripttion: React hook 以及 React Fiber 原理
     * @version: 
     * @Author: lhl
     * @Date: 2021-03-10 15:42:23
     * @LastEditors: lhl
     * @LastEditTime: 2021-03-23 15:59:18
     */
    // hook使用规则
    // 只在最顶层使用 Hook
    // 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们
    // 只在 React 函数中调用 Hook 不要在普通的 JavaScript 函数中调用 Hoo
    // 在 React 的函数组件中调用 Hook 在自定义 Hook 中调用其他 Hook
    // npm install eslint-plugin-react-hooks --save-dev ESLint 插件来强制执行这两条规则
    // ESLint 配置
    /*
        {
            "plugins": [
            // ...
            "react-hooks"
            ],
            "rules": {
            // ...
            "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
            "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
            }
        }
    */
    
    // 为什么选择使用 Hook
    /**
     * 1.在组件之间复用状态逻辑很难
     * 2.复杂组件变得难以理解
     * 3.用更少的代码,实现同样的效果
     */
    
    // React Hooks的几个常用钩子  
    // useCallback 的功能完全可以由 useMemo 所取代
    // useCallback(fn, inputs)    ===   useMemo(() => fn, inputs)
    /**
        1.useState()    // 状态钩子
        2.useContext()  // 共享状态钩子
        3.useReducer()  // action 钩子
        4.useEffect()   // 副作用钩子 useEffect在浏览器渲染完成后执行
        5.useCallback()  // 记忆函数  
        6.useMemo() // 记忆组件 
        7.useRef() // 保存引用值
        8.useImperativeHandle() // 穿透 Ref
        9.useLayoutEffect() // 同步执行副作用 不常用  useLayoutEffect在浏览器渲染前执行
        10.自定义 hook  // 以useXX开头的 封装重复使用的代码提高复用性
     * 
    */
    import React, { useState, useEffect, useRef, useReducer, useCallback, useMemo, forwardRef, useImperativeHandle, Component } from 'react';
    
    const HookComp = () => {
        const size = useChangeSize();
        const [count, setCount] = useState(0);
        const [title, setTitle] = useState('标题')
        let timer = useRef();
        const ref = useRef();
        // useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。
        // 它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
        
        // 相当于 componentDidMount 和 componentDidUpdate:
        useEffect(() => {
            timer.current = setInterval(() => {
               console.log(1)
            },1000)
            document.title = `You clicked ${count} times`;
            return () => {
                clearInterval(timer.current) //相当于 componentWillUnmount
            } 
        },[count]);
    
        useEffect(() => {
            ref.current.open()
        },[])
        
        return (
            <div>
              <p>You clicked {count} times</p>
              <button onClick={() => setCount(count + 1)}>
                Click me
              </button>
              <input type="text" value={title}  onChange={e => setTitle(e.target.value)} />
              <p>{title}</p>
              <div>页面大小{size.width}*****{size.height}</div>
              <TimeTest/>
              <TestRef ref={ref}></TestRef>
              <TestRef1 ref={ref}></TestRef1>
              <BookkList/>
            </div>
        )
    }
    
    // useReducer 用法
    function BookkList() {
        const inputRef = useRef();
        const [items, dispatch] = useReducer((state,action)=> {
            console.log(state,action)
            switch(action.type){
                case 'add':
                    return [
                        ...state,
                        {
                            id: state.length,
                            name: action.name
                        }
                    ]
                case 'del':
                    return state.filter((_,index) => index != action.index)
            }
        },[])
        
        function handleAdd(event){
            event.preventDefault();
            dispatch({
                type:'add',
                name:inputRef.current.value
            });
            inputRef.current.value = '';
        }
    
        return (
            <>
                <form>
                    <input ref={inputRef}/>
                    <button onClick={ handleAdd }>点击添加</button>
                </form>
                <ul>
                    {
                        items.map((item, index) => (
                            <li key={item.id}>
                                {item.name}
                                <button onClick={ () => dispatch({type:'del',index})}>点击删除</button>
                            </li>
                        ))
                    }
                </ul>
            </>
        )
    }
    
    /*
        useRef是一个方法,且useRef返回一个可变的ref对象
        修改 ref 的值是不会引发组件的重新 render 
        useRef非常常用的一个操作,访问DOM节点,对DOM进行操作,监听事件等等
        ref 在所有 render 都保持着唯一的引用,因此所有的对 ref 的赋值或者取值拿到的都是一个 最终 的状态,而不会存在隔离
        使用 useRef 来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染
        createRef 与 useRef 的区别: createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。
        forwardRef是用来解决HOC组件传递ref的问题的
    */
    const TestRef = forwardRef((props, ref) => {
        useImperativeHandle(ref, () => ({
          open() {
            alert("ref")
          }
        }))
        return (
            <div>useImperativeHandle 穿透 ref的使用</div>
        )
    })
    
    // 等同于 =>
    // useImperativeHandle(ref,createHandle,[deps])可以自定义暴露给父组件的实例值。如果不使用,
    // 父组件的 ref(chidlRef) 访问不到任何值(childRef.current==null)
    // useImperativeHandle应该与forwradRef搭配使用
    // React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。
    // React.forward接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数
    
    const ChildComponent = (props, ref) => {
      useImperativeHandle(ref, () => ({
        open() {
            alert("ref")
        }
      }));
      return <h3>useImperativeHandle 穿透 ref的使用</h3>;
    };
    const TestRef1 = forwardRef(ChildComponent);
    
    
    function TimeTest(){
        const [count, setCount] = useState(0);
        const preCount =  usePreVal(count) // 使用自定义hook
        const doubleCount = useMemo(() => {
          return 2 * count;
        }, [count]);
      
        const timerID = useRef();
        
        useEffect(() => {
          timerID.current = setInterval(()=>{
              setCount(count => count + 1);
          }, 1000); 
        }, []);
        
        useEffect(()=>{
            if(count > 10){
                console.log('大于10定时器不再走了')
                clearInterval(timerID.current);
            }
        });
        
        return (
          <>
            <button ref={timerID} onClick={() => {setCount(count + 1)}}>
                Count: {count} === double: {doubleCount} === preCount:{preCount}
            </button>
          </>
        );
    }
    
    // 使用 const preState =  usePreVal(state) 获取上一个值
    // useRef 不仅仅是用来管理 DOM ref 的,它还相当于 this , 可以存放任何变量
    function usePreVal(state){
    
        const ref = useRef();
    
        useEffect(() => {
            ref.current = state
        })
        
        return ref.current
    }
      
    // 自定义hooks,用use开头命名,封装重复使用的代码,在多个场景下使用,提高代码的复用性
    // react hook监听窗口大小
    function useChangeSize(){
        const win = {
             document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        }
        const [size, setSize] = useState(win)
    
        // useCallback缓存方法  [] 只执行一次
        const onResize = useCallback(()=>{
            console.log('111')
            setSize(win)
        },[])
    
        useEffect(() => {
            window.addEventListener('resize',onResize)
            // 销毁时
            return () => {
                window.removeEventListener('resize',onResize)
            }
        },[onResize])
        
        return size
    }
    
    export default HookComp
    
    /**
     * 复用一个有状态的组件引发的思考:
     * 
        HOC【高阶组件: 一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件】使用的问题:
        嵌套地狱,每一次HOC调用都会产生一个组件实例
        可以使用类装饰器缓解组件嵌套带来的可维护性问题,但装饰器本质上还是HOC
        包裹太多层级之后,可能会带来props属性的覆盖问题
    
        Render Props【渲染属性】:
        数据流向更直观了,子孙组件可以很明确地看到数据来源
        但本质上Render Props是基于闭包实现的,大量地用于组件的复用将不可避免地引入了callback hell问题
        丢失了组件的上下文,因此没有this.props属性,不能像HOC那样访问this.props.children
     * 
    
        涉及优化对比 函数组件 hook 和 class 【类】组件、函数组件
        
        useCallback 和 useMemo 区别    
        useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs)
        useCallback和useMemo的参数跟useEffect一致
        useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,
        useMemo返回缓存的变量,useCallback返回缓存的函数。
     * 唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你
        
        demo: useMemo 的用法
        export default function WithMemo() {
            const [count, setCount] = useState(1);
            const [val, setValue] = useState('');
            const countSum = useMemo(() => {
                console.log('compute');
                let sum = 0;
                for (let i = 0; i < count * 100; i++) {
                    sum += i;
                }
                return sum;
            }, [count]);
        
            return <div>
                <h4>{count}-{countSum}</h4>
                {val}
                <div>
                    <button onClick={() => setCount(count + 1)}>+c1</button>
                    <input value={val} onChange={event => setValue(event.target.value)}/>
                </div>
            </div>;
        }
    
        demo: useCallback 的用法
        function Parent() {
            const [count, setCount] = useState(0);
            const [val, setVal] = useState('');
    
            const callback = useCallback(() => {
                return count;
            }, [count]);
    
            return <div>
                <p>{count}</p>
                <Child callback={callback}/>
                <div>
                    <input value={val} onChange={event => setVal(event.target.value)}/>
                    <button onClick={() => setCount(count + 1)}>add</button>
                </div>
            </div>;
        }
     
        function Child({ callback }) {
            const [count, setCount] = useState(() => callback());
            useEffect(() => {
                setCount(callback());
            }, [callback]);
            return <div> {count} </div>
        }
        使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,
        如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,
        然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新
        
        所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景
     * 
     * 和 函数组件 class【类】 组件对比
     * import React, { PureComponent } from 'react'
     * import React, { Component } from 'react'
     * React.memo(comp) 是React v16.6引进来的新属性
     * React.memo会返回一个纯化(purified)的组件MemoFuncComponent
     * 当组件的参数props和状态state发生改变时,React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染
     * 它的作用和React.PureComponent类似,是用来控制函数组件的重新渲染的。React.memo(...) 其实就是函数组件的 React.PureComponent
     * 
     * demo:
     * class: Component 需要判断何时渲染 何时不会进行组件的重新渲染
     * shouldComponentUpdate(nextProps, nextState) {
            if (this.state.count === nextState.count) {
                return false
            }
            return true
        }
     *  const Funcomponent = ()=> {
            return (
                <div>
                    Hiya!! I am a Funtional component
                </div>
            )
        }
        const MemodFuncComponent = React.memo(FunComponent)
        
        React.PureComponent是给ES6的类组件使用的
        React.memo(comp)是给函数组件使用的
        React.PureComponent减少ES6的类组件的无用渲染
        React.memo(comp)减少函数组件的无用渲染
        继承PureComponent时,不能再重写shouldComponentUpdate
        useEffect、useMemo、useCallback都是自带闭包的。每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),
        所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。
        对于这种情况,应该使用ref来访问。
        依赖为 [] 时: re-render 不会重新执行 effect 函数
        没有依赖:re-render 会重新执行 effect 函数
    
        useLayoutEffect 和 componentDidMount 和 componentDidUpdate 触发时机一致(都在在DOM修改后且浏览器渲染之前);
        useLayoutEffect 要比 useEffect 更早的触发执行;
        useLayoutEffect 会阻塞浏览器渲染,切记执行同步的耗时操作
    
        关于class 组件的 createRef
        class RefTest extends React.Component{
            constructor(props){
                super(props);
                this.myRef = React.createRef();
            }
            componentDidMount(){
                console.log(this.myRef.current);
            }
            render(){
                return <input ref={this.myRef}/>
            }
        }
     * 
    */
    
    // React 组件种类
    // 1 Es5原生方式React.createClass定义的组件
    // const Eg1 = React.createClass({
    //     getInitialState:function(){
    //       return {
    //            name:'react'
    //       };
    //     },
    //     render:function(){
    //         return  <div onClick={this._ClickEvent}>{ this.state.name }</div>
    //     },
    //     _ClickEvent:function(){
    //         console.log(`事件`)
    //     }
    // })
    
    // 2 无状态组件 【功能组件】
    function Eg2(props){
        return <div>{ props.name }</div>
    }
    
    // 3 类组件 【有状态组件 or 容器组件】
    class Eg3 extends Component {
        constructor(props){
            super(props)
            this.state = {
                
            }
        }
        render() {
            return (
                <div>
                    
                </div>
            )
        }
    }
    
    // 4 渲染组件 与 无状态组件类似
    const Eg4 = (props) => {
        return (
            <div>{ props.name }</div>
        )
    }
    
    // 5 高阶组件 HOC 【不要在 render 方法中使用 HOC】
    // 一个高阶组件是一个函数,它输入一个组件,然后返回一个新的组件 const EnhancedComponent = higherOrderComponent(WrappedComponent);
    function Eg5(WrappedComponent) {
        // HOC设置显示名称
        Eg5.displayName = `Eg5(${getDisplayName(WrappedComponent)})`;
    
        return class extends React.Component {
          componentDidUpdate(prevProps) {
            console.log('Current props: ', this.props);
            console.log('Previous props: ', prevProps);
          }
          
          render() {
            return <WrappedComponent {...this.props} />;
          }
        }
    }
    
    // HOC设置显示名称
    function getDisplayName(WrappedComponent) {
        return WrappedComponent.displayName || WrappedComponent.name || "Component";
    }
    
    // 6 function + hook 组件
    
    /**
     *  React Fiber 【Fiber 架构就是用 异步的方式解决旧版本 同步递归导致的性能问题】
        旧版 React 通过递归的方式进行渲染,使用的是 【JS引擎自身的函数调用栈】,它会一直执行到栈空为止。
        Fiber实现了自己的【组件调用栈】,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。
        实现方式是使用了浏览器的 requestIdleCallback 这一 API:
        将 计算任务 分给成一个个小任务,分批完成,在完成一个小任务后,将控制权还给浏览器,让浏览器利用间隙进行 UI 渲染
    
        React Fiber 出现的背景:
        当页面元素很多,且需要频繁刷新的场景下,浏览器页面会出现卡顿现象,原因是因为 计算任务 持续占据着主线程,从而阻塞了 UI 渲染
        
        React 框架内部的运作可以分为 3 层
        Virtual DOM 层 --> 描述页面长什么样
        Reconciler 层 --> 负责调用组件生命周期方法,进行 Diff 运算等。
        Renderer 层 --> 根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative
     * 
     * Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示
     * 
     * const fiber = {
            stateNode,    // 节点实例
            child,        // 子节点
            sibling,      // 兄弟节点
            return,       // 父节点
        }
    
        Fiber Reconciler 的 2 个阶段:
        阶段一,生成 Fiber 树【本质来说是一个链表】,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
        【让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率】
        阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断
    
        阶段一有两颗树,Virtual DOM 树和 Fiber 树,Fiber 树是在 Virtual DOM 树的基础上通过额外信息生成的。
        它每生成一个新节点,就会将控制权还给浏览器,如果浏览器没有更高级别的任务要执行,则继续构建;反之则会丢弃 正在生成的 Fiber 树,等空闲的时候再重新执行一遍
        
        【V16版本之前】栈调和(Stack reconciler) --> 【V16版本之后】 Fiber reconciler
    
        React diff 将传统 diff 算法的复杂度 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题
        React Diff三大策略: 【将 Virtual DOM 树转换成 actual DOM 树的最少操作的过程称为 协调(Reconciliaton)】
        1.tree diff: 【Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计】
            React对 Virtual DOM 树进行层级控制,只会对相同层级的DOM节点进行比较,即同一个父元素下的所有子节点,
            当发现节点已经不存在了,则会删除掉该节点下所有的子节点,不会再进行比较。
            这样只需要对DOM树进行一次遍历,就可以完成整个树的比较。复杂度变为O(n);
        2.component diff: 【拥有相同类的两个组件 生成相似的树形结构,拥有不同类的两个组件 生成不同的树形结构】
        3.element diff: 【对于同一层级的一组子节点,通过唯一id区分】 当节点属于同一层级:插入、移动、删除 【设置唯一 key的策略】
     */

     未经允许,请勿随意转载!!谢谢合作!!!

  • 相关阅读:
    【转】Chrome 控制台不完全指南
    AngularJS 之 Factory vs Service vs Provider【转】
    【转】NuGet.org 无法访问的解决方法
    jquery easyui 1.4.1 验证时tooltip 的位置调整
    jquery easyui 1.4.1 API( CHM版)
    扩展 easyui-tabs 插件 关闭标签页方法
    easyui layout 折叠后显示标题
    easyui 中Datagrid 控件在列较多且无数据时,列显示不全的解决方案
    为easyui datagrid 添加上下方向键移动
    Android布局实现圆角边框
  • 原文地址:https://www.cnblogs.com/lhl66/p/14583737.html
Copyright © 2020-2023  润新知