react class模式存在一些问题:
- 难搞的this,函数需要bind
- 复杂的生命周期
- 继承和组件嵌套需要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
包裹,且其实现中拥有useState
,useReducer
或useContext
的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
为什么我们把deps设置为空数组,但是callback永远不变呢?两个[]明明不相等?
源码中使用的是Object.is判断两次依赖,如果是空数组,直接跳过for循环,返回true
useMemo
useMemo, useCallback 都会在组件第一次渲染时候执行,之后会在其依赖的变量发生改变时再执行,并且这两个hook都会返回缓存的值:
useMemo返回缓存的变量
useCallback返回缓存的函数
场景:
- 子组件使用useEffect并且依赖了父组件传递的参数,这种情况只要父组件更新,子组件必然更新,即使参数值没有变,但是引用变了。【解决:在父组件中用useCallback / useMemo将传递给子组件的参数包裹】
为什么在JSX文件里写html,必须引入React?
因为jsx 最终编译为 React.createElement