深入react框架学习

107 阅读8分钟

1.React Hooks实现原理

如果让我们来实现一个React Hooks我们如何实现呢?好像毫无头绪,可以先看一个简单的useState:(这部分内容只是帮我们更好的理解Hooks工作原理,想了解Hooks最佳实践可以直接查看React 生产应用)

    function App(){
        const [count,setCount] = useState(0);
        useEffect(()=>{
            console.log(`update--${count}`)
        },[count])
        return(
            <div>
                <button onClick={()=>setCount(count+1)}>
                {`当前点击次数:${count}`}
                </button>
            </div>
        )
    }
复制代码
  • 实现setState

上面可以看出来当调用useState时;会返回一个变量和一个函数,其参数时返回变量的默认值。我们先构建如下的useState函数:

function useState(initVal) {
    let val = initVal;
    function setVal(newVal) {
        val = newVal;
        render(); // 修改val后 重新渲染页面
    }
    return [val, setVal];
}
复制代码

我们可以在代码中来使用useState--查看demo;

不出意外当我们点击页面上的按钮时候,按钮中数字并不会改变;看控制台中每次点击都会输出0,说明useState是执行了。由于val是在函数内部被声明的,每次useState都会重新声明val从而导致状态无法被保存,因此我们需要将val放到全局作用域声明。

let val; // 放到全局作用域
function useState(initVal) {
    val = val|| initVal; // 判断val是否存在 存在就使用
    function setVal(newVal) {
        val = newVal;
        render(); // 修改val后 重新渲染页面
    }
    return [val, setVal];
}
复制代码

修改useState后,点击按钮时按钮就发生改变了--修改后Demo

  • 实现useEffect

useEffect是一个函数,有两个参数一个是函数,一个是可选参数-数组,根据第二个参数中是否有变化来判断是否执行第一个参数的函数:

// 实现第一版 不考虑第二个参数
function useEffect(fn){
    fn();
}
复制代码

ok!不考虑第二个参数很简单,其实就是执行下函数--这里查看Demo(控制台中能看到useEffect执行了)。但是我们需要根据第二个参数来判断是否执行,而不是一直执行。所以我们还需要有一个判断逻辑去执行函数。

let watchArr; // 为了记录状态变化 放到全局作用域
function useEffect(fn,watch){
    // 判断是否变化 
    const hasWatchChange = watchArr?
    !watch.every((val,i)=>{ val===watchArr[i] }):true;
    if( hasWatchChange ){
        fn();
        watchArr = watch;
    }
}
复制代码

完成好useEffect我们在去测试下 --测试demo

打开测试页面我们每次点击按钮,控制台会打印当前更新的count;到目前为止,我们模拟实现了useStateuseEffect可以正常工作了。不知道大家是否还记得我们通过全局变量来保证状态的实时更新;如果组件中要多次调用,就会发生变量冲突的问题,因为他们共享一个全局变量。如何解决这个问题呢?

  • 解决同时调用多个 useState useEffect的问题
// 通过数组维护变量
let memoizedState  = [];
let currentCursor = 0;

function useState(initVal) {
    memoizedState[currentCursor] = memoizedState[currentCursor] || initVal;
    function setVal(newVal) {
        memoizedState[currentCursor] = newVal;
        render(); 
    }
    // 返回state 然后 currentCursor+1
    return [memoizedState[currentCursor++], setVal]; 
}

function useEffect(fn, watch) {
  const hasWatchChange = memoizedState[currentCursor]
    ? !watch.every((val, i) => val === memoizedState[currentCursor][i])
    : true;
  if (hasWatchChange) {
    fn();
    memoizedState[currentCursor] = watch;
    currentCursor++; // 累加 currentCursor
  }
}
复制代码

修改核心是将useState,useEffect按照调用的顺序放入memoizedState中,每次更新时,按照顺序进行取值和判断逻辑--查看Demo

  • 图解React Hooks 原理

图解Reat Hooks

如上图我们根据调用hooks顺序,将hooks依次存入数组memoizedState中,每次存入时都是将当前的currentcursor作为数组的下标,将其传入的值作为数组的值,然后在累加currentcursor,所以hooks的状态值都被存入数组中memoizedState

图解React Hooks执行更新

上面状态更新图,我们可以看到执行setCount(count + 1)setData(data + 2)时,先将旧数组memoizedState中对应的值取出来重新复值,从而生成新数组memoizedState。对于是否执行useEffect通过判断其第二个参数是否发生变化而决定的。

这里我们就知道了为啥官方文档介绍:不要在循环,条件或嵌套函数中调用 Hooks, 确保总是在你的 React 函数的最顶层调用他们。因为我们是根据调用hooks的顺序依次将值存入数组中,如果在判断逻辑循环嵌套中,就有可能导致更新时不能获取到对应的值,从而导致取值混乱。同时useEffect第二个参数是数组,也是因为它就是以数组的形式存入的。

当然,react官方不会像我们这么粗暴的方式去实现的,想了解官方是如何实现可以去这里查看

点击出处链接:

2.高阶组件

1.定义:高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

2.hoc:高阶组件是参数为组件,返回值为新组件的函数

3.HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用

  1. 优点:复用了业务逻辑提高了开发效率,同时还方便后期维护
  2. 缺点:
  • 高阶组件的props都是直接透传下来,无法确实子组件的props的来源。
  • 可能会出现props重复导致报错。
  • 组件的嵌套层级太深。
  • 会导致ref丢失。

点击案例出处链接:

3.mixin

React mixin 是通过React.createClass创建组件时使用的,现在主流都是通过ES6方式创建react组件,官方因为mixin不好追踪变化以及影响性能,所以放弃了对其支持,同时也不推荐我们使用。这里就简单介绍下mixin:

mixin的原理其实就是将[mixin]里面的方法合并到组件的prototype上

点击案例::

4.mobox 和 redux 有什么区别?

(1)共同点

  • 为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;
  • 某一状态只有一个可信数据来源(通常命名为store,指状态容器);
  • 操作更新状态方式统一,并且可控(通常以action方式提供更新状态的途径);
  • 支持将store与React组件连接,如react-redux,mobx- react;

(2)区别 Redux更多的是遵循Flux模式的一种实现,是一个 JavaScript库,它关注点主要是以下几方面∶

  • Action∶ 一个JavaScript对象,描述动作相关信息,主要包含type属性和payload属性∶

    o type∶ action 类型; o payload∶ 负载数据;
    复制代码
    
  • Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;

  • Store∶ 管理action和reducer及其关系的对象,主要提供以下功能∶

    o 维护应用状态并支持访问状态(getState());
    o 支持监听action的分发,更新状态(dispatch(action)); 
    o 支持订阅store的变更(subscribe(listener));
    复制代码
    
  • 异步流∶ 由于Redux所有对store状态的变更,都应该通过action触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入React组件中,就需要使用其他框架配合管理异步任务流程,如redux-thunk,redux-saga等;

Mobx是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶

  • Action∶定义改变状态的动作函数,包括如何变更状态;
  • Store∶ 集中管理模块状态(State)和动作(action)
  • Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据

对比总结:

  • redux将数据保存在单一的store中,mobx将数据保存在分散的多个store中
  • redux使用plain object保存数据,需要手动处理变化后的操作;mobx适用observable保存数据,数据变化后自动处理响应的操作
  • redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx中的状态是可变的,可以直接对其进行修改
  • mobx相对来说比较简单,在其中有很多的抽象,mobx更多的使用面向对象的编程思维;redux会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
  • mobx中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而redux提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易

5. hooks的一个更新流程?

调 用 useState 返 回的第二个参数内部调用的就是 dispatchAction。dispatchAction内部会创建一个update对象,这个对象会赋值 给fiber节点(当前触发更新的)。fiber对应是app这个函数组件。fiber会传给scheduleUpdateOnFilber函数,调度这次更新,然后这个fiber就会跟其他的fiber抢优先级,谁优先级高,就会先进去render阶段,diff算法,看哪些dom要变化。然后就会进入commitRoot,commitRoot传的参数是root,root是fiberRootNode,也是整个应用的根节点,root.current是当前应用的根节点,也就是ReactDom.render(,rootEl)对应的这个应用的跟节点,root.current.firstEffect是本次要执行更新的节点有哪些,它是一个链表,它上边有一个nextEffect字段,指下一个要更新的节点,要是还是下下个,就下一个对象的nextEffect就指向下下个。从而形成一个链表。遍历这个链表,就会知道有哪些dom要执行更新操作。