hooks原理

129 阅读4分钟

react class模式存在一些问题:

  1. 难搞的this,函数需要bind
  2. 复杂的生命周期
  3. 继承和组件嵌套需要props层层传递

最重要的是 class component不符合UI=F(data)的理念!

每个hook里维持了一个memorizedState来存放我们外部更新的state值,并且有一个next来挂住下一个hook,让组件的所有钩子呈链表形式;函数组件在渲染时将hook按照调用顺序以链表的形式挂在FiberNode上(这个FiberNode可以理解成每个函数组件在渲染时都会生成的一个挂载信息的点)

得到的链表如下所示:

FiberNode

memorizedState --->   hookA

...etc                                memorizedState

                                         next             -------------->      hookB

                                                                                              memorizedState

                                                                                              next

FiberNode属性的memorizedState用来指向hook链表的第一个节点,hook的属性memorizedState 用来存放state值

useState

第一次渲染时,useState会将初始值存放在hook.memorizedState上,并将其和保留了【组件对应的Fiber Node】和【更新队列hook.queue】引用的diapatch函数一起返回,这样我们就得到一个初始变量和一个setState函数

当点击触发更新操作后setState(dispatchAction)会将回调函数作为更新操作放在更新队列hook.queue中,然后schedulework,之后触发重新渲染。在重新渲染阶段函数组件会被再次执行,此时useState会先执行更新队列hook.queue里的更新动作,然后返回更新后的值和dispatch函数

为什么要有更新队列呢?

因为每次setstate的时候,dispatch操作是异步的,并不会立刻更新值并重新渲染,而是先放在UpdateQueue里,并且还要做一些优先级策略,所以要把所有的action操作记录下来,保证更新渲染的时候依此执行,拿到最新的state值

注意:hooks是链表结构挂载的,所以要按照顺序调用,不能放在条件语句或者循环中

useReducer

// 传入一个reducer(可以自己定义)和state初始值
// 在reducer中可根据dispatch触发的type,来做相应的处理,返回新的state
const [state, dispatch] = useReducer(reducer,initialState);

const myReducer = (state, action) => {
    switch(action.type) {
        case('') ...
    }
}

useEffect

可以用来进入具有副作用的操作,最常见的就是向服务器请求数据;在return 里可以做一些清除副作用的操作,比如unsubscribe,类似于 componentWillUnmount。第二个参数设置为空数组,表示没有可以依赖的对象,所以只会在一次渲染的时候调用。

注意:如果第二个参数不传,效果跟空数组是不同的,不传的话会一直调用

useCallBack

const memorizedCallBack = useCallBack(() => { doSomething(a,b) }, [a,b])

只要依赖不变,我们返回的callback就不会变

场景:被父组件包含的子组件,会随着父组件的更新而重新渲染,但子组件没有更新的话是没有必要重新渲染的。所以,我们可以借助useCallBack返回一个函数,将这个函数作为props传给子组件,以此来避免不必要的更新。(⚠️:要搭配React.memo才有用)

在class时代,我们用PurComponent,对props进行一次浅比较,也可以在shouldComponent中进行更深层次的比较

React.memo是一个高阶组件。如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReduceruseContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

为什么我们把deps设置为空数组,但是callback永远不变呢?两个[]明明不相等?

源码中使用的是Object.is判断两次依赖,如果是空数组,直接跳过for循环,返回true

useMemo

useMemo, useCallback 都会在组件第一次渲染时候执行,之后会在其依赖的变量发生改变时再执行,并且这两个hook都会返回缓存的值:

useMemo返回缓存的变量

useCallback返回缓存的函数

场景:

  • 子组件使用useEffect并且依赖了父组件传递的参数,这种情况只要父组件更新,子组件必然更新,即使参数值没有变,但是引用变了。【解决:在父组件中用useCallback / useMemo将传递给子组件的参数包裹】

为什么在JSX文件里写html,必须引入React?

因为jsx 最终编译为 React.createElement